aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile29
-rw-r--r--[-rwxr-xr-x]Rakefile2
-rw-r--r--actionmailer/CHANGELOG.md2
-rw-r--r--actionmailer/README.rdoc6
-rw-r--r--[-rwxr-xr-x]actionmailer/Rakefile1
-rw-r--r--actionmailer/lib/action_mailer.rb3
-rw-r--r--actionmailer/lib/action_mailer/base.rb9
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb17
-rw-r--r--actionmailer/test/base_test.rb13
-rw-r--r--actionmailer/test/fixtures/anonymous/welcome.erb1
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb2
-rw-r--r--actionmailer/test/url_test.rb4
-rw-r--r--actionpack/CHANGELOG.md58
-rw-r--r--[-rwxr-xr-x]actionpack/Rakefile2
-rw-r--r--actionpack/lib/abstract_controller.rb4
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb2
-rw-r--r--actionpack/lib/abstract_controller/base.rb7
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb22
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb6
-rw-r--r--actionpack/lib/abstract_controller/logger.rb2
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb6
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb14
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb1
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb56
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb5
-rw-r--r--actionpack/lib/action_controller/metal/head.rb23
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb1
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb88
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb14
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb9
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb15
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb12
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb3
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb2
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb8
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb3
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb14
-rw-r--r--actionpack/lib/action_controller/test_case.rb76
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb3
-rw-r--r--actionpack/lib/action_dispatch.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb15
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb47
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb38
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb18
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb90
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb44
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb4
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb166
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb73
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb14
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb54
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb136
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb41
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/tag.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb31
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb1
-rw-r--r--actionpack/lib/action_view.rb9
-rw-r--r--actionpack/lib/action_view/asset_paths.rb2
-rw-r--r--actionpack/lib/action_view/base.rb5
-rw-r--r--actionpack/lib/action_view/context.rb4
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb109
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb1
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb62
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb49
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb142
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb62
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb90
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb46
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb41
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb265
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb15
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb1
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb10
-rw-r--r--actionpack/lib/action_view/helpers/tags/check_box.rb6
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_helpers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb1
-rw-r--r--actionpack/lib/action_view/helpers/tags/file_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/hidden_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/label.rb6
-rw-r--r--actionpack/lib/action_view/helpers/tags/number_field.rb1
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_field.rb14
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb132
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb19
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb27
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb4
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb94
-rw-r--r--actionpack/lib/action_view/template/handlers.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/raw.rb11
-rw-r--r--actionpack/lib/action_view/template/resolver.rb5
-rw-r--r--actionpack/test/abstract_unit.rb10
-rw-r--r--actionpack/test/activerecord/active_record_store_test.rb9
-rw-r--r--actionpack/test/activerecord/render_partial_with_record_identification_test.rb4
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb61
-rw-r--r--actionpack/test/controller/base_test.rb16
-rw-r--r--actionpack/test/controller/caching_test.rb39
-rw-r--r--actionpack/test/controller/filters_test.rb7
-rw-r--r--actionpack/test/controller/flash_test.rb2
-rw-r--r--actionpack/test/controller/force_ssl_test.rb23
-rw-r--r--actionpack/test/controller/integration_test.rb97
-rw-r--r--actionpack/test/controller/mime_responds_test.rb34
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb66
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb8
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb17
-rw-r--r--actionpack/test/controller/redirect_test.rb4
-rw-r--r--actionpack/test/controller/render_json_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb31
-rw-r--r--actionpack/test/controller/render_xml_test.rb2
-rw-r--r--actionpack/test/controller/rescue_test.rb11
-rw-r--r--actionpack/test/controller/resources_test.rb4
-rw-r--r--actionpack/test/controller/routing_test.rb358
-rw-r--r--actionpack/test/controller/send_file_test.rb11
-rw-r--r--actionpack/test/controller/test_case_test.rb93
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb32
-rw-r--r--actionpack/test/controller/url_for_test.rb16
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb2
-rw-r--r--actionpack/test/controller/webservice_test.rb2
-rw-r--r--actionpack/test/dispatch/cookies_test.rb44
-rw-r--r--actionpack/test/dispatch/header_test.rb5
-rw-r--r--actionpack/test/dispatch/mapper_test.rb19
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb9
-rw-r--r--actionpack/test/dispatch/mount_test.rb5
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb26
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/session_test.rb48
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/xml_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request_id_test.rb4
-rw-r--r--actionpack/test/dispatch/request_test.rb134
-rw-r--r--actionpack/test/dispatch/routing_assertions_test.rb6
-rw-r--r--actionpack/test/dispatch/routing_test.rb353
-rw-r--r--actionpack/test/dispatch/session/abstract_store_test.rb56
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb15
-rw-r--r--actionpack/test/fixtures/plain_text.raw1
-rw-r--r--actionpack/test/fixtures/plain_text_with_characters.raw1
-rw-r--r--actionpack/test/fixtures/routes/bogus.rb1
-rw-r--r--actionpack/test/fixtures/routes/external.rb1
-rw-r--r--actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb1
-rw-r--r--actionpack/test/fixtures/test/hello_world_with_partial.html.erb2
-rw-r--r--actionpack/test/fixtures/translations/templates/default.erb1
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb198
-rw-r--r--actionpack/test/template/atom_feed_helper_test.rb31
-rw-r--r--actionpack/test/template/benchmark_helper_test.rb2
-rw-r--r--actionpack/test/template/date_helper_i18n_test.rb41
-rw-r--r--actionpack/test/template/date_helper_test.rb138
-rw-r--r--actionpack/test/template/erb/tag_helper_test.rb6
-rw-r--r--actionpack/test/template/form_collections_helper_test.rb9
-rw-r--r--actionpack/test/template/form_helper_test.rb209
-rw-r--r--actionpack/test/template/form_options_helper_test.rb78
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb21
-rw-r--r--actionpack/test/template/javascript_helper_test.rb34
-rw-r--r--actionpack/test/template/number_helper_test.rb2
-rw-r--r--actionpack/test/template/render_test.rb82
-rw-r--r--actionpack/test/template/tag_helper_test.rb13
-rw-r--r--actionpack/test/template/template_test.rb7
-rw-r--r--actionpack/test/template/test_test.rb2
-rw-r--r--actionpack/test/template/testing/fixture_resolver_test.rb4
-rw-r--r--actionpack/test/template/testing/null_resolver_test.rb4
-rw-r--r--actionpack/test/template/text_helper_test.rb106
-rw-r--r--actionpack/test/template/translation_helper_test.rb25
-rw-r--r--actionpack/test/template/url_helper_test.rb44
-rw-r--r--actionpack/test/ts_isolated.rb3
-rw-r--r--activemodel/CHANGELOG.md2
-rw-r--r--activemodel/README.rdoc25
-rw-r--r--[-rwxr-xr-x]activemodel/Rakefile0
-rw-r--r--activemodel/lib/active_model.rb7
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb32
-rw-r--r--activemodel/lib/active_model/configuration.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb7
-rw-r--r--activemodel/lib/active_model/errors.rb20
-rw-r--r--activemodel/lib/active_model/locale/en.yml2
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb2
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb31
-rw-r--r--activemodel/lib/active_model/model.rb16
-rw-r--r--activemodel/lib/active_model/naming.rb3
-rw-r--r--activemodel/lib/active_model/observing.rb68
-rw-r--r--activemodel/lib/active_model/secure_password.rb26
-rw-r--r--activemodel/lib/active_model/serialization.rb23
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb4
-rw-r--r--activemodel/lib/active_model/translation.rb17
-rw-r--r--activemodel/lib/active_model/validations.rb23
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb4
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb3
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb7
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb38
-rw-r--r--activemodel/lib/active_model/validations/format.rb52
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb26
-rw-r--r--activemodel/lib/active_model/validations/length.rb43
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb50
-rw-r--r--activemodel/lib/active_model/validations/presence.rb28
-rw-r--r--activemodel/lib/active_model/validations/with.rb16
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb19
-rw-r--r--activemodel/test/cases/dirty_test.rb2
-rw-r--r--activemodel/test/cases/helper.rb3
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb8
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb2
-rw-r--r--activemodel/test/cases/observing_test.rb69
-rw-r--r--activemodel/test/cases/secure_password_test.rb36
-rw-r--r--activemodel/test/cases/serialization_test.rb18
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb17
-rw-r--r--activemodel/test/cases/translation_test.rb10
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb21
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb17
-rw-r--r--activemodel/test/cases/validations/validates_test.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb15
-rw-r--r--activemodel/test/models/administrator.rb3
-rw-r--r--activemodel/test/models/user.rb3
-rw-r--r--activemodel/test/models/visitor.rb7
-rw-r--r--activerecord/CHANGELOG.md137
-rw-r--r--[-rwxr-xr-x]activerecord/Rakefile1
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record.rb8
-rw-r--r--activerecord/lib/active_record/aggregations.rb14
-rw-r--r--activerecord/lib/active_record/associations.rb20
-rw-r--r--activerecord/lib/active_record/associations/association.rb13
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb15
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb41
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb62
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb147
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb576
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb563
-rw-r--r--activerecord/lib/active_record/core.rb41
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb108
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb169
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb30
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb163
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb2
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb8
-rw-r--r--activerecord/lib/active_record/null_relation.rb53
-rw-r--r--activerecord/lib/active_record/persistence.rb13
-rw-r--r--activerecord/lib/active_record/querying.rb4
-rw-r--r--activerecord/lib/active_record/railtie.rb6
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb111
-rw-r--r--activerecord/lib/active_record/relation/batches.rb9
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb183
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb242
-rw-r--r--activerecord/lib/active_record/relation/merger.rb122
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb312
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb151
-rw-r--r--activerecord/lib/active_record/result.rb1
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema.rb17
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/scoping.rb121
-rw-r--r--activerecord/lib/active_record/scoping/default.rb11
-rw-r--r--activerecord/lib/active_record/scoping/named.rb25
-rw-r--r--activerecord/lib/active_record/session_store.rb17
-rw-r--r--activerecord/lib/active_record/store.rb18
-rw-r--r--activerecord/lib/active_record/transactions.rb18
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb1
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb9
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb30
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb16
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb31
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb12
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb2
-rw-r--r--activerecord/test/cases/ar_schema_test.rb7
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb42
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb28
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb5
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb14
-rw-r--r--activerecord/test/cases/associations/eager_test.rb396
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb86
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb328
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb26
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb6
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb54
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb99
-rw-r--r--activerecord/test/cases/associations_test.rb8
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb14
-rw-r--r--activerecord/test/cases/autosave_association_test.rb38
-rw-r--r--activerecord/test/cases/base_test.rb194
-rw-r--r--activerecord/test/cases/batches_test.rb16
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb17
-rw-r--r--activerecord/test/cases/calculations_test.rb162
-rw-r--r--activerecord/test/cases/column_test.rb52
-rw-r--r--activerecord/test/cases/connection_adapters/quoting_test.rb13
-rw-r--r--activerecord/test/cases/connection_pool_test.rb24
-rw-r--r--activerecord/test/cases/custom_locking_test.rb2
-rw-r--r--activerecord/test/cases/deprecated_dynamic_methods_test.rb607
-rw-r--r--activerecord/test/cases/deprecated_finder_test.rb30
-rw-r--r--activerecord/test/cases/dirty_test.rb4
-rw-r--r--activerecord/test/cases/dynamic_finder_match_test.rb106
-rw-r--r--activerecord/test/cases/explain_test.rb4
-rw-r--r--activerecord/test/cases/finder_test.rb666
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb7
-rw-r--r--activerecord/test/cases/inheritance_test.rb32
-rw-r--r--activerecord/test/cases/invalid_date_test.rb2
-rw-r--r--activerecord/test/cases/lifecycle_test.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb17
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb17
-rw-r--r--activerecord/test/cases/method_scoping_test.rb558
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb4
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb221
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb16
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb99
-rw-r--r--activerecord/test/cases/migration/rename_column_test.rb6
-rw-r--r--activerecord/test/cases/migration_test.rb270
-rw-r--r--activerecord/test/cases/modules_test.rb16
-rw-r--r--activerecord/test/cases/named_scope_test.rb139
-rw-r--r--activerecord/test/cases/persistence_test.rb31
-rw-r--r--activerecord/test/cases/query_cache_test.rb6
-rw-r--r--activerecord/test/cases/readonly_test.rb13
-rw-r--r--activerecord/test/cases/reflection_test.rb2
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb55
-rw-r--r--activerecord/test/cases/relation_test.rb156
-rw-r--r--activerecord/test/cases/relations_test.rb137
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb21
-rw-r--r--activerecord/test/cases/session_store/sql_bypass_test.rb (renamed from activerecord/test/cases/session_store/sql_bypass.rb)5
-rw-r--r--activerecord/test/cases/store_test.rb36
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb68
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb6
-rw-r--r--activerecord/test/cases/validations_test.rb24
-rw-r--r--activerecord/test/models/admin/user.rb2
-rw-r--r--activerecord/test/models/car.rb1
-rw-r--r--activerecord/test/models/comment.rb4
-rw-r--r--activerecord/test/models/company.rb5
-rw-r--r--activerecord/test/models/developer.rb20
-rw-r--r--activerecord/test/models/post.rb15
-rw-r--r--activerecord/test/models/topic.rb20
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb37
-rw-r--r--activerecord/test/schema/schema.rb9
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb2
-rw-r--r--activesupport/CHANGELOG.md58
-rw-r--r--[-rwxr-xr-x]activesupport/Rakefile0
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support.rb15
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb6
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb8
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb52
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb94
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/uniq_by.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb140
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb32
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb54
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb46
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb36
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_dup.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/hash/diff.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb37
-rw-r--r--activesupport/lib/active_support/core_ext/hash/reverse_merge.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/integer/multiple.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb44
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/range/blockless_step.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_range.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb73
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb44
-rw-r--r--activesupport/lib/active_support/core_ext/string/exclude.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb108
-rw-r--r--activesupport/lib/active_support/core_ext/string/inquiry.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/interpolation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb45
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb118
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb29
-rw-r--r--activesupport/lib/active_support/dependencies.rb10
-rw-r--r--activesupport/lib/active_support/deprecation.rb1
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb25
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb5
-rw-r--r--activesupport/lib/active_support/duration.rb1
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb63
-rw-r--r--activesupport/lib/active_support/i18n.rb1
-rw-r--r--activesupport/lib/active_support/inflections.rb10
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb9
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb21
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb2
-rw-r--r--activesupport/lib/active_support/json/decoding.rb6
-rw-r--r--activesupport/lib/active_support/json/encoding.rb23
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb8
-rw-r--r--activesupport/lib/active_support/multibyte.rb3
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb15
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb3
-rw-r--r--activesupport/lib/active_support/notifications.rb37
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb12
-rw-r--r--activesupport/lib/active_support/ordered_options.rb2
-rw-r--r--activesupport/lib/active_support/railtie.rb7
-rw-r--r--activesupport/lib/active_support/rescuable.rb1
-rw-r--r--activesupport/lib/active_support/ruby/shim.rb15
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb2
-rw-r--r--activesupport/lib/active_support/testing/performance.rb7
-rw-r--r--activesupport/lib/active_support/testing/performance/jruby.rb2
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby.rb2
-rw-r--r--activesupport/lib/active_support/time.rb4
-rw-r--r--activesupport/lib/active_support/time/autoload.rb5
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb29
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb22
-rw-r--r--activesupport/test/abstract_unit.rb3
-rw-r--r--activesupport/test/caching_test.rb16
-rw-r--r--activesupport/test/callback_inheritance_test.rb2
-rw-r--r--activesupport/test/callbacks_test.rb6
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/deep_dup_test.rb53
-rw-r--r--activesupport/test/core_ext/duplicable_test.rb4
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb25
-rw-r--r--activesupport/test/core_ext/module_test.rb19
-rw-r--r--activesupport/test/core_ext/object_and_class_ext_test.rb14
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb22
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb37
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb80
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb39
-rw-r--r--activesupport/test/deprecation_test.rb20
-rw-r--r--activesupport/test/file_update_checker_test.rb4
-rw-r--r--activesupport/test/inflector_test_cases.rb33
-rw-r--r--activesupport/test/json/encoding_test.rb15
-rw-r--r--activesupport/test/log_subscriber_test.rb2
-rw-r--r--activesupport/test/logger_test.rb (renamed from activesupport/test/buffered_logger_test.rb)4
-rw-r--r--activesupport/test/multibyte_chars_test.rb9
-rw-r--r--activesupport/test/ordered_options_test.rb8
-rw-r--r--activesupport/test/time_zone_test.rb18
-rw-r--r--activesupport/test/ts_isolated.rb2
-rw-r--r--guides/assets/images/favicon.icobin0 -> 1150 bytes
-rw-r--r--guides/assets/images/getting_started/confirm_dialog.pngbin0 -> 36070 bytes
-rw-r--r--guides/assets/images/getting_started/form_with_errors.pngbin0 -> 20820 bytes
-rw-r--r--guides/assets/images/getting_started/index_action_with_edit_link.pngbin0 -> 15547 bytes
-rw-r--r--guides/assets/images/getting_started/post_with_comments.pngbin0 -> 31630 bytes
-rw-r--r--guides/assets/images/getting_started/show_action_for_posts.pngbin0 -> 6885 bytes
-rw-r--r--guides/assets/images/getting_started/undefined_method_post_path.pngbin0 -> 15254 bytes
-rw-r--r--guides/assets/images/posts_index.pngbin60846 -> 0 bytes
-rw-r--r--guides/assets/stylesheets/main.css6
-rw-r--r--guides/code/getting_started/Gemfile8
-rw-r--r--guides/code/getting_started/README.rdoc4
-rw-r--r--guides/code/getting_started/app/assets/javascripts/comments.js.coffee3
-rw-r--r--guides/code/getting_started/app/assets/javascripts/home.js.coffee3
-rw-r--r--guides/code/getting_started/app/assets/javascripts/posts.js.coffee3
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/comments.css.scss3
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/home.css.scss3
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/posts.css.scss3
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss56
-rw-r--r--guides/code/getting_started/app/controllers/comments_controller.rb5
-rw-r--r--guides/code/getting_started/app/controllers/home_controller.rb2
-rw-r--r--guides/code/getting_started/app/controllers/posts_controller.rb69
-rw-r--r--guides/code/getting_started/app/helpers/home_helper.rb2
-rw-r--r--guides/code/getting_started/app/helpers/posts_helper.rb3
-rw-r--r--guides/code/getting_started/app/helpers/welcome_helper.rb2
-rw-r--r--guides/code/getting_started/app/models/post.rb7
-rw-r--r--guides/code/getting_started/app/models/tag.rb3
-rw-r--r--guides/code/getting_started/app/views/comments/_comment.html.erb8
-rw-r--r--guides/code/getting_started/app/views/comments/_form.html.erb12
-rw-r--r--guides/code/getting_started/app/views/home/index.html.erb2
-rw-r--r--guides/code/getting_started/app/views/posts/_form.html.erb51
-rw-r--r--guides/code/getting_started/app/views/posts/edit.html.erb3
-rw-r--r--guides/code/getting_started/app/views/posts/index.html.erb18
-rw-r--r--guides/code/getting_started/app/views/posts/new.html.erb2
-rw-r--r--guides/code/getting_started/app/views/posts/show.html.erb29
-rw-r--r--guides/code/getting_started/app/views/tags/_form.html.erb12
-rw-r--r--guides/code/getting_started/app/views/welcome/index.html.erb2
-rw-r--r--guides/code/getting_started/config/boot.rb2
-rw-r--r--guides/code/getting_started/config/environments/production.rb2
-rw-r--r--guides/code/getting_started/config/routes.rb7
-rw-r--r--guides/code/getting_started/db/migrate/20110901013701_create_tags.rb11
-rw-r--r--guides/code/getting_started/db/migrate/20120420083127_create_posts.rb (renamed from guides/code/getting_started/db/migrate/20110901012504_create_posts.rb)3
-rw-r--r--guides/code/getting_started/db/schema.rb17
-rw-r--r--guides/code/getting_started/public/robots.txt2
-rw-r--r--guides/code/getting_started/test/fixtures/posts.yml6
-rw-r--r--guides/code/getting_started/test/fixtures/tags.yml9
-rw-r--r--guides/code/getting_started/test/functional/home_controller_test.rb2
-rw-r--r--guides/code/getting_started/test/performance/browsing_test.rb2
-rw-r--r--guides/code/getting_started/vendor/assets/stylesheets/.gitkeep0
-rw-r--r--guides/rails_guides.rb3
-rw-r--r--guides/rails_guides/textile_extensions.rb6
-rw-r--r--guides/source/3_2_release_notes.textile2
-rw-r--r--guides/source/action_controller_overview.textile9
-rw-r--r--guides/source/action_mailer_basics.textile2
-rw-r--r--guides/source/action_view_overview.textile28
-rw-r--r--guides/source/active_model_basics.textile5
-rw-r--r--guides/source/active_record_querying.textile117
-rw-r--r--guides/source/active_record_validations_callbacks.textile2
-rw-r--r--guides/source/active_support_core_extensions.textile270
-rw-r--r--guides/source/ajax_on_rails.textile4
-rw-r--r--guides/source/asset_pipeline.textile57
-rw-r--r--guides/source/caching_with_rails.textile40
-rw-r--r--guides/source/command_line.textile18
-rw-r--r--guides/source/configuring.textile62
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile15
-rw-r--r--guides/source/debugging_rails_applications.textile41
-rw-r--r--guides/source/engines.textile83
-rw-r--r--guides/source/form_helpers.textile12
-rw-r--r--guides/source/generators.textile21
-rw-r--r--guides/source/getting_started.textile1374
-rw-r--r--guides/source/i18n.textile32
-rw-r--r--guides/source/index.html.erb2
-rw-r--r--guides/source/initialization.textile2
-rw-r--r--guides/source/layout.html.erb2
-rw-r--r--guides/source/layouts_and_rendering.textile104
-rw-r--r--guides/source/migrations.textile46
-rw-r--r--guides/source/plugins.textile10
-rw-r--r--guides/source/rails_on_rack.textile123
-rw-r--r--guides/source/routing.textile103
-rw-r--r--guides/source/security.textile7
-rw-r--r--guides/source/testing.textile2
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile4
-rw-r--r--guides/w3c_validator.rb3
-rw-r--r--load_paths.rb1
-rw-r--r--railties/CHANGELOG.md8
-rw-r--r--[-rwxr-xr-x]railties/Rakefile13
-rw-r--r--railties/lib/rails.rb37
-rw-r--r--railties/lib/rails/application.rb12
-rw-r--r--railties/lib/rails/application/configuration.rb11
-rw-r--r--railties/lib/rails/application/finisher.rb9
-rw-r--r--railties/lib/rails/application/route_inspector.rb4
-rw-r--r--railties/lib/rails/application/routes_reloader.rb13
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb4
-rw-r--r--railties/lib/rails/commands.rb13
-rw-r--r--railties/lib/rails/commands/application.rb1
-rw-r--r--railties/lib/rails/commands/console.rb6
-rw-r--r--railties/lib/rails/commands/dbconsole.rb88
-rw-r--r--railties/lib/rails/commands/plugin_new.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb1
-rw-r--r--railties/lib/rails/commands/server.rb2
-rw-r--r--railties/lib/rails/configuration.rb4
-rw-r--r--railties/lib/rails/engine.rb40
-rw-r--r--railties/lib/rails/engine/commands.rb4
-rw-r--r--railties/lib/rails/engine/configuration.rb5
-rw-r--r--railties/lib/rails/generators.rb6
-rw-r--r--railties/lib/rails/generators/actions.rb53
-rw-r--r--railties/lib/rails/generators/app_base.rb33
-rw-r--r--railties/lib/rails/generators/base.rb34
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb36
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb17
-rw-r--r--railties/lib/rails/generators/migration.rb4
-rw-r--r--railties/lib/rails/generators/named_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README4
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/app/templates/Rakefile1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/boot.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml3
-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.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/422.html2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/index.html4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/robots.txt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile2
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/plugin_new/templates/Rakefile1
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb1
-rw-r--r--railties/lib/rails/generators/rails/resource/resource_generator.rb11
-rw-r--r--railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb13
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb3
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb2
-rw-r--r--railties/lib/rails/generators/test_case.rb13
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb1
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb3
-rw-r--r--railties/lib/rails/paths.rb16
-rw-r--r--railties/lib/rails/queueing.rb73
-rw-r--r--railties/lib/rails/rack/debugger.rb4
-rw-r--r--railties/lib/rails/railtie.rb4
-rw-r--r--railties/lib/rails/railtie/configuration.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb15
-rw-r--r--railties/lib/rails/tasks/documentation.rake2
-rw-r--r--railties/railties.gemspec6
-rw-r--r--railties/test/abstract_unit.rb2
-rw-r--r--railties/test/application/asset_debugging_test.rb10
-rw-r--r--railties/test/application/assets_test.rb18
-rw-r--r--railties/test/application/configuration_test.rb8
-rw-r--r--railties/test/application/initializers/frameworks_test.rb2
-rw-r--r--railties/test/application/initializers/i18n_test.rb4
-rw-r--r--railties/test/application/loading_test.rb16
-rw-r--r--railties/test/application/middleware/cache_test.rb2
-rw-r--r--railties/test/application/middleware/exceptions_test.rb37
-rw-r--r--railties/test/application/middleware/session_test.rb20
-rw-r--r--railties/test/application/middleware_test.rb7
-rw-r--r--railties/test/application/paths_test.rb2
-rw-r--r--railties/test/application/queue_test.rb145
-rw-r--r--railties/test/application/rake/notes_test.rb82
-rw-r--r--railties/test/application/route_inspect_test.rb85
-rw-r--r--railties/test/application/routing_test.rb103
-rw-r--r--railties/test/application/url_generation_test.rb2
-rw-r--r--railties/test/backtrace_cleaner_test.rb32
-rw-r--r--railties/test/commands/dbconsole_test.rb160
-rw-r--r--railties/test/engine_test.rb24
-rw-r--r--railties/test/generators/app_generator_test.rb70
-rw-r--r--railties/test/generators/migration_generator_test.rb4
-rw-r--r--railties/test/generators/model_generator_test.rb8
-rw-r--r--railties/test/generators/namespaced_generators_test.rb11
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb11
-rw-r--r--railties/test/generators/scaffold_generator_test.rb6
-rw-r--r--railties/test/generators/shared_generator_tests.rb4
-rw-r--r--railties/test/isolation/abstract_unit.rb15
-rw-r--r--railties/test/paths_test.rb2
-rw-r--r--railties/test/queueing/test_queue_test.rb67
-rw-r--r--railties/test/queueing/threaded_consumer_test.rb100
-rw-r--r--railties/test/rails_info_controller_test.rb2
-rw-r--r--railties/test/rails_info_test.rb2
-rw-r--r--railties/test/railties/engine_test.rb74
-rw-r--r--railties/test/railties/mounted_engine_test.rb22
-rwxr-xr-xtools/profile1
717 files changed, 13199 insertions, 9475 deletions
diff --git a/Gemfile b/Gemfile
index acfb1c8e80..ac835936ca 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,34 +3,41 @@ source 'https://rubygems.org'
gemspec
if ENV['AREL']
- gem 'arel', :path => ENV['AREL']
+ gem 'arel', path: ENV['AREL']
else
gem 'arel'
end
-gem 'rack-test', :git => "git://github.com/brynary/rack-test.git"
+gem 'rack-test', github: "brynary/rack-test"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
+gem 'minitest', '~> 3.0.0'
if ENV['JOURNEY']
- gem 'journey', :path => ENV['JOURNEY']
+ gem 'journey', path: ENV['JOURNEY']
else
- gem 'journey', :git => "git://github.com/rails/journey"
+ gem 'journey', github: "rails/journey"
+end
+
+if ENV['AR_DEPRECATED_FINDERS']
+ gem 'active_record_deprecated_finders', path: ENV['AR_DEPRECATED_FINDERS']
+else
+ gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
end
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
-gem 'uglifier', '>= 1.0.3', :require => false
+gem 'uglifier', '>= 1.0.3', require: false
gem 'rake', '>= 0.8.7'
-gem 'mocha', '>= 0.9.8'
+gem 'mocha', '>= 0.11.2'
group :doc do
# The current sdoc cannot generate GitHub links due
# to a bug, but the PR that fixes it has been there
# for some weeks unapplied. As a temporary solution
# this is our own fork with the fix.
- gem 'sdoc', :git => 'git://github.com/fxn/sdoc.git'
+ gem 'sdoc', github: 'fxn/sdoc'
gem 'RedCloth', '~> 4.2'
gem 'w3c_validators'
end
@@ -44,7 +51,7 @@ instance_eval File.read local_gemfile if File.exists? local_gemfile
platforms :mri do
group :test do
- gem 'ruby-prof'
+ gem 'ruby-prof', '~> 0.11.2'
end
end
@@ -54,7 +61,7 @@ platforms :ruby do
gem 'nokogiri', '>= 1.4.5'
# AR
- gem 'sqlite3', '~> 1.3.5'
+ gem 'sqlite3', '~> 1.3.6'
group :db do
gem 'pg', '>= 0.11.0'
@@ -84,9 +91,9 @@ if ENV['ORACLE_ENHANCED_PATH'] || ENV['ORACLE_ENHANCED']
gem 'ruby-oci8', '>= 2.0.4'
end
if ENV['ORACLE_ENHANCED_PATH']
- gem 'activerecord-oracle_enhanced-adapter', :path => ENV['ORACLE_ENHANCED_PATH']
+ gem 'activerecord-oracle_enhanced-adapter', path: ENV['ORACLE_ENHANCED_PATH']
else
- gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git'
+ gem 'activerecord-oracle_enhanced-adapter', github: 'rsim/oracle-enhanced'
end
end
diff --git a/Rakefile b/Rakefile
index 0d218844b2..21eb60bbe1 100755..100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,5 +1,3 @@
-#!/usr/bin/env rake
-
require 'rdoc/task'
require 'sdoc'
require 'net/http'
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index be7b82eecc..a33d6e072b 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,4 +1,4 @@
-## Rails 3.2.3 (unreleased) ##
+## Rails 3.2.3 (March 30, 2012) ##
* Upgrade mail version to 2.4.3 *ML*
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 755717cfba..cf10bfffdb 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -22,12 +22,12 @@ the email.
This can be as simple as:
class Notifier < ActionMailer::Base
- delivers_from 'system@loudthinking.com'
+ default from: 'system@loudthinking.com'
def welcome(recipient)
@recipient = recipient
- mail(:to => recipient,
- :subject => "[Signed up] Welcome #{recipient}")
+ mail(to: recipient,
+ subject: "[Signed up] Welcome #{recipient}")
end
end
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 8f5aeb9603..c1c4171cdf 100755..100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 1045dd58ef..e45a1cd5ff 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -21,9 +21,6 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-actionpack_path = File.expand_path('../../../actionpack/lib', __FILE__)
-$:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(actionpack_path)
-
require 'abstract_controller'
require 'action_view'
require 'action_mailer/version'
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 4af2d0a4a8..4f0cff0612 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -3,6 +3,7 @@ require 'action_mailer/collector'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/module/anonymous'
require 'action_mailer/log_subscriber'
module ActionMailer #:nodoc:
@@ -401,7 +402,7 @@ module ActionMailer #:nodoc:
end
def mailer_name
- @mailer_name ||= name.underscore
+ @mailer_name ||= anonymous? ? "anonymous" : name.underscore
end
attr_writer :mailer_name
alias :controller_path :mailer_name
@@ -486,7 +487,7 @@ module ActionMailer #:nodoc:
self.class.mailer_name
end
- # Allows you to pass random and unusual headers to the new +Mail::Message+ object
+ # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt> object
# which will add them to itself.
#
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
@@ -497,7 +498,7 @@ module ActionMailer #:nodoc:
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
# 'In-Reply-To' => incoming.message_id
#
- # The resulting Mail::Message will have the following in it's header:
+ # The resulting Mail::Message will have the following in its header:
#
# X-Special-Domain-Specific-Header: SecretValue
def headers(args=nil)
@@ -696,7 +697,7 @@ module ActionMailer #:nodoc:
# 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('/', '.')
+ mailer_scope = self.class.mailer_name.tr('/', '.')
I18n.t(:subject, :scope => [mailer_scope, action_name], :default => action_name.humanize)
end
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index d1467fb526..3b38dbccc7 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -65,7 +65,7 @@ module ActionMailer
when NilClass
raise "Delivery method cannot be nil"
when Symbol
- if klass = delivery_methods[method.to_sym]
+ if klass = delivery_methods[method]
mail.delivery_method(klass, send(:"#{method}_settings"))
else
raise "Invalid delivery method #{method.inspect}"
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
index f6ed7cf8ac..edcfb4233d 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
@@ -6,7 +6,7 @@ class <%= class_name %> < ActionMailer::Base
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
- # en.<%= file_path.gsub("/",".") %>.<%= action %>.subject
+ # en.<%= file_path.tr("/",".") %>.<%= action %>.subject
#
def <%= action %>
@greeting = "Hi"
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 3a519253f9..99c44179fd 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -1,16 +1,4 @@
-# Pathname has a warning, so require it first while silencing
-# warnings to shut it up.
-#
-# Also, in 1.9, Bundler creates warnings due to overriding
-# Rubygems methods
-begin
- old, $VERBOSE = $VERBOSE, nil
- require 'pathname'
- require File.expand_path('../../../load_paths', __FILE__)
-ensure
- $VERBOSE = old
-end
-
+require File.expand_path('../../../load_paths', __FILE__)
require 'active_support/core_ext/kernel/reporting'
# These are the normal settings that will be set up by Railties
@@ -20,9 +8,6 @@ silence_warnings do
Encoding.default_external = "UTF-8"
end
-lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
require 'minitest/autorun'
require 'action_mailer'
require 'action_mailer/test_case'
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index aed3ee1874..1d747ed18a 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -610,6 +610,19 @@ class BaseTest < ActiveSupport::TestCase
assert_equal Set.new(["notify"]), FooMailer.action_methods
end
+ test "mailer can be anonymous" do
+ mailer = Class.new(ActionMailer::Base) do
+ def welcome
+ mail
+ end
+ end
+
+ assert_equal "anonymous", mailer.mailer_name
+
+ assert_equal "Welcome", mailer.welcome.subject
+ assert_equal "Anonymous mailer body", mailer.welcome.body.encoded.strip
+ end
+
protected
# Execute the block setting the given values and restoring old values after
diff --git a/actionmailer/test/fixtures/anonymous/welcome.erb b/actionmailer/test/fixtures/anonymous/welcome.erb
new file mode 100644
index 0000000000..8361da62c4
--- /dev/null
+++ b/actionmailer/test/fixtures/anonymous/welcome.erb
@@ -0,0 +1 @@
+Anonymous mailer body
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 7040ae6f8d..68ed86e0d4 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -24,7 +24,7 @@ end
class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
def app
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 0536e83098..2ea1723434 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -57,8 +57,8 @@ class ActionMailerUrlTest < ActionMailer::TestCase
UrlTestMailer.delivery_method = :test
AppRoutes.draw do
- match ':controller(/:action(/:id))'
- match '/welcome' => "foo#bar", :as => "welcome"
+ get ':controller(/:action(/:id))'
+ get '/welcome' => "foo#bar", :as => "welcome"
end
expected = new_mail
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index a4f5116a5d..e625bbe7af 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,59 @@
## Rails 4.0.0 (unreleased) ##
+* Add `divider` option to `grouped_options_for_select` to generate a separator
+ `optgroup` automatically, and deprecate `prompt` as third argument, in favor
+ of using an options hash. *Nicholas Greenfield*
+
+* Add `time_field` and `time_field_tag` helpers which render an `input[type="time"]` tag. *Alex Soulim*
+
+* Removed old text_helper apis for highlight, excerpt and word_wrap *Jeremy Walker*
+
+* Templates without a handler extension now raises a deprecation warning but still
+ defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik*
+
+* Remove `:disable_with` in favor of `'data-disable-with'` option from `submit_tag`, `button_tag` and `button_to` helpers.
+
+ *Carlos Galdino + Rafael Mendonça França*
+
+* Remove `:mouseover` option from `image_tag` helper. *Rafael Mendonça França*
+
+* The `select` method (select tag) forces :include_blank if `required` is true and
+ `display size` is one and `multiple` is not true. *Angelo Capilleri*
+
+* Copy literal route constraints to defaults so that url generation know about them.
+ The copied constraints are `:protocol`, `:subdomain`, `:domain`, `:host` and `:port`.
+
+ *Andrew White*
+
+* `respond_to` and `respond_with` now raise ActionController::UnknownFormat instead
+ of directly returning head 406. The exception is rescued and converted to 406
+ in the exception handling middleware. *Steven Soroka*
+
+* Allows `assert_redirected_to` to match against a regular expression. *Andy Lindeman*
+
+* Add backtrace to development routing error page. *Richard Schneeman*
+
+* Replace `include_seconds` boolean argument with `:include_seconds => true` option
+ in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko*
+
+* Remove `button_to_function` and `link_to_function` helpers. *Rafael Mendonça França*
+
+* Make current object and counter (when it applies) variables accessible when
+ rendering templates with :object / :collection. *Carlos Antonio da Silva*
+
+* JSONP now uses mimetype application/javascript instead of application/json. *omjokine*
+
+* Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki*
+
+* Session arguments passed to `process` calls in functional tests are now merged into
+ the existing session, whereas previously they would replace the existing session.
+ This change may break some existing tests if they are asserting the exact contents of
+ the session but should not break existing tests that only assert individual keys.
+
+ *Andrew White*
+
+* Add `index` method to FormBuilder class. *Jorge Bejar*
+
* Remove the leading \n added by textarea on assert_select. *Santiago Pastorino*
* Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms`
@@ -133,7 +187,7 @@
HTML5 `mark` element. *Brian Cardarella*
-## Rails 3.2.3 (unreleased) ##
+## Rails 3.2.3 (March 30, 2012) ##
* Add `config.action_view.embed_authenticity_token_in_remote_forms` (defaults to true) which allows to set if authenticity token will be included by default in remote forms. If you change it to false, you can still force authenticity token by passing `:authenticity_token => true` in form options *Piotr Sarnacki*
@@ -5746,7 +5800,7 @@
== Rendering a collection of partials
The example of partial use describes a familar pattern where a template needs
- to iterate over a array and render a sub template for each of the elements.
+ 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 of as the elements contained within. So the
three-lined example in "Using partials" can be rewritten with a single line:
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index bb1e704767..50e3bb0d48 100755..100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
@@ -24,6 +23,7 @@ end
namespace :test do
Rake::TestTask.new(:isolated) do |t|
+ t.libs << 'test'
t.pattern = 'test/ts_isolated.rb'
end
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index cc5878c88e..b95ea5f0b2 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -1,9 +1,5 @@
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
require 'action_pack'
require 'active_support/concern'
-require 'active_support/ruby/shim'
require 'active_support/dependencies/autoload'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/attr_internal'
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index dd5f9a1942..822254b1a4 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -1,5 +1,5 @@
module AbstractController
- module AssetPaths
+ module AssetPaths #:nodoc:
extend ActiveSupport::Concern
included do
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index b068846a13..97a9eec144 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -5,8 +5,11 @@ require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
module AbstractController
- class Error < StandardError; end
- class ActionNotFound < StandardError; end
+ class Error < StandardError #:nodoc:
+ end
+
+ class ActionNotFound < StandardError #:nodoc:
+ end
# <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
# using it directly, and subclasses (like ActionController::Base) are
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index c0fa28cae9..5705ab590c 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -14,7 +14,7 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
def process_action(*args)
- run_callbacks(:process_action, action_name) do
+ run_callbacks(:process_action) do
super
end
end
@@ -163,11 +163,11 @@ module AbstractController
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
# Append a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
+ def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
+ end # end
+ end # end
# Prepend a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
@@ -179,11 +179,11 @@ module AbstractController
# Skip a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def skip_#{filter}_filter(*names) # def skip_before_filter(*names)
- _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
- skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
- end # end
- end # end
+ def skip_#{filter}_filter(*names) # def skip_before_filter(*names)
+ _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
+ skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
+ end # end
+ end # end
# *_filter is the same as append_*_filter
alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 77cc4c07d9..4e0672d590 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -49,9 +49,9 @@ module AbstractController
meths.each do |meth|
_helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def #{meth}(*args, &blk)
- controller.send(%(#{meth}), *args, &blk)
- end
+ def #{meth}(*args, &blk) # def current_user(*args, &blk)
+ controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk)
+ end # end
ruby_eval
end
end
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index a4e31cd2e5..c31ea6c5b5 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -1,7 +1,7 @@
require "active_support/benchmarkable"
module AbstractController
- module Logger
+ module Logger #:nodoc:
extend ActiveSupport::Concern
included do
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 59ec197347..80901b8bf3 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -133,6 +133,8 @@ module ActionController #:nodoc:
end
def filter(controller)
+ cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
+
path_options = if @cache_path.respond_to?(:call)
controller.instance_exec(controller, &@cache_path)
else
@@ -144,13 +146,13 @@ module ActionController #:nodoc:
body = controller.read_fragment(cache_path.path, @store_options)
unless body
- controller.action_has_layout = false unless @cache_layout
+ controller.action_has_layout = false unless cache_layout
yield
controller.action_has_layout = true
body = controller._save_fragment(cache_path.path, @store_options)
end
- body = controller.render_to_string(:text => body, :layout => true) unless @cache_layout
+ body = controller.render_to_string(:text => body, :layout => true) unless cache_layout
controller.response_body = body
controller.content_type = Mime[cache_path.extension || :html]
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 307594d54a..dd4eddbe9a 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -60,7 +60,8 @@ module ActionController #:nodoc:
end
module ClassMethods
- # Expires the page that was cached with the +path+ as a key. Example:
+ # Expires the page that was cached with the +path+ as a key.
+ #
# expire_page "/lists/show"
def expire_page(path)
return unless perform_caching
@@ -72,7 +73,8 @@ module ActionController #:nodoc:
end
end
- # Manually cache the +content+ in the key determined by +path+. Example:
+ # Manually cache the +content+ in the key determined by +path+.
+ #
# cache_page "I'm the cached content", "/lists/show"
def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
return unless perform_caching
@@ -93,8 +95,6 @@ module ActionController #:nodoc:
#
# You can also pass a :gzip option to override the class configuration one.
#
- # Usage:
- #
# # cache the index action
# caches_page :index
#
@@ -142,7 +142,8 @@ module ActionController #:nodoc:
end
end
- # Expires the page that was cached with the +options+ as a key. Example:
+ # Expires the page that was cached with the +options+ as a key.
+ #
# expire_page :controller => "lists", :action => "show"
def expire_page(options = {})
return unless self.class.perform_caching
@@ -161,7 +162,8 @@ module ActionController #:nodoc:
end
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used.
- # If no options are provided, the url of the current request being handled is used. Example:
+ # If no options are provided, the url of the current request being handled is used.
+ #
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
return unless self.class.perform_caching && caching_allowed?
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index bb176ca3f9..cc1fa23935 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -93,7 +93,7 @@ module ActionController #:nodoc:
end
def method_missing(method, *arguments, &block)
- super unless @controller
+ return super unless @controller
@controller.__send__(method, *arguments, &block)
end
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 4c76f4c43b..11aa393bf9 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -20,7 +20,7 @@ module ActionController
status = payload[:status]
if status.nil? && payload[:exception].present?
- status = Rack::Utils.status_code(ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code)
+ status = ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 5b25a0d303..2193dde667 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -108,7 +108,6 @@ module ActionController
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a <tt>private</tt> instruction, so that
# intermediate caches must not cache the response.
#
- # Examples:
# expires_in 20.minutes
# expires_in 3.hours, :public => true
# expires_in 3.hours, :public => true, :must_revalidate => true
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 30ddf6c16e..379ff97048 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -8,15 +8,13 @@ module ActionController #:nodoc:
include ActionController::Rendering
- DEFAULT_SEND_FILE_OPTIONS = {
- :type => 'application/octet-stream'.freeze,
- :disposition => 'attachment'.freeze,
- }.freeze
+ DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
+ DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
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.
+ # +config.action_dispatch.x_sendfile_header+.
# 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
@@ -74,7 +72,27 @@ module ActionController #:nodoc:
self.status = options[:status] || 200
self.content_type = options[:content_type] if options.key?(:content_type)
- self.response_body = File.open(path, "rb")
+ self.response_body = FileBody.new(path)
+ end
+
+ # Avoid having to pass an open file handle as the response body.
+ # Rack::Sendfile will usually intercepts the response and just uses
+ # the path directly, so no reason to open the file.
+ class FileBody #:nodoc:
+ attr_reader :to_path
+
+ def initialize(path)
+ @to_path = path
+ end
+
+ # Stream the file's contents if Rack::Sendfile isn't present.
+ def each
+ File.open(to_path, 'rb') do |file|
+ while chunk = file.read(16384)
+ yield chunk
+ end
+ end
+ end
end
# Sends the given binary data to the browser. This method is similar to
@@ -107,7 +125,7 @@ module ActionController #:nodoc:
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
- send_file_headers! options.dup
+ send_file_headers! options
render options.slice(:status, :content_type).merge(:text => data)
end
@@ -115,15 +133,8 @@ module ActionController #:nodoc:
def send_file_headers!(options)
type_provided = options.has_key?(:type)
- 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]
+ content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
+ raise ArgumentError, ":type option required" if content_type.nil?
if content_type.is_a?(Symbol)
extension = Mime[content_type]
@@ -132,15 +143,18 @@ module ActionController #:nodoc:
else
if !type_provided && options[:filename]
# If type wasn't provided, try guessing from file extension.
- content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
end
self.content_type = content_type
end
- headers.merge!(
- 'Content-Disposition' => disposition,
- 'Content-Transfer-Encoding' => 'binary'
- )
+ disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
+ unless disposition.nil?
+ disposition += %(; filename="#{options[:filename]}") if options[:filename]
+ headers['Content-Disposition'] = disposition
+ end
+
+ headers['Content-Transfer-Encoding'] = 'binary'
response.sending_file = true
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 505b2ac609..90648c37ad 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -14,8 +14,6 @@ module ActionController
end
class MethodNotAllowed < ActionControllerError #:nodoc:
- attr_reader :allowed_methods
-
def initialize(*allowed_methods)
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
end
@@ -40,4 +38,7 @@ module ActionController
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
+
+ class UnknownFormat < ActionControllerError #:nodoc:
+ end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index a618533d09..2fcd933d32 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -20,6 +20,7 @@ module ActionController
options, status = status, nil if status.is_a?(Hash)
status ||= options.delete(:status) || :ok
location = options.delete(:location)
+ content_type = options.delete(:content_type)
options.each do |key, value|
headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
@@ -27,8 +28,28 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- self.content_type = Mime[formats.first] if formats
+
+ if include_content_headers?(self.status)
+ self.content_type = content_type || (Mime[formats.first] if formats)
+ else
+ headers.delete('Content-Type')
+ headers.delete('Content-Length')
+ end
+
self.response_body = " "
end
+
+ private
+ # :nodoc:
+ def include_content_headers?(status)
+ case status
+ when 100..199
+ false
+ when 204, 205, 304
+ false
+ else
+ true
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 1a4bca12d2..86d061e3b7 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -16,7 +16,6 @@ module ActionController
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
# controller which inherits from it.
#
- # ==== Examples
# The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if
# a \Time object is blank:
#
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 44d2f740e6..57bb0e2a32 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -2,8 +2,9 @@ require 'base64'
require 'active_support/core_ext/object/blank'
module ActionController
+ # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
module HttpAuthentication
- # Makes it dead easy to do HTTP \Basic and \Digest authentication.
+ # Makes it dead easy to do HTTP \Basic authentication.
#
# === Simple \Basic example
#
@@ -60,47 +61,6 @@ module ActionController
#
# assert_equal 200, status
# end
- #
- # === Simple \Digest example
- #
- # require 'digest/md5'
- # class PostsController < ApplicationController
- # REALM = "SuperSecret"
- # USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
- #
- # 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_digest(REALM) do |username|
- # USERS[username]
- # end
- # end
- # end
- #
- # === Notes
- #
- # The +authenticate_or_request_with_http_digest+ block must return the user's password
- # or the ha1 digest hash so the framework can appropriately hash to check the user's
- # credentials. Returning +nil+ will cause authentication to fail.
- #
- # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
- # the password file or database is compromised, the attacker would be able to use the ha1 hash to
- # authenticate as the user at this +realm+, but would not have the user's password to try using at
- # other sites.
- #
- # In rare instances, web servers or front proxies strip authorization headers before
- # they reach your application. You can debug this situation by logging all environment
- # variables, and check for HTTP_AUTHORIZATION, amongst others.
module Basic
extend self
@@ -155,6 +115,48 @@ module ActionController
end
end
+ # Makes it dead easy to do HTTP \Digest authentication.
+ #
+ # === Simple \Digest example
+ #
+ # require 'digest/md5'
+ # class PostsController < ApplicationController
+ # REALM = "SuperSecret"
+ # USERS = {"dhh" => "secret", #plain text password
+ # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
+ #
+ # 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_digest(REALM) do |username|
+ # USERS[username]
+ # end
+ # end
+ # end
+ #
+ # === Notes
+ #
+ # The +authenticate_or_request_with_http_digest+ block must return the user's password
+ # or the ha1 digest hash so the framework can appropriately hash to check the user's
+ # credentials. Returning +nil+ will cause authentication to fail.
+ #
+ # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
+ # the password file or database is compromised, the attacker would be able to use the ha1 hash to
+ # authenticate as the user at this +realm+, but would not have the user's password to try using at
+ # other sites.
+ #
+ # In rare instances, web servers or front proxies strip authorization headers before
+ # they reach your application. You can debug this situation by logging all environment
+ # variables, and check for HTTP_AUTHORIZATION, amongst others.
module Digest
extend self
@@ -229,7 +231,7 @@ module ActionController
def decode_credentials(header)
Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair|
key, value = pair.split('=', 2)
- [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')]
+ [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')]
end]
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index fbb5d01e86..0b800c3c62 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -16,8 +16,6 @@ module ActionController #:nodoc:
# Defines mime types that are rendered by default when invoking
# <tt>respond_with</tt>.
#
- # Examples:
- #
# respond_to :html, :xml, :json
#
# Specifies that all actions in the controller respond to requests
@@ -74,7 +72,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
#
@@ -185,7 +183,6 @@ module ActionController #:nodoc:
# end
#
# Be sure to check respond_with and respond_to documentation for more examples.
- #
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
@@ -323,7 +320,6 @@ module ActionController #:nodoc:
# a successful html +post+ request.
# 2. <tt>:action</tt> - overwrites the default render action used after an
# unsuccessful html +post+ request.
- #
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?
@@ -339,7 +335,6 @@ module ActionController #:nodoc:
# Collect mimes declared in the class method respond_to valid for the
# current action.
- #
def collect_mimes_from_class_level #:nodoc:
action = action_name.to_s
@@ -362,7 +357,6 @@ module ActionController #:nodoc:
#
# Sends :not_acceptable to the client and returns nil if no suitable format
# is available.
- #
def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
collector = Collector.new(mimes)
@@ -375,8 +369,7 @@ module ActionController #:nodoc:
lookup_context.rendered_format = lookup_context.formats.first
collector
else
- head :not_acceptable
- nil
+ raise ActionController::UnknownFormat
end
end
@@ -389,7 +382,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
#
# In this usage, the argument passed to the block (+format+ above) is an
@@ -402,7 +395,6 @@ module ActionController #:nodoc:
# A subsequent call to #negotiate_format(request) will enable the Collector
# to determine which specific mime-type it should respond with for the current
# request, with this response then being accessible by calling #response.
- #
class Collector
include AbstractController::Collector
attr_accessor :order, :format
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index fa760f2658..1f52c164de 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -48,7 +48,7 @@ module ActionController
# method attribute_names.
#
# If you're going to pass the parameters to an +ActiveModel+ object (such as
- # +User.new(params[:user])+), you might consider passing the model class to
+ # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
# the method instead. The +ParamsWrapper+ will actually try to determine the
# list of attribute names from the model and only wrap those attributes:
#
@@ -66,7 +66,7 @@ module ActionController
# class Admin::UsersController < ApplicationController
# end
#
- # will try to check if +Admin::User+ or +User+ model exists, and use it to
+ # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
# determine the wrapper key respectively. If both models don't exist,
# it will then fallback to use +user+ as the key.
module ParamsWrapper
@@ -166,8 +166,9 @@ module ActionController
unless options[:include] || options[:exclude]
model ||= _default_wrap_model
- if model.respond_to?(:accessible_attributes) && model.accessible_attributes.present?
- options[:include] = model.accessible_attributes.to_a
+ role = options.fetch(:as, :default)
+ if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present?
+ options[:include] = model.accessible_attributes(role).to_a
elsif model.respond_to?(:attribute_names) && model.attribute_names.present?
options[:include] = model.attribute_names
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 3ffb7ef426..ee0e69d87c 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -24,7 +24,6 @@ module ActionController
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
- # Examples:
# redirect_to :action => "show", :id => 5
# redirect_to post
# redirect_to "http://www.rubyonrails.org"
@@ -35,7 +34,6 @@ module ActionController
#
# The redirection happens as a "302 Moved" header unless otherwise specified.
#
- # Examples:
# redirect_to post_url(@post), :status => :found
# redirect_to :action=>'atom', :status => :moved_permanently
# redirect_to post_url(@post), :status => 301
@@ -45,10 +43,18 @@ module ActionController
# integer, or a symbol representing the downcased, underscored and symbolized description.
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
#
+ # If you are using XHR requests other than GET or POST and redirecting after the
+ # request then some browsers will follow the redirect using the original request
+ # method. This may lead to undesirable behavior such as a double DELETE. To work
+ # around this you can return a <tt>303 See Other</tt> status code which will be
+ # followed using a GET request.
+ #
+ # redirect_to posts_url, :status => :see_other
+ # redirect_to :action => 'index', :status => 303
+ #
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
- # Examples:
# redirect_to post_url(@post), :alert => "Watch it, mister!"
# redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road"
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
@@ -59,6 +65,7 @@ module ActionController
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
+ logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
@@ -93,7 +100,7 @@ module ActionController
_compute_redirect_to_location options.call
else
url_for(options)
- end.gsub(/[\0\r\n]/, '')
+ end.delete("\0\r\n")
end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 6e9ce450ac..1927c8bdc7 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -49,7 +49,6 @@ module ActionController
# is the value paired with its key and the second is the remaining
# hash of options passed to +render+.
#
- # === Example
# Create a csv renderer:
#
# ActionController::Renderers.add :csv do |obj, options|
@@ -91,9 +90,14 @@ module ActionController
add :json do |json, options|
json = json.to_json(options) unless json.kind_of?(String)
- json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
- self.content_type ||= Mime::JSON
- json
+
+ if options[:callback].present?
+ self.content_type ||= Mime::JS
+ "#{options[:callback]}(#{json})"
+ else
+ self.content_type ||= Mime::JSON
+ json
+ end
end
add :js do |js, options|
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 3081c14c09..95b0e99ed5 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -17,7 +17,6 @@ module ActionController #:nodoc:
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
# which checks the token and resets the session if it doesn't match what was expected.
# A call to this method is generated for new \Rails applications by default.
- # You can customize the error message by editing public/422.html.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
@@ -52,8 +51,6 @@ module ActionController #:nodoc:
module ClassMethods
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
#
- # Example:
- #
# class FooController < ApplicationController
# protect_from_forgery :except => :index
#
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 1e8990495c..83407846dc 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -63,7 +63,7 @@ module ActionController #:nodoc:
#
# def create
# @project = Project.find(params[:project_id])
- # @task = @project.comments.build(params[:task])
+ # @task = @project.tasks.build(params[:task])
# flash[:notice] = 'Task was successfully created.' if @task.save
# respond_with(@project, @task)
# end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index e9783e6919..eeb37db2e7 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -139,17 +139,17 @@ module ActionController #:nodoc:
# session or flash after the template starts rendering will not propagate
# to the client.
#
- # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
+ # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt>
# will be raised, showing those objects are closed for modification.
#
# == Middlewares
#
# Middlewares that need to manipulate the body won't work with streaming.
# You should disable those middlewares whenever streaming in development
- # or production. For instance, +Rack::Bug+ won't work when streaming as it
+ # or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it
# needs to inject contents in the HTML body.
#
- # Also +Rack::Cache+ won't work with streaming as it does not support
+ # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support
# streaming bodies yet. Whenever streaming Cache-Control is automatically
# set to "no-cache".
#
@@ -162,7 +162,7 @@ module ActionController #:nodoc:
# Currently, when an exception happens in development or production, Rails
# will automatically stream to the client:
#
- # "><script type="text/javascript">window.location = "/500.html"</script></html>
+ # "><script>window.location = "/500.html"</script></html>
#
# The first two characters (">) are required in case the exception happens
# while rendering attributes for a given tag. You can check the real cause
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 8e7b56dbcc..e28c05cc2d 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -6,8 +6,6 @@ module ActionController
# url options like the +host+. In order to do so, this module requires the host class
# to implement +env+ and +request+, which need to be a Rack-compatible.
#
- # Example:
- #
# class RootUrl
# include ActionController::UrlFor
# include Rails.application.routes.url_helpers
@@ -19,7 +17,6 @@ module ActionController
# @url = root_path # named route from the application.
# end
# end
- #
module UrlFor
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 18dda978b3..16a5decc62 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/module'
module ActionController
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
# pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
- # a higher logical level. Example:
+ # a higher logical level.
#
# # routes
# resources :posts
@@ -30,7 +30,7 @@ module ActionController
JOIN = '_'.freeze
NEW = 'new'.freeze
- # The DOM class convention is to use the singular form of an object or class. Examples:
+ # The DOM class convention is to use the singular form of an object or class.
#
# dom_class(post) # => "post"
# dom_class(Person) # => "person"
@@ -45,7 +45,7 @@ module ActionController
end
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
- # If no id is found, prefix with "new_" instead. Examples:
+ # If no id is found, prefix with "new_" instead.
#
# dom_id(Post.find(45)) # => "post_45"
# dom_id(Post.new) # => "new_post"
@@ -53,6 +53,7 @@ module ActionController
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
+ # dom_id(Post.new, :custom) # => "custom_post"
def dom_id(record, prefix = nil)
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
@@ -74,12 +75,7 @@ module ActionController
def record_key_for_dom_id(record)
record = record.to_model if record.respond_to?(:to_model)
key = record.to_key
- key ? sanitize_dom_id(key.join('_')) : key
- end
-
- # Replaces characters that are invalid in HTML DOM ids with valid ones.
- def sanitize_dom_id(candidate_id)
- candidate_id # TODO implement conversion to valid DOM id values
+ key ? key.join('_') : key
end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 9bd2e622ad..028a8d3fba 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -20,20 +20,25 @@ module ActionController
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
path = payload[:layout]
- @layouts[path] += 1
+ if path
+ @layouts[path] += 1
+ if path =~ /^layouts\/(.*)/
+ @layouts[$1] += 1
+ end
+ end
end
ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
+
if partial
@partials[path] += 1
@partials[path.split("/").last] += 1
- @templates[path] += 1
- else
- @templates[path] += 1
end
+
+ @templates[path] += 1
end
end
@@ -51,14 +56,21 @@ module ActionController
# Asserts that the request was rendered with the appropriate template file or partials.
#
- # ==== Examples
- #
# # assert that the "new" view template was rendered
# assert_template "new"
#
# # assert that the exact template "admin/posts/new" was rendered
# assert_template %r{\Aadmin/posts/new\Z}
#
+ # # assert that the layout 'admin' was rendered
+ # assert_template :layout => 'admin'
+ # assert_template :layout => 'layouts/admin'
+ # assert_template :layout => :admin
+ #
+ # # assert that no layout was rendered
+ # assert_template :layout => nil
+ # assert_template :layout => false
+ #
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
@@ -70,7 +82,6 @@ module ActionController
#
# # assert that the "_customer" partial was rendered with a specific object
# assert_template :partial => '_customer', :locals => { :customer => @customer }
- #
def assert_template(options = {}, message = nil)
# Force body to be read in case the
# template is being streamed
@@ -82,24 +93,25 @@ module ActionController
rendered = @templates
msg = message || sprintf("expecting <%s> but rendering with <%s>",
options.inspect, rendered.keys)
- assert_block(msg) do
+ matches_template =
if options
rendered.any? { |t,num| t.match(options) }
else
@templates.blank?
end
- end
+ assert matches_template, msg
when Hash
- if expected_layout = options[:layout]
+ if options.key?(:layout)
+ expected_layout = options[:layout]
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
expected_layout, @layouts.keys)
case expected_layout
- when String
- assert_includes @layouts.keys, expected_layout, msg
+ when String, Symbol
+ assert_includes @layouts.keys, expected_layout.to_s, msg
when Regexp
assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg)
- when nil
+ when nil, false
assert(@layouts.empty?, msg)
end
end
@@ -120,7 +132,7 @@ module ActionController
options[:partial], @partials.keys)
assert_includes @partials, expected_partial, msg
end
- else
+ elsif options.key?(:partial)
assert @partials.empty?,
"Expected no partials to be rendered"
end
@@ -140,9 +152,6 @@ module ActionController
class Result < ::Array #:nodoc:
def to_s() join '/' end
- def self.new_escaped(strings)
- new strings.collect {|str| uri_parser.unescape str}
- end
end
def assign_parameters(routes, controller_path, action, parameters = {})
@@ -150,17 +159,23 @@ module ActionController
extra_keys = routes.extra_keys(parameters)
non_path_parameters = get? ? query_parameters : request_parameters
parameters.each do |key, value|
- if value.is_a? Fixnum
- value = value.to_s
- elsif value.is_a? Array
- value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v })
- elsif value.is_a? String
+ if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
+ value = value.map{ |v| v.duplicable? ? v.dup : v }
+ elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
+ value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
+ elsif value.frozen? && value.duplicable?
value = value.dup
end
if extra_keys.include?(key.to_sym)
non_path_parameters[key] = value
else
+ if value.is_a?(Array)
+ value = Result.new(value.map(&:to_param))
+ else
+ value = value.to_param
+ end
+
path_parameters[key.to_s] = value
end
end
@@ -332,7 +347,6 @@ module ActionController
# == \Testing named routes
#
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
- # Example:
#
# assert_redirected_to page_url(:title => 'foo')
class TestCase < ActiveSupport::TestCase
@@ -351,12 +365,11 @@ module ActionController
module ClassMethods
# Sets the controller class name. Useful if the name can't be inferred from test class.
- # Normalizes +controller_class+ before using. Examples:
+ # Normalizes +controller_class+ before using.
#
# tests WidgetController
# tests :widget
# tests 'widget'
- #
def tests(controller_class)
case controller_class
when String, Symbol
@@ -456,7 +469,7 @@ module ActionController
# Ensure that numbers and symbols passed as params are converted to
# proper params, as is the case when engaging rack.
- parameters = paramify_values(parameters)
+ parameters = paramify_values(parameters) if html_format?(parameters)
@request.recycle!
@response.recycle!
@@ -469,14 +482,13 @@ module ActionController
parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
- "anonymous_controller" :
+ "anonymous" :
@controller.class.name.underscore.sub(/_controller$/, '')
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
- @request.session = ActionController::TestSession.new(session) if session
+ @request.session.update(session) if session
@request.session["flash"] = @request.flash.update(flash || {})
- @request.session["flash"].sweep
@controller.request = @request
build_request_uri(action, parameters)
@@ -549,6 +561,12 @@ module ActionController
@request.env["QUERY_STRING"] = query_string || ""
end
end
+
+ def html_format?(parameters)
+ return true unless parameters.is_a?(Hash)
+ format = Mime[parameters[:format]]
+ format.nil? || format.html?
+ end
end
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index e9b50ff8ce..6b269e7a31 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -1,6 +1,7 @@
require 'set'
require 'cgi'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/class/attribute_accessors'
module HTML
class Sanitizer
@@ -99,7 +100,7 @@ module HTML
self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
feed svn urn aim rsync tag ssh sftp rtsp afs))
- # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
+ # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
border-color border-left-color border-right-color border-top-color clear color cursor direction display
elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index e3b04ac097..1e4ac70f3d 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -21,12 +21,6 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
require 'active_support'
require 'active_support/dependencies/autoload'
require 'active_support/core_ext/module/attribute_accessors'
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 132b0c82bc..6413929be3 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -10,8 +10,6 @@ module ActionDispatch
# value of the params hash and all subhashes is passed to it, the value
# or key can be replaced using String#replace or similar method.
#
- # Examples:
- #
# env["action_dispatch.parameter_filter"] = [:password]
# => replaces the value to all keys matching /password/i with "[FILTERED]"
#
@@ -22,7 +20,6 @@ module ActionDispatch
# v.reverse! if k =~ /secret/i
# end
# => reverses the value to all keys matching /secret/i
- #
module FilterParameters
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 040b51e040..a3bb25f75a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -14,17 +14,18 @@ module ActionDispatch
end
def [](header_name)
- if include?(header_name)
- super
- else
- super(env_name(header_name))
- end
+ super env_name(header_name)
+ end
+
+ def fetch(header_name, default=nil, &block)
+ super env_name(header_name), default, &block
end
private
- # Converts a HTTP header name to an environment variable name.
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(header_name)
- @@env_cache[header_name]
+ include?(header_name) ? header_name : @@env_cache[header_name]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 5c48a60469..e31f3b823d 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
module ActionDispatch
module Http
module MimeNegotiation
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index e039eb1288..0eaae80461 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -38,7 +38,7 @@ module Mime
# respond_to do |format|
# format.html
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
# end
@@ -179,11 +179,11 @@ module Mime
end
end
- # input: 'text'
- # returned value: [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ # For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS,
+ # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>.
#
- # input: 'application'
- # returned value: [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]
+ # For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
+ # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
def parse_data_with_trailing_star(input)
Mime::SET.select { |m| m =~ input }
end
@@ -192,7 +192,7 @@ module Mime
#
# Usage:
#
- # Mime::Type.unregister(:mobile)
+ # Mime::Type.unregister(:mobile)
def unregister(symbol)
symbol = symbol.to_s.upcase
mime = Mime.const_get(symbol)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index d9b63faf5e..bcfd0b0d00 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -35,6 +35,10 @@ module ActionDispatch
@env["action_dispatch.request.path_parameters"] ||= {}
end
+ def reset_parameters #:nodoc:
+ @env.delete("action_dispatch.request.parameters")
+ end
+
private
# TODO: Validate that the characters are UTF-8. If they aren't,
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 796e0dbc45..56908b5794 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -17,6 +17,8 @@ module ActionDispatch
include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
+ autoload :Session, 'action_dispatch/request/session'
+
LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
@@ -220,11 +222,11 @@ module ActionDispatch
end
def session=(session) #:nodoc:
- @env['rack.session'] = session
+ Session.set @env, session
end
def session_options=(options)
- @env['rack.session.options'] = options
+ Session::Options.set @env, options
end
# Override Rack's GET method to support indifferent access
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 078229efd2..cc46f9983c 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -29,7 +29,7 @@ module ActionDispatch # :nodoc:
# class DemoControllerTest < ActionDispatch::IntegrationTest
# def test_print_root_path_to_console
# get('/')
- # puts @response.body
+ # puts response.body
# end
# end
class Response
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index f9dae5dad7..4266ec042e 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -23,23 +23,6 @@ module ActionDispatch
end
def url_for(options = {})
- if options[:host].blank? && options[:only_path].blank?
- raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
- end
-
- rewritten_url = ""
-
- unless options[:only_path]
- unless options[:protocol] == false
- rewritten_url << (options[:protocol] || "http")
- rewritten_url << ":" unless rewritten_url.match(%r{:|//})
- end
- rewritten_url << "//" unless rewritten_url.match("//")
- rewritten_url << rewrite_authentication(options)
- rewritten_url << host_or_subdomain_and_domain(options)
- rewritten_url << ":#{options.delete(:port)}" if options[:port]
- end
-
path = ""
path << options.delete(:script_name).to_s.chomp("/")
path << options.delete(:path).to_s
@@ -47,14 +30,36 @@ module ActionDispatch
params = options[:params] || {}
params.reject! {|k,v| v.to_param.nil? }
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "?#{params.to_query}" unless params.empty?
- rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- rewritten_url
+ result = build_host_url(options)
+
+ result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ result << "?#{params.to_query}" unless params.empty?
+ result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
+ result
end
private
+ def build_host_url(options)
+ if options[:host].blank? && options[:only_path].blank?
+ raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
+ end
+
+ result = ""
+
+ unless options[:only_path]
+ unless options[:protocol] == false
+ result << (options[:protocol] || "http")
+ result << ":" unless result.match(%r{:|//})
+ end
+ result << "//" unless result.match("//")
+ result << rewrite_authentication(options)
+ result << host_or_subdomain_and_domain(options)
+ result << ":#{options.delete(:port)}" if options[:port]
+ end
+ result
+ end
+
def named_host?(host)
host && IP_HOST_REGEXP !~ host
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 25f1db8228..771f075275 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/keys'
module ActionDispatch
- class Request
+ class Request < Rack::Request
def cookie_jar
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
end
@@ -26,9 +26,9 @@ module ActionDispatch
# # Sets a cookie that expires in 1 hour.
# cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
#
- # # Sets a signed cookie, which prevents a user from tampering with its value.
+ # # Sets a signed cookie, which prevents users from tampering with its value.
# # The cookie is signed by your app's <tt>config.secret_token</tt> value.
- # # Rails generates this value by default when you create a new Rails app.
+ # # It can be read using the signed method <tt>cookies.signed[:key]</tt>
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -39,9 +39,10 @@ module ActionDispatch
#
# Examples for reading:
#
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- # cookies[:lat_lon] # => [47.68, -122.37]
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ # cookies[:lat_lon] # => [47.68, -122.37]
+ # cookies.signed[:login] # => "XJ-122"
#
# Example for deleting:
#
@@ -82,7 +83,7 @@ module ActionDispatch
TOKEN_KEY = "action_dispatch.secret_token".freeze
# Raised when storing more than 4K of session data.
- class CookieOverflow < StandardError; end
+ CookieOverflow = Class.new StandardError
class CookieJar #:nodoc:
include Enumerable
@@ -117,7 +118,6 @@ module ActionDispatch
@delete_cookies = {}
@host = host
@secure = secure
- @closed = false
@cookies = {}
end
@@ -154,7 +154,7 @@ module ActionDispatch
end
elsif options[:domain].is_a? Array
# if host matches one of the supplied domains without a dot in front of it
- options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] }
+ options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
end
end
@@ -169,12 +169,14 @@ module ActionDispatch
options = { :value => value }
end
- @cookies[key.to_s] = value
-
handle_options(options)
- @set_cookies[key.to_s] = options
- @delete_cookies.delete(key.to_s)
+ if @cookies[key.to_s] != value or options[:expires]
+ @cookies[key.to_s] = value
+ @set_cookies[key.to_s] = options
+ @delete_cookies.delete(key.to_s)
+ end
+
value
end
@@ -182,8 +184,9 @@ module ActionDispatch
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
def delete(key, options = {})
- options.symbolize_keys!
+ return unless @cookies.has_key? key.to_s
+ options.symbolize_keys!
handle_options(options)
value = @cookies.delete(key.to_s)
@@ -225,7 +228,7 @@ module ActionDispatch
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
- # This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
#
# Example:
#
@@ -273,10 +276,6 @@ module ActionDispatch
@parent_jar[key] = options
end
- def signed
- @signed ||= SignedCookieJar.new(self, @secret)
- end
-
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
end
@@ -343,7 +342,6 @@ module ActionDispatch
end
def call(env)
- cookie_jar = nil
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index c0532c80c4..a8f49bd3bd 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,6 @@
require 'action_controller/metal/exceptions'
require 'active_support/core_ext/exception'
+require 'active_support/core_ext/class/attribute_accessors'
module ActionDispatch
class ExceptionWrapper
@@ -10,6 +11,7 @@ module ActionDispatch
'AbstractController::ActionNotFound' => :not_found,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::UnknownFormat' => :not_acceptable,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
)
@@ -75,4 +77,4 @@ module ActionDispatch
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index cff0877030..9928b7cc3a 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,23 +1,23 @@
module ActionDispatch
- class Request
+ class Request < Rack::Request
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash
- @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new)
+ @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep)
end
end
# The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
# action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
- # then expose the flash to its template. Actually, that exposure is automatically done. Example:
+ # then expose the flash to its template. Actually, that exposure is automatically done.
#
# class PostsController < ActionController::Base
# def create
# # save post
# flash[:notice] = "Post successfully created"
- # redirect_to posts_path(@post)
+ # redirect_to @post
# end
#
# def show
@@ -79,7 +79,6 @@ module ActionDispatch
def initialize #:nodoc:
@discard = Set.new
- @closed = false
@flashes = {}
@now = nil
end
@@ -217,13 +216,9 @@ module ActionDispatch
end
def call(env)
- if (session = env['rack.session']) && (flash = session['flash'])
- flash.sweep
- end
-
@app.call(env)
ensure
- session = env['rack.session'] || {}
+ session = Request::Session.find(env) || {}
flash_hash = env[KEY]
if flash_hash
@@ -237,7 +232,8 @@ module ActionDispatch
env[KEY] = new_hash
end
- if session.key?('flash') && session['flash'].empty?
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
+ session.key?('flash') && session['flash'].empty?
session.delete('flash')
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index a0388e0e13..2f6968eb2e 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -18,10 +18,10 @@ module ActionDispatch
# classes before they are unloaded.
#
# By default, ActionDispatch::Reloader is included in the middleware stack
- # only in the development environment; specifically, when config.cache_classes
+ # only in the development environment; specifically, when +config.cache_classes+
# is false. Callbacks may be registered even when it is not included in the
- # middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+
- # or +ActionDispatch::Reloader.cleanup!+ are called manually.
+ # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
+ # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
#
class Reloader
include ActiveSupport::Callbacks
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index d924f21fad..ec15a2a715 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -5,11 +5,14 @@ module ActionDispatch
# IP addresses that are "trusted proxies" that can be stripped from
# the comma-delimited list in the X-Forwarded-For header. See also:
# http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
+ # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
TRUSTED_PROXIES = %r{
^127\.0\.0\.1$ | # localhost
+ ^::1$ |
^(10 | # private IP 10.x.x.x
172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
- 192\.168 # private IP 192.168.x.x
+ 192\.168 | # private IP 192.168.x.x
+ fc00:: # private IP fc00
)\.
}x
@@ -19,13 +22,13 @@ module ActionDispatch
@app = app
@check_ip = check_ip_spoofing
@proxies = case custom_proxies
- when Regexp
- custom_proxies
- when nil
- TRUSTED_PROXIES
- else
- Regexp.union(TRUSTED_PROXIES, custom_proxies)
- end
+ when Regexp
+ custom_proxies
+ when nil
+ TRUSTED_PROXIES
+ else
+ Regexp.union(TRUSTED_PROXIES, custom_proxies)
+ end
end
def call(env)
@@ -34,6 +37,31 @@ module ActionDispatch
end
class GetIp
+
+ # IP v4 and v6 (with compression) validation regexp
+ # https://gist.github.com/1289635
+ VALID_IP = %r{
+ (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
+ (^(
+ (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
+ (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
+ (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
+ (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
+ (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
+ (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
+ (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
+ (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
+ )$)
+ }x
+
def initialize(env, middleware)
@env = env
@middleware = middleware
@@ -44,25 +72,31 @@ module ActionDispatch
# but will be wrong if the user is behind a proxy. Proxies will set
# HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
# HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
- # multiple chained proxies. The last address which is not a known proxy
- # will be the originating IP.
+ # multiple chained proxies. The first address which is in this list
+ # if it's not a known proxy will be the originating IP.
+ # Format of HTTP_X_FORWARDED_FOR:
+ # client_ip, proxy_ip1, proxy_ip2...
+ # http://en.wikipedia.org/wiki/X-Forwarded-For
def calculate_ip
- client_ip = @env['HTTP_CLIENT_IP']
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
- remote_addrs = ips_from('REMOTE_ADDR')
+ client_ip = @env['HTTP_CLIENT_IP']
+ forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first
+ remote_addrs = ips_from('REMOTE_ADDR')
check_ip = client_ip && @middleware.check_ip
- if check_ip && !forwarded_ips.include?(client_ip)
+ if check_ip && forwarded_ip != client_ip
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?!" \
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
- not_proxy = client_ip || forwarded_ips.first || remote_addrs.first
-
- # Return first REMOTE_ADDR if there are no other options
- not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
+ client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten
+ if client_ips.present?
+ client_ips.first
+ else
+ # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR
+ remote_addrs.find { |ip| valid_ip? ip }
+ end
end
def to_s
@@ -71,12 +105,24 @@ module ActionDispatch
@ip = calculate_ip
end
- protected
+ private
- def ips_from(header, allow_proxies = false)
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
- allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
+ def ips_from(header)
+ @env[header] ? @env[header].strip.split(/[,\s]+/) : []
end
+
+ def valid_ip?(ip)
+ ip =~ VALID_IP
+ end
+
+ def not_a_proxy?(ip)
+ ip !~ @middleware.proxies
+ end
+
+ def remove_proxies(ips)
+ ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
+ end
+
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 6a8e690d18..64159fa8e7 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -2,26 +2,23 @@ require 'rack/utils'
require 'rack/request'
require 'rack/session/abstract/id'
require 'action_dispatch/middleware/cookies'
+require 'action_dispatch/request/session'
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
- end
+ attr_reader :original_exception
+
+ def initialize(const_error)
+ @original_exception = const_error
- module DestroyableSession
- def destroy
- clear
- options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
- options ||= {}
- @by.send(:destroy_session, @env, options[:id], options) if @by
- options[:id] = nil
- @loaded = false
+ super("Session contains objects whose class definition isn't available.\n" +
+ "Remember to require the classes for all objects kept in the session.\n" +
+ "(Original exception: #{const_error.message} [#{const_error.class}])\n")
end
end
- ::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession
-
module Compatibility
def initialize(app, options = {})
options[:key] ||= '_session_id'
@@ -58,11 +55,8 @@ module ActionDispatch
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
- rescue LoadError, NameError => const_error
- raise ActionDispatch::Session::SessionRestoreError,
- "Session contains objects whose class definition isn't available.\n" +
- "Remember to require the classes for all objects kept in the session.\n" +
- "(Original exception: #{const_error.message} [#{const_error.class}])\n"
+ rescue LoadError, NameError => e
+ raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
end
retry
else
@@ -71,9 +65,27 @@ module ActionDispatch
end
end
+ module SessionObject # :nodoc:
+ def prepare_session(env)
+ Request::Session.create(self, env, @default_options)
+ end
+
+ def loaded_session?(session)
+ !session.is_a?(Request::Session) || session.loaded?
+ end
+ end
+
class AbstractStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
+ include SessionObject
+
+ private
+
+ def set_cookie(env, session_id, cookie)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar[key] = cookie
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index a4866f5a8f..7efc094f98 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -27,7 +27,7 @@ module ActionDispatch
# CGI::Session instance as an argument. It's important that the secret
# is not vulnerable to a dictionary attack. Therefore, you should choose
# a secret consisting of random numbers and letters and more than 30
- # characters. Examples:
+ # characters.
#
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
# :secret => Proc.new { User.current_user.secret_key }
@@ -43,6 +43,7 @@ module ActionDispatch
class CookieStore < Rack::Session::Cookie
include Compatibility
include StaleSessionCheck
+ include SessionObject
private
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index 4dd9a946c2..38a737cd2b 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -6,6 +6,7 @@ module ActionDispatch
class MemCacheStore < Rack::Session::Memcache
include Compatibility
include StaleSessionCheck
+ include SessionObject
def initialize(app, options = {})
require 'memcache'
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 28e8fbdab8..bbf734f103 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -75,6 +75,11 @@ module ActionDispatch
middlewares[i]
end
+ def unshift(*args, &block)
+ middleware = self.class::Middleware.new(*args, &block)
+ middlewares.unshift(middleware)
+ end
+
def initialize_copy(other)
self.middlewares = other.middlewares.dup
end
@@ -110,7 +115,7 @@ module ActionDispatch
def build(app = nil, &block)
app ||= block
raise "MiddlewareStack#build requires an app" unless app
- middlewares.reverse.inject(app) { |a, e| e.build(a) }
+ middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
end
protected
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 0c5bafa666..823f5d25b6 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -12,8 +12,8 @@
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
- def debug_hash(hash)
- hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
end unless self.class.method_defined?(:debug_hash)
%>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index f06c07daa5..177d383e94 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -12,4 +12,6 @@
<% end %>
<p>
Try running <code>rake routes</code> for more information on available routes.
-</p> \ No newline at end of file
+</p>
+
+<%= render :template => "rescues/_trace" %>
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
new file mode 100644
index 0000000000..4ad7071820
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -0,0 +1,166 @@
+require 'rack/session/abstract/id'
+
+module ActionDispatch
+ class Request < Rack::Request
+ # SessionHash is responsible to lazily load the session from store.
+ class Session # :nodoc:
+ ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
+ ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
+
+ def self.create(store, env, default_options)
+ session_was = find env
+ session = Request::Session.new(store, env)
+ session.merge! session_was if session_was
+
+ set(env, session)
+ Options.set(env, Request::Session::Options.new(store, env, default_options))
+ session
+ end
+
+ def self.find(env)
+ env[ENV_SESSION_KEY]
+ end
+
+ def self.set(env, session)
+ env[ENV_SESSION_KEY] = session
+ end
+
+ class Options #:nodoc:
+ def self.set(env, options)
+ env[ENV_SESSION_OPTIONS_KEY] = options
+ end
+
+ def self.find(env)
+ env[ENV_SESSION_OPTIONS_KEY]
+ end
+
+ def initialize(by, env, default_options)
+ @by = by
+ @env = env
+ @delegate = default_options.dup
+ end
+
+ def [](key)
+ if key == :id
+ @delegate.fetch(key) {
+ @delegate[:id] = @by.send(:extract_session_id, @env)
+ }
+ else
+ @delegate[key]
+ end
+ end
+
+ def []=(k,v); @delegate[k] = v; end
+ def to_hash; @delegate.dup; end
+ def values_at(*args); @delegate.values_at(*args); end
+ end
+
+ def initialize(by, env)
+ @by = by
+ @env = env
+ @delegate = {}
+ @loaded = false
+ @exists = nil # we haven't checked yet
+ end
+
+ def options
+ Options.find @env
+ end
+
+ def destroy
+ clear
+ options = self.options || {}
+ @by.send(:destroy_session, @env, options[:id], options)
+ options[:id] = nil
+ @loaded = false
+ end
+
+ def [](key)
+ load_for_read!
+ @delegate[key.to_s]
+ end
+
+ def has_key?(key)
+ load_for_read!
+ @delegate.key?(key.to_s)
+ end
+ alias :key? :has_key?
+ alias :include? :has_key?
+
+ def []=(key, value)
+ load_for_write!
+ @delegate[key.to_s] = value
+ end
+
+ def clear
+ load_for_write!
+ @delegate.clear
+ end
+
+ def to_hash
+ load_for_read!
+ @delegate.dup.delete_if { |_,v| v.nil? }
+ end
+
+ def update(hash)
+ load_for_write!
+ @delegate.update stringify_keys(hash)
+ end
+
+ def delete(key)
+ load_for_write!
+ @delegate.delete key.to_s
+ end
+
+ def inspect
+ if loaded?
+ super
+ else
+ "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
+ end
+ end
+
+ def exists?
+ return @exists unless @exists.nil?
+ @exists = @by.send(:session_exists?, @env)
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ def empty?
+ load_for_read!
+ @delegate.empty?
+ end
+
+ def merge!(other)
+ load_for_write!
+ @delegate.merge!(other)
+ end
+
+ private
+
+ def load_for_read!
+ load! if !loaded? && exists?
+ end
+
+ def load_for_write!
+ load! unless loaded?
+ end
+
+ def load!
+ id, session = @by.load_session @env
+ options[:id] = id
+ @delegate.replace(stringify_keys(session))
+ @loaded = true
+ end
+
+ def stringify_keys(other)
+ other.each_with_object({}) { |(key, value), hash|
+ hash[key.to_s] = value
+ }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index fccbff1749..67a208263b 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,4 +1,6 @@
require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/reverse_merge'
+require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/enumerable'
require 'active_support/inflector'
@@ -34,6 +36,8 @@ module ActionDispatch
}
return true
+ ensure
+ req.reset_parameters
end
def call(env)
@@ -58,6 +62,16 @@ module ActionDispatch
@options = (@scope[:options] || {}).merge(options)
@path = normalize_path(path)
normalize_options!
+
+ via_all = @options.delete(:via) if @options[:via] == :all
+
+ if !via_all && request_method_condition.empty?
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n\n" \
+ " Instead of: match \"controller#action\"\n" \
+ " Do: get \"controller#action\""
+ raise msg
+ end
end
def to_route
@@ -87,6 +101,10 @@ module ActionDispatch
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
end
end
+
+ if @options[:constraints].is_a?(Hash)
+ (@options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(@options[:constraints]))
+ end
end
# match "account/overview"
@@ -232,6 +250,11 @@ module ActionDispatch
def default_action
@options[:action] || @scope[:action]
end
+
+ def defaults_from_constraints(constraints)
+ url_keys = [:protocol, :subdomain, :domain, :host, :port]
+ constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
+ end
end
# Invokes Rack::Mount::Utils.normalize path and ensure that
@@ -244,7 +267,7 @@ module ActionDispatch
end
def self.normalize_name(name)
- normalize_path(name)[1..-1].gsub("/", "_")
+ normalize_path(name)[1..-1].tr("/", "_")
end
module Base
@@ -263,7 +286,7 @@ module ActionDispatch
# of most Rails applications, this is beneficial.
def root(options = {})
options = { :to => options } if options.is_a?(String)
- match '/', { :as => :root }.merge(options)
+ match '/', { :as => :root, :via => :get }.merge(options)
end
# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -416,7 +439,7 @@ module ActionDispatch
options[:as] ||= app_name(app)
- match(path, options.merge(:to => app, :anchor => false, :format => false))
+ match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all))
define_generate_prefix(app, options[:as])
self
@@ -441,7 +464,7 @@ module ActionDispatch
app.railtie_name
else
class_name = app.class.is_a?(Class) ? app.name : app.class.name
- ActiveSupport::Inflector.underscore(class_name).gsub("/", "_")
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
end
end
@@ -472,8 +495,6 @@ module ActionDispatch
# Define a route that only recognizes HTTP GET.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
# get 'bacon', :to => 'food#bacon'
def get(*args, &block)
map_method(:get, args, &block)
@@ -482,8 +503,6 @@ module ActionDispatch
# Define a route that only recognizes HTTP POST.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
# post 'bacon', :to => 'food#bacon'
def post(*args, &block)
map_method(:post, args, &block)
@@ -492,8 +511,6 @@ module ActionDispatch
# Define a route that only recognizes HTTP PATCH.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
# patch 'bacon', :to => 'food#bacon'
def patch(*args, &block)
map_method(:patch, args, &block)
@@ -502,8 +519,6 @@ module ActionDispatch
# Define a route that only recognizes HTTP PUT.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
# put 'bacon', :to => 'food#bacon'
def put(*args, &block)
map_method(:put, args, &block)
@@ -512,8 +527,6 @@ module ActionDispatch
# Define a route that only recognizes HTTP DELETE.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
# delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block)
map_method(:delete, args, &block)
@@ -522,7 +535,8 @@ module ActionDispatch
private
def map_method(method, args, &block)
options = args.extract_options!
- options[:via] = method
+ options[:via] = method
+ options[:path] ||= args.first if args.first.is_a?(String)
match(*args, options, &block)
self
end
@@ -627,6 +641,10 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
+ if options[:constraints].is_a?(Hash)
+ (options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(options[:constraints]))
+ end
+
scope_options.each do |option|
if value = options.delete(option)
recover[option] = @scope[option]
@@ -653,7 +671,6 @@ module ActionDispatch
# Scopes routes to a specific controller
#
- # Example:
# controller "food" do
# match "bacon", :action => "bacon"
# end
@@ -835,6 +852,11 @@ module ActionDispatch
def override_keys(child) #:nodoc:
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
end
+
+ def defaults_from_constraints(constraints)
+ url_keys = [:protocol, :subdomain, :domain, :host, :port]
+ constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
+ end
end
# Resource routing allows you to quickly declare all of the common routes
@@ -1294,6 +1316,22 @@ module ActionDispatch
parent_resource.instance_of?(Resource) && @scope[:shallow]
end
+ def draw(name)
+ path = @draw_paths.find do |_path|
+ _path.join("#{name}.rb").file?
+ end
+
+ unless path
+ msg = "Your router tried to #draw the external file #{name}.rb,\n" \
+ "but the file was not found in:\n\n"
+ msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
+ raise ArgumentError, msg
+ end
+
+ route_path = path.join("#{name}.rb")
+ instance_eval(route_path.read, route_path.to_s)
+ end
+
# match 'path' => 'controller#action'
# match 'path', to: 'controller#action'
# match 'path', 'otherpath', on: :member, via: :get
@@ -1481,7 +1519,7 @@ module ActionDispatch
prefix = shallow_scoping? ?
"#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
- path = if canonical_action?(action, path.blank?)
+ if canonical_action?(action, path.blank?)
prefix.to_s
else
"#{prefix}/#{action_path(action, path)}"
@@ -1543,6 +1581,7 @@ module ActionDispatch
def initialize(set) #:nodoc:
@set = set
+ @draw_paths = set.draw_paths
@scope = { :path_names => @set.resources_path_names }
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 013cf93dbc..8fde667108 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -43,16 +43,14 @@ module ActionDispatch
# edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
#
- # == Using with mounted engines
+ # == Usage with mounted engines
#
- # If you use mounted engine, there is a possibility that you will need to use
- # polymorphic_url pointing at engine's routes. To do that, just pass proxy used
- # to reach engine's routes as a first argument:
+ # If you are using a mounted engine and you need to use a polymorphic_url
+ # pointing at the engine's routes, pass in the engine's route proxy as the first
+ # argument to the method. For example:
#
- # For example:
- #
- # polymorphic_url([blog, @post]) # it will call blog.post_path(@post)
- # form_for([blog, @post]) # => "/blog/posts/1
+ # polymorphic_url([blog, @post]) # calls blog.post_path(@post)
+ # form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 617b24b46a..b3823bb496 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -1,4 +1,7 @@
require 'action_dispatch/http/request'
+require 'active_support/core_ext/uri'
+require 'active_support/core_ext/array/extract_options'
+require 'rack/utils'
module ActionDispatch
module Routing
@@ -32,6 +35,25 @@ module ActionDispatch
def path(params, request)
block.call params, request
end
+
+ def inspect
+ "redirect(#{status})"
+ end
+ end
+
+ class PathRedirect < Redirect
+ def path(params, request)
+ (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
+ end
+
+ def inspect
+ "redirect(#{status}, #{block})"
+ end
+
+ private
+ def escape(params)
+ Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ end
end
class OptionRedirect < Redirect # :nodoc:
@@ -46,8 +68,21 @@ module ActionDispatch
:params => request.query_parameters
}.merge options
+ if !params.empty? && url_options[:path].match(/%\{\w*\}/)
+ url_options[:path] = (url_options[:path] % escape_path(params))
+ end
+
ActionDispatch::Http::URL.url_for url_options
end
+
+ def inspect
+ "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
+ end
+
+ private
+ def escape_path(params)
+ Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
+ end
end
module Redirection
@@ -67,10 +102,13 @@ module ActionDispatch
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
- # match 'jokes/:number', :to => redirect do |params, request|
- # path = (params[:number].to_i.even? ? "/wheres-the-beef" : "/i-love-lamp")
+ # match 'jokes/:number', :to => redirect { |params, request|
+ # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
- # end
+ # }
+ #
+ # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
+ # the block to +match+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
@@ -85,16 +123,12 @@ module ActionDispatch
# match 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
status = options.delete(:status) || 301
+ path = args.shift
return OptionRedirect.new(status, options) if options.any?
-
- path = args.shift
-
- block = lambda { |params, request|
- (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params)
- } if String === path
+ return PathRedirect.new(status, path) if String === path
block = path if path.respond_to? :call
raise ArgumentError, "redirection argument not supported" unless block
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 30e9e5634b..0ae668d42a 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -96,7 +96,25 @@ module ActionDispatch
def initialize
@routes = {}
@helpers = []
- @module = Module.new
+ @module = Module.new do
+ protected
+
+ def handle_positional_args(args, options, segment_keys)
+ inner_options = args.extract_options!
+ result = options.dup
+
+ if args.size > 0
+ keys = segment_keys
+ if args.size < keys.size - 1 # take format into account
+ keys -= self.url_options.keys if self.respond_to?(:url_options)
+ keys -= options.keys
+ end
+ result.merge!(Hash[keys.zip(args)])
+ end
+
+ result.merge!(inner_options)
+ end
+ end
end
def helper_names
@@ -135,43 +153,19 @@ module ActionDispatch
end
private
- def url_helper_name(name, kind = :url)
- :"#{name}_#{kind}"
- end
-
- def hash_access_name(name, kind = :url)
- :"hash_for_#{name}_#{kind}"
- end
-
- def define_named_route_methods(name, route)
- {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
- hash = route.defaults.merge(:use_route => name).merge(opts)
- define_hash_access route, name, kind, hash
- define_url_helper route, name, kind, hash
+ def url_helper_name(name, only_path)
+ if only_path
+ :"#{name}_path"
+ else
+ :"#{name}_url"
end
end
- def define_hash_access(route, name, kind, options)
- selector = hash_access_name(name, kind)
-
- @module.module_eval do
- remove_possible_method selector
-
- define_method(selector) do |*args|
- inner_options = args.extract_options!
- result = options.dup
-
- if args.any?
- result[:_positional_args] = args
- result[:_positional_keys] = route.segment_keys
- end
-
- result.merge(inner_options)
- end
-
- protected selector
+ def define_named_route_methods(name, route)
+ [true, false].each do |only_path|
+ hash = route.defaults.merge(:use_route => name, :only_path => only_path)
+ define_url_helper route, name, hash
end
- helpers << selector
end
# Create a url helper allowing ordered parameters to be associated
@@ -187,38 +181,29 @@ module ActionDispatch
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
- def define_url_helper(route, name, kind, options)
- selector = url_helper_name(name, kind)
- hash_access_method = hash_access_name(name, kind)
-
- if optimize_helper?(route)
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{selector}
- def #{selector}(*args)
- if args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
- options = #{options.inspect}.merge!(url_options)
- options[:path] = "#{optimized_helper(route)}"
- ActionDispatch::Http::URL.url_for(options)
- else
- url_for(#{hash_access_method}(*args))
- end
+ def define_url_helper(route, name, options)
+ selector = url_helper_name(name, options[:only_path])
+
+ @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
+ remove_possible_method :#{selector}
+ def #{selector}(*args)
+ if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
+ options = #{options.inspect}
+ options.merge!(url_options) if respond_to?(:url_options)
+ options[:path] = "#{optimized_helper(route)}"
+ ActionDispatch::Http::URL.url_for(options)
+ else
+ url_for(handle_positional_args(args, #{options.inspect}, #{route.segment_keys.inspect}))
end
- END_EVAL
- else
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{selector}
- def #{selector}(*args)
- url_for(#{hash_access_method}(*args))
- end
- END_EVAL
- end
+ end
+ END_EVAL
helpers << selector
end
# Clause check about when we need to generate an optimized helper.
def optimize_helper?(route) #:nodoc:
- route.ast.grep(Journey::Nodes::Star).empty? && route.requirements.except(:controller, :action).empty?
+ route.requirements.except(:controller, :action).empty?
end
# Generates the interpolation to be used in the optimized helper.
@@ -230,7 +215,10 @@ module ActionDispatch
end
route.required_parts.each_with_index do |part, i|
- string_route.gsub!(part.inspect, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
+ # Replace each route parameter
+ # e.g. :id for regular parameter or *path for globbing
+ # with ruby string interpolation code
+ string_route.gsub!(/(\*|:)#{part}/, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
end
string_route
@@ -240,6 +228,7 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
+ attr_accessor :draw_paths
alias :routes :set
@@ -251,6 +240,7 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
+ self.draw_paths = []
self.request_class = request_class
@valid_conditions = {}
@@ -470,12 +460,12 @@ module ActionDispatch
normalize_options!
normalize_controller_action_id!
use_relative_controller!
- controller.sub!(%r{^/}, '') if controller
+ normalize_controller!
handle_nil_action!
end
def controller
- @controller ||= @options[:controller]
+ @options[:controller]
end
def current_controller
@@ -532,10 +522,15 @@ module ActionDispatch
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
- @controller = @options[:controller] = parts.join("/")
+ @options[:controller] = parts.join("/")
end
end
+ # Remove leading slashes from controllers
+ def normalize_controller!
+ @options[:controller] = controller.sub(%r{^/}, '') if controller
+ end
+
# This handles the case of :action => nil being explicitly passed.
# It is identical to :action => "index"
def handle_nil_action!
@@ -605,10 +600,9 @@ module ActionDispatch
nil
end
+ # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
def url_for(options)
- options = (options || {}).reverse_merge!(default_url_options)
-
- handle_positional_args(options)
+ options = default_url_options.merge(options || {})
user, password = extract_authentication(options)
path_segments = options.delete(:_path_segments)
@@ -679,16 +673,6 @@ module ActionDispatch
end
end
- def handle_positional_args(options)
- return unless args = options.delete(:_positional_args)
-
- keys = options.delete(:_positional_keys)
- keys -= options.keys if args.size < keys.size - 1 # take format into account
-
- # Tell url_for to skip default_url_options
- options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }])
- end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 94db36ce1f..fd3bed7e8f 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -68,7 +68,7 @@ module ActionDispatch
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
- # you can do that by including ActionController::UrlFor in your class:
+ # you can do that by including Rails.application.routes.url_helpers in your class:
#
# class User < ActiveRecord::Base
# include Rails.application.routes.url_helpers
@@ -132,8 +132,6 @@ module ActionDispatch
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
#
- # Examples:
- #
# url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080'
# # => 'http://somehost.org:8080/tasks/testing'
# url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true
@@ -144,10 +142,12 @@ module ActionDispatch
# # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = nil)
case options
+ when nil
+ _routes.url_for(url_options.symbolize_keys)
+ when Hash
+ _routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
when String
options
- when nil, Hash
- _routes.url_for((options || {}).symbolize_keys.reverse_merge!(url_options))
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
index edea6dab39..7dc3d0f97c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
@@ -5,11 +5,8 @@ module ActionDispatch
module DomAssertions
# \Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
#
- # ==== Examples
- #
# # assert that the referenced method generates the appropriate HTML string
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
- #
def assert_dom_equal(expected, actual, message = "")
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
@@ -18,11 +15,8 @@ module ActionDispatch
# The negated form of +assert_dom_equivalent+.
#
- # ==== Examples
- #
# # assert that the referenced method does not generate the specified HTML string
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
- #
def assert_dom_not_equal(expected, actual, message = "")
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index a5e7a8c715..3d121b6b9c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -4,11 +4,9 @@ module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
module ResponseAssertions
- extend ActiveSupport::Concern
-
# Asserts that the response is one of the following types:
#
- # * <tt>:success</tt> - Status code was 200
+ # * <tt>:success</tt> - Status code was in the 200-299 range
# * <tt>:redirect</tt> - Status code was in the 300-399 range
# * <tt>:missing</tt> - Status code was 404
# * <tt>:error</tt> - Status code was in the 500-599 range
@@ -17,14 +15,11 @@ module ActionDispatch
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
#
- # ==== Examples
- #
# # assert that the response was a redirection
# assert_response :redirect
#
# # assert that the response code was status code 401 (unauthorized)
# assert_response 401
- #
def assert_response(type, message = nil)
message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
@@ -44,8 +39,6 @@ module ActionDispatch
# This match can be partial, such that <tt>assert_redirected_to(:controller => "weblog")</tt> will also
# match the redirection of <tt>redirect_to(:controller => "weblog", :action => "show")</tt> and so on.
#
- # ==== Examples
- #
# # assert that the redirection was to the "index" action on the WeblogController
# assert_redirected_to :controller => "weblog", :action => "index"
#
@@ -55,15 +48,17 @@ module ActionDispatch
# # assert that the redirection was to the url for @customer
# assert_redirected_to @customer
#
+ # # asserts that the redirection matches the regular expression
+ # assert_redirected_to %r(\Ahttp://example.org)
def assert_redirected_to(options = {}, message=nil)
assert_response(:redirect, message)
- return true if options == @response.location
+ return true if options === @response.location
redirect_is = normalize_argument_to_redirection(@response.location)
redirect_expected = normalize_argument_to_redirection(options)
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
- assert_equal redirect_expected, redirect_is, message
+ assert_operator redirect_expected, :===, redirect_is, message
end
private
@@ -73,17 +68,21 @@ module ActionDispatch
end
def normalize_argument_to_redirection(fragment)
- case fragment
- when %r{^\w[A-Za-z\d+.-]*:.*}
- fragment
- when String
- @request.protocol + @request.host_with_port + fragment
- when :back
- raise RedirectBackError unless refer = @request.headers["Referer"]
- refer
- else
- @controller.url_for(fragment)
- end.gsub(/[\0\r\n]/, '')
+ normalized = case fragment
+ when Regexp
+ fragment
+ when %r{^\w[A-Za-z\d+.-]*:.*}
+ fragment
+ when String
+ @request.protocol + @request.host_with_port + fragment
+ when :back
+ raise RedirectBackError unless refer = @request.headers["Referer"]
+ refer
+ else
+ @controller.url_for(fragment)
+ end
+
+ normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 1f4b905d18..567ca0c392 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -26,7 +26,6 @@ module ActionDispatch
#
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
#
- # ==== Examples
# # Check the default route (i.e., the index action)
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
#
@@ -57,7 +56,6 @@ module ActionDispatch
#
# The +defaults+ parameter is unused.
#
- # ==== Examples
# # Asserts that the default action is generated for a route with no action
# assert_generates "/items", :controller => "items", :action => "index"
#
@@ -100,7 +98,6 @@ module ActionDispatch
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
# +message+ parameter allows you to specify a custom error message to display upon failure.
#
- # ==== Examples
# # Assert a basic route: a controller with the default action (index)
# assert_routing '/home', :controller => 'home', :action => 'index'
#
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index ea1ed20f3c..5f9c3bbf48 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -39,7 +39,6 @@ module ActionDispatch
# The selector may be a CSS selector expression (String), an expression
# with substitution values (Array) or an HTML::Selector object.
#
- # ==== Examples
# # Selects all div tags
# divs = css_select("div")
#
@@ -58,7 +57,6 @@ module ActionDispatch
# inputs = css_select(form, "input")
# ...
# end
- #
def css_select(*args)
# See assert_select to understand what's going on here.
arg = args.shift
@@ -340,7 +338,6 @@ module ActionDispatch
# The content of each element is un-encoded, and wrapped in the root
# element +encoded+. It then calls the block with all un-encoded elements.
#
- # ==== Examples
# # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
# assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
# # Select each entry item and then the title item
@@ -401,8 +398,6 @@ module ActionDispatch
# You must enable deliveries for this assertion to work, use:
# ActionMailer::Base.perform_deliveries = true
#
- # ==== Examples
- #
# assert_select_email do
# assert_select "h1", "Email alert"
# end
@@ -413,7 +408,6 @@ module ActionDispatch
# # Work with items here...
# end
# end
- #
def assert_select_email(&block)
deliveries = ActionMailer::Base.deliveries
assert !deliveries.empty?, "No e-mail in delivery list"
diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
index 5c735e61b2..68f1347e7c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
@@ -48,8 +48,6 @@ module ActionDispatch
# * if the condition is +true+, the value must not be +nil+.
# * if the condition is +false+ or +nil+, the value must be +nil+.
#
- # === Examples
- #
# # Assert that there is a "span" tag
# assert_tag :tag => "span"
#
@@ -104,7 +102,6 @@ module ActionDispatch
# Identical to +assert_tag+, but asserts that a matching tag does _not_
# exist. (See +assert_tag+ for a full discussion of the syntax.)
#
- # === Examples
# # Assert that there is not a "div" containing a "p"
# assert_no_tag :tag => "div", :descendant => { :tag => "p" }
#
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 69d54f6981..08fd28d72d 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -201,9 +201,16 @@ module ActionDispatch
reset!
end
- remove_method :default_url_options
- def default_url_options
- { :host => host, :protocol => https? ? "https" : "http" }
+ def url_options
+ @url_options ||= default_url_options.dup.tap do |url_options|
+ url_options.reverse_merge!(controller.url_options) if controller
+
+ if @app.respond_to?(:routes) && @app.routes.respond_to?(:default_url_options)
+ url_options.reverse_merge!(@app.routes.default_url_options)
+ end
+
+ url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http")
+ end
end
# Resets the instance. This can be used to reset the state information
@@ -216,6 +223,7 @@ module ActionDispatch
@controller = @request = @response = nil
@_mock_session = nil
@request_count = 0
+ @url_options = nil
self.host = DEFAULT_HOST
self.remote_addr = "127.0.0.1"
@@ -310,6 +318,7 @@ module ActionDispatch
response = _mock_session.last_response
@response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body)
@html_document = nil
+ @url_options = nil
@controller = session.last_request.env['action_controller.instance']
@@ -367,12 +376,14 @@ module ActionDispatch
end
end
- extend ActiveSupport::Concern
- include ActionDispatch::Routing::UrlFor
+ def default_url_options
+ reset! unless integration_session
+ integration_session.default_url_options
+ end
- def url_options
+ def default_url_options=(options)
reset! unless integration_session
- integration_session.url_options
+ integration_session.default_url_options = options
end
def respond_to?(method, include_private = false)
@@ -476,6 +487,7 @@ module ActionDispatch
class IntegrationTest < ActiveSupport::TestCase
include Integration::Runner
include ActionController::TemplateAssertions
+ include ActionDispatch::Routing::UrlFor
@@app = nil
@@ -495,5 +507,10 @@ module ActionDispatch
def app
super || self.class.app
end
+
+ def url_options
+ reset! unless integration_session
+ integration_session.url_options
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 7280e9a93b..d04be2099c 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/hash/reverse_merge'
require 'rack/utils'
module ActionDispatch
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 349a3fcc6e..3823f87027 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -21,9 +21,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-require 'active_support/ruby/shim'
-require 'active_support/core_ext/class/attribute_accessors'
-
+require 'active_support'
require 'action_pack'
module ActionView
@@ -78,7 +76,8 @@ module ActionView
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
end
-require 'active_support/i18n'
require 'active_support/core_ext/string/output_safety'
-I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
+end
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index add8d94b70..4ce41d51f1 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -4,7 +4,7 @@ require 'action_controller/metal/exceptions'
module ActionView
class AssetPaths #:nodoc:
- URI_REGEXP = %r{^[-a-z]+://|^cid:|^//}
+ URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
attr_reader :config, :controller
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 056dbc9529..f98648d930 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
@@ -139,7 +140,7 @@ module ActionView #:nodoc:
# How to complete the streaming when an exception occurs.
# This is our best guess: first try to close the attribute, then the tag.
cattr_accessor :streaming_completion_on_exception
- @@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>)
+ @@streaming_completion_on_exception = %("><script>window.location = "/500.html"</script></html>)
# Specify whether rendering within namespaced controllers should prefix
# the partial paths for ActiveModel objects with the namespace.
@@ -200,7 +201,7 @@ module ActionView #:nodoc:
# TODO Provide a new API for AV::Base and deprecate this one.
if context.is_a?(ActionView::Renderer)
@view_renderer = context
- elsif
+ else
lookup_context = context.is_a?(ActionView::LookupContext) ?
context : ActionView::LookupContext.new(context)
lookup_context.formats = formats if formats
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
index 083856b2ca..245849d706 100644
--- a/actionpack/lib/action_view/context.rb
+++ b/actionpack/lib/action_view/context.rb
@@ -5,7 +5,7 @@ module ActionView
# = Action View Context
#
- # Action View contexts are supplied to Action Controller to render template.
+ # Action View contexts are supplied to Action Controller to render a template.
# The default Action View context is ActionView::Base.
#
# In order to work with ActionController, a Context must just include this module.
@@ -25,7 +25,7 @@ module ActionView
end
# Encapsulates the interaction with the view flow so it
- # returns the correct buffer on yield. This is usually
+ # returns the correct buffer on +yield+. This is usually
# overwriten by helpers to add more behavior.
# :api: plugin
def _layout_for(name=nil)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 6dd52d8186..02c1250c76 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -13,15 +13,16 @@ module ActionView
# the assets exist before linking to them:
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
+ #
#
# === Using asset hosts
#
# By default, Rails links to these assets on the current host in the public
# folder, but you can direct Rails to link to assets from a dedicated asset
- # server by setting ActionController::Base.asset_host in the application
+ # server by setting <tt>ActionController::Base.asset_host</tt> in the application
# configuration, typically in <tt>config/environments/production.rb</tt>.
# For example, you'd define <tt>assets.example.com</tt> to be your asset
# host this way, inside the <tt>configure</tt> block of your environment-specific
@@ -32,9 +33,9 @@ module ActionView
# Helpers take that into account:
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# Browsers typically open at most two simultaneous connections to a single
# host, which means your assets often have to wait for other assets to finish
@@ -45,9 +46,9 @@ module ActionView
# will open eight simultaneous connections rather than two.
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# To do this, you can either setup four actual hosts, or you can use wildcard
# DNS to CNAME the wildcard to a single asset host. You can read more about
@@ -64,29 +65,28 @@ module ActionView
# "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" />
+ # # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# The example above generates "http://assets1.example.com" and
# "http://assets2.example.com". This option is useful for example if
# you need fewer/more than four hosts, custom host names, etc.
#
# As you see the proc takes a +source+ parameter. That's a string with the
- # absolute path of the asset with any extensions and timestamps in place,
- # for example "/images/rails.png?1230601161".
+ # absolute path of the asset, for example "/assets/rails.png".
#
# ActionController::Base.asset_host = Proc.new { |source|
- # if source.starts_with?('/images')
- # "http://images.example.com"
+ # if source.ends_with?('.css')
+ # "http://stylesheets.example.com"
# else
# "http://assets.example.com"
# end
# }
# image_tag("rails.png")
- # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# Alternatively you may ask for a second parameter +request+. That one is
# particularly useful for serving assets from an SSL-protected page. The
@@ -163,7 +163,7 @@ module ActionView
# image_tag("rails.png")
# # => <img alt="Rails" src="/release-12345/images/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# Changing the asset_path does require that your web servers have
# knowledge of the asset template paths that you rewrite to so it's not
@@ -252,7 +252,6 @@ module ActionView
# The following call would generate such a tag:
#
# <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
- #
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
@@ -261,13 +260,13 @@ module ActionView
}.merge(options.symbolize_keys))
end
- # Computes the path to an image asset in the public images directory.
+ # Computes the path to an image asset.
# Full paths from the document root will be passed through.
# Used internally by +image_tag+ to build the image path:
#
- # image_path("edit") # => "/images/edit"
- # image_path("edit.png") # => "/images/edit.png"
- # image_path("icons/edit.png") # => "/images/icons/edit.png"
+ # image_path("edit") # => "/assets/edit"
+ # image_path("edit.png") # => "/assets/edit.png"
+ # image_path("icons/edit.png") # => "/assets/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
@@ -275,11 +274,11 @@ module ActionView
# 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')
+ source.present? ? asset_paths.compute_public_path(source, 'images') : ""
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
- # Computes the full URL to an image asset in the public images directory.
+ # Computes the full URL to an image asset.
# This will use +image_path+ internally, so most of their behaviors will be the same.
def image_url(source)
URI.join(current_host, path_to_image(source)).to_s
@@ -290,7 +289,6 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +video_tag+ to build the video path.
#
- # ==== Examples
# video_path("hd") # => /videos/hd
# video_path("hd.avi") # => /videos/hd.avi
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
@@ -312,7 +310,6 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +audio_tag+ to build the audio path.
#
- # ==== Examples
# audio_path("horse") # => /audios/horse
# audio_path("horse.wav") # => /audios/horse.wav
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
@@ -323,20 +320,19 @@ module ActionView
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
- # Computes the full URL to a audio asset in the public audios directory.
+ # Computes the full URL to an audio asset in the public audios directory.
# This will use +audio_path+ internally, so most of their behaviors will be the same.
def audio_url(source)
URI.join(current_host, path_to_audio(source)).to_s
end
alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
- # Computes the path to a font asset in the public fonts directory.
+ # Computes the path to a font asset.
# Full paths from the document root will be passed through.
#
- # ==== Examples
- # font_path("font") # => /fonts/font
- # font_path("font.ttf") # => /fonts/font.ttf
- # font_path("dir/font.ttf") # => /fonts/dir/font.ttf
+ # font_path("font") # => /assets/font
+ # font_path("font.ttf") # => /assets/font.ttf
+ # font_path("dir/font.ttf") # => /assets/dir/font.ttf
# font_path("/dir/font.ttf") # => /dir/font.ttf
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
def font_path(source)
@@ -344,7 +340,7 @@ module ActionView
end
alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
- # Computes the full URL to a font asset in the public fonts directory.
+ # Computes the full URL to a font asset.
# This will use +font_path+ internally, so most of their behaviors will be the same.
def font_url(source)
URI.join(current_host, path_to_font(source)).to_s
@@ -352,7 +348,7 @@ module ActionView
alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
# Returns an html image tag for the +source+. The +source+ can be a full
- # path or a file that exists in your public images directory.
+ # path or a file.
#
# ==== Options
# You can add HTML attributes using the +options+. The +options+ supports
@@ -363,33 +359,25 @@ module ActionView
# * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
# width="30" and height="45". <tt>:size</tt> will be ignored if the
# value is not in the correct format.
- # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
- # event is fired, and sets the original image to be replaced onmouseout.
- # This can be used to implement an easy image toggle that fires on onmouseover.
#
- # ==== Examples
# image_tag("icon") # =>
- # <img src="/images/icon" alt="Icon" />
+ # <img src="/assets/icon" alt="Icon" />
# image_tag("icon.png") # =>
- # <img src="/images/icon.png" alt="Icon" />
+ # <img src="/assets/icon.png" alt="Icon" />
# image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
- # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
+ # <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
# image_tag("/icons/icon.gif", :size => "16x16") # =>
# <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
# image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
- # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
- # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
- # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
def image_tag(source, options={})
- options = options.dup.symbolize_keys!
+ options = options.symbolize_keys
src = options[:src] = path_to_image(source)
- unless src =~ /^cid:/
+ unless src =~ /^(?:cid|data):/ || src.blank?
options[:alt] = options.fetch(:alt){ image_alt(src) }
end
@@ -397,11 +385,6 @@ module ActionView
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
end
- if mouseover = options.delete(:mouseover)
- options[:onmouseover] = "this.src='#{path_to_image(mouseover)}'"
- options[:onmouseout] = "this.src='#{src}'"
- end
-
tag("img", options)
end
@@ -425,7 +408,6 @@ module ActionView
# width="30" and height="45". <tt>:size</tt> will be ignored if the
# value is not in the correct format.
#
- # ==== Examples
# video_tag("trailer") # =>
# <video src="/videos/trailer" />
# video_tag("trailer.ogg") # =>
@@ -433,7 +415,7 @@ module ActionView
# video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
# <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
# video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
- # <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png" />
+ # <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
# video_tag("/trailers/hd.avi", :size => "16x16") # =>
# <video src="/trailers/hd.avi" width="16" height="16" />
# video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
@@ -442,7 +424,7 @@ module ActionView
# <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"]) # =>
# <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
+ # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") # =>
# <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
def video_tag(*sources)
multiple_sources_tag('video', sources) do |options|
@@ -458,15 +440,14 @@ module ActionView
# The +source+ can be full path or file that exists in
# your public audios directory.
#
- # ==== Examples
- # audio_tag("sound") # =>
- # <audio src="/audios/sound" />
- # audio_tag("sound.wav") # =>
- # <audio src="/audios/sound.wav" />
- # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
- # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
- # audio_tag("sound.wav", "sound.mid") # =>
- # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
+ # audio_tag("sound") # =>
+ # <audio src="/audios/sound" />
+ # audio_tag("sound.wav") # =>
+ # <audio src="/audios/sound.wav" />
+ # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
+ # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
+ # audio_tag("sound.wav", "sound.mid") # =>
+ # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
def audio_tag(*sources)
multiple_sources_tag('audio', sources)
end
@@ -478,7 +459,7 @@ module ActionView
end
def multiple_sources_tag(type, sources)
- options = sources.extract_options!.dup.symbolize_keys!
+ options = sources.extract_options!.symbolize_keys
sources.flatten!
yield options if block_given?
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 dd4e9ae4cc..35f91cec18 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,5 +1,6 @@
require 'thread'
require 'active_support/core_ext/file'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
module Helpers
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 c67f81dcf4..4292d29f60 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
@@ -16,7 +16,7 @@ module ActionView
end
def asset_tag(source, options)
- content_tag("script", "", { "type" => Mime::JS, "src" => path_to_asset(source) }.merge(options))
+ content_tag("script", "", { "src" => path_to_asset(source) }.merge(options))
end
def custom_dir
@@ -60,9 +60,9 @@ module ActionView
# ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
#
# javascript_include_tag :monkey # =>
- # <script type="text/javascript" src="/javascripts/head.js"></script>
- # <script type="text/javascript" src="/javascripts/body.js"></script>
- # <script type="text/javascript" src="/javascripts/tail.js"></script>
+ # <script src="/javascripts/head.js"></script>
+ # <script src="/javascripts/body.js"></script>
+ # <script src="/javascripts/tail.js"></script>
def register_javascript_expansion(expansions)
js_expansions = JavascriptIncludeTag.expansions
expansions.each do |key, values|
@@ -76,7 +76,6 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by javascript_include_tag to build the script path.
#
- # ==== Examples
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
@@ -114,38 +113,35 @@ module ActionView
# 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>
+ # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "xmlhr.js"
- # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # # => <script 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>
+ # # => <script src="/javascripts/common.javascript?1284139606"></script>
+ # # <script src="/elsewhere/cools.js?1423139606"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr"></script>
+ # # => <script src="http://www.example.com/xmlhr"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js"></script>
+ # # => <script src="http://www.example.com/xmlhr.js"></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
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
#
# 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/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>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
+ # # <script src="/javascripts/shop.js?1284139606"></script>
+ # # <script src="/javascripts/checkout.js?1284139606"></script>
#
# Note that your defaults of choice will be included first, so they will be available to all subsequently
# included files.
@@ -162,29 +158,27 @@ module ActionView
# <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
- #
# # 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>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
+ # # <script src="/javascripts/shop.js?1284139606"></script>
+ # # <script 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>
+ # # => <script src="/javascripts/all.js?1344139789"></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>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/cart.js?1289139157"></script>
+ # # <script src="/javascripts/checkout.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>
+ # # => <script src="/javascripts/shop.js?1299139816"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
#
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 2584b67548..57b0627225 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
@@ -17,7 +17,7 @@ module ActionView
def asset_tag(source, options)
# We force the :request protocol here to avoid a double-download bug in IE7 and IE8
- tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
+ tag("link", { "rel" => "stylesheet", "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
end
def custom_dir
@@ -38,9 +38,9 @@ module ActionView
# 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" />
+ # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" />
def register_stylesheet_expansion(expansions)
style_expansions = StylesheetIncludeTag.expansions
expansions.each do |key, values|
@@ -54,7 +54,6 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
- # ==== Examples
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
@@ -79,32 +78,31 @@ module ActionView
# to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
# apply to all media types.
#
- # ==== Examples
# stylesheet_link_tag "style" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style.css" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "http://www.example.com/style.css" # =>
- # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style", :media => "all" # =>
- # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="all" rel="stylesheet" />
#
# stylesheet_link_tag "style", :media => "print" # =>
- # <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="print" rel="stylesheet" />
#
# stylesheet_link_tag "random.styles", "/css/stylish" # =>
- # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />
+ # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
#
# You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
#
# stylesheet_link_tag :all # =>
- # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" />
#
# If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
#
@@ -113,26 +111,25 @@ module ActionView
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
+ # compressed by gzip (leading to faster transfers). Caching will only happen if +config.perform_caching+
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
#
- # ==== Examples
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is false =>
- # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is true =>
- # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false =>
- # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true =>
- # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" />
#
# The <tt>:recursive</tt> option is also available for caching:
#
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index 73824dc1f8..f9aa8d7cee 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -176,6 +176,7 @@ module ActionView
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
+ # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
def entry(record, options = {})
@xml.entry do
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
@@ -188,7 +189,9 @@ module ActionView
@xml.updated((options[:updated] || record.updated_at).xmlschema)
end
- @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
+ type = options.fetch(:type, 'text/html')
+
+ @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
yield AtomBuilder.new(@xml)
end
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 850dd5f448..33799d7d71 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -10,7 +10,6 @@ module ActionView
#
# See ActionController::Caching::Fragments for usage instructions.
#
- # ==== Examples
# If you want to cache a navigation menu, you can do following:
#
# <% cache do %>
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 278139cadb..397738dd98 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -13,7 +13,6 @@ module ActionView
# The capture method allows you to extract part of a template into a
# variable. You can then use this variable anywhere in your templates or layout.
#
- # ==== Examples
# The capture method can be used in ERB templates...
#
# <% @greeting = capture do %>
@@ -96,7 +95,7 @@ module ActionView
# Please login!
#
# <% content_for :script do %>
- # <script type="text/javascript">alert('You are not authorized to view this page!')</script>
+ # <script>alert('You are not authorized to view this page!')</script>
# <% end %>
#
# Then, in another view, you could to do something like this:
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index cb46f26d99..659aacf6d7 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -12,14 +12,14 @@ module ActionView
# elements. All of the select-type methods share a number of common options that are as follows:
#
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
- # would give birthday[month] instead of date[month] if passed to the <tt>select_month</tt> method.
+ # would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method.
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
# * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true,
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
- # of "date[month]".
+ # of \date[month].
module DateHelper
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
- # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs.
+ # Pass <tt>:include_seconds => true</tt> if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
#
# 0 <-> 29 secs # => less than a minute
@@ -29,14 +29,15 @@ module ActionView
# 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
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month
+ # 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months
# 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
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years
# 2 yrs <-> max time or date # => (same rules as 1 yr)
#
- # With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds:
+ # With <tt>:include_seconds => true</tt> and the difference < 1 minute 29 seconds:
# 0-4 secs # => less than 5 seconds
# 5-9 secs # => less than 10 seconds
# 10-19 secs # => less than 20 seconds
@@ -46,36 +47,44 @@ module ActionView
#
# ==== Examples
# from_time = Time.now
- # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
- # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
- # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
- # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
- # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
- # 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
- # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
- # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
+ # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
+ # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
+ # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, :include_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) # => 3 days
+ # distance_of_time_in_words(from_time, from_time + 45.seconds, :include_seconds => true) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time - 45.seconds, :include_seconds => true) # => less than a minute
+ # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
+ # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
+ # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
#
# to_time = Time.now + 6.years + 19.days
- # distance_of_time_in_words(from_time, to_time, true) # => about 6 years
- # distance_of_time_in_words(to_time, from_time, true) # => about 6 years
- # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
- #
- def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
+ # distance_of_time_in_words(from_time, to_time, :include_seconds => true) # => about 6 years
+ # distance_of_time_in_words(to_time, from_time, :include_seconds => true) # => about 6 years
+ # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
+ if include_seconds_or_options.is_a?(Hash)
+ options = include_seconds_or_options
+ else
+ ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " +
+ "as a part of options hash, not a boolean argument", caller
+ options[:include_seconds] ||= !!include_seconds_or_options
+ end
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
- distance_in_minutes = (((to_time - from_time).abs)/60).round
- distance_in_seconds = ((to_time - from_time).abs).round
+ from_time, to_time = to_time, from_time if from_time > to_time
+ distance_in_minutes = ((to_time - from_time)/60.0).round
+ distance_in_seconds = (to_time - from_time).round
I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
- locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
+ locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds]
case distance_in_seconds
when 0..4 then locale.t :less_than_x_seconds, :count => 5
@@ -86,26 +95,35 @@ module ActionView
else locale.t :x_minutes, :count => 1
end
- 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..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
+ when 2...45 then locale.t :x_minutes, :count => distance_in_minutes
+ when 45...90 then locale.t :about_x_hours, :count => 1
+ # 90 mins up to 24 hours
+ when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
+ # 24 hours up to 42 hours
+ when 1440...2520 then locale.t :x_days, :count => 1
+ # 42 hours up to 30 days
+ when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ # 30 days up to 60 days
+ when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ # 60 days up to 365 days
+ when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
- fyear = from_time.year
- fyear += 1 if from_time.month >= 3
- tyear = to_time.year
- tyear -= 1 if to_time.month < 3
- leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
- minute_offset_for_leap_year = leap_years * 1440
- # Discount the leap year days when calculating year distance.
- # e.g. if there are 20 leap year days between 2 dates having the same day
- # and month then the based on 365 days calculation
- # the distance in years will come out to over 80 years when in written
- # english it would read better as about 80 years.
- minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ if from_time.acts_like?(:time) && to_time.acts_like?(:time)
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # english it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ else
+ minutes_with_offset = distance_in_minutes
+ end
remainder = (minutes_with_offset % 525600)
distance_in_years = (minutes_with_offset / 525600)
if remainder < 131400
@@ -121,16 +139,15 @@ module ActionView
# Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
#
- # ==== Examples
- # time_ago_in_words(3.minutes.from_now) # => 3 minutes
- # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
- # time_ago_in_words(Time.now) # => less than a minute
+ # time_ago_in_words(3.minutes.from_now) # => 3 minutes
+ # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
+ # time_ago_in_words(Time.now) # => less than a minute
+ # time_ago_in_words(Time.now, :include_seconds => true) # => less than 5 seconds
#
# 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)
+ def time_ago_in_words(from_time, include_seconds_or_options = {})
+ distance_of_time_in_words(from_time, Time.now, include_seconds_or_options)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
@@ -177,7 +194,6 @@ module ActionView
#
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
#
- # ==== Examples
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
# date_select("article", "written_on")
#
@@ -233,7 +249,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
# time_select("article", "sunrise")
#
@@ -266,7 +281,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
# # attribute.
# datetime_select("article", "written_on")
@@ -305,7 +319,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# my_date_time = Time.now + 4.days
#
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today).
@@ -342,7 +355,6 @@ module ActionView
# select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours
# select_datetime(my_date_time, :prompt => true) # generic prompts for all
- #
def select_datetime(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_datetime
end
@@ -354,7 +366,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# my_date = Time.now + 6.days
#
# # Generates a date select that defaults to the date in my_date (six days after today).
@@ -383,7 +394,6 @@ module ActionView
# select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours
# select_date(my_date, :prompt => true) # generic prompts for all
- #
def select_date(date = Date.current, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_date
end
@@ -394,7 +404,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
#
# # Generates a time select that defaults to the time in my_time.
@@ -422,7 +431,6 @@ module ActionView
# select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
# select_time(my_time, :prompt => true) # generic prompts for all
- #
def select_time(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_time
end
@@ -431,7 +439,6 @@ module ActionView
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
#
- # ==== Examples
# my_time = Time.now + 16.minutes
#
# # Generates a select field for seconds that defaults to the seconds for the time in my_time.
@@ -447,7 +454,6 @@ module ActionView
# # Generates a select field for seconds with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_second(14, :prompt => 'Choose seconds')
- #
def select_second(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_second
end
@@ -457,7 +463,6 @@ module ActionView
# selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
- # ==== Examples
# my_time = Time.now + 6.hours
#
# # Generates a select field for minutes that defaults to the minutes for the time in my_time.
@@ -473,7 +478,6 @@ module ActionView
# # Generates a select field for minutes with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_minute(14, :prompt => 'Choose minutes')
- #
def select_minute(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_minute
end
@@ -482,7 +486,6 @@ module ActionView
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
#
- # ==== Examples
# my_time = Time.now + 6.hours
#
# # Generates a select field for hours that defaults to the hour for the time in my_time.
@@ -501,7 +504,6 @@ module ActionView
#
# # Generate a select field for hours in the AM/PM format
# select_hour(my_time, :ampm => true)
- #
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
end
@@ -511,7 +513,6 @@ module ActionView
# If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
#
- # ==== Examples
# my_date = Time.now + 2.days
#
# # Generates a select field for days that defaults to the day for the date in my_date.
@@ -530,7 +531,6 @@ module ActionView
# # Generates a select field for days with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_day(5, :prompt => 'Choose day')
- #
def select_day(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_day
end
@@ -545,7 +545,6 @@ module ActionView
# If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
#
- # ==== Examples
# # Generates a select field for months that defaults to the current month that
# # will use keys like "January", "March".
# select_month(Date.today)
@@ -577,7 +576,6 @@ module ActionView
# # Generates a select field for months with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_month(14, :prompt => 'Choose month')
- #
def select_month(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_month
end
@@ -588,7 +586,6 @@ module ActionView
# greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number.
# Override the field name using the <tt>:field_name</tt> option, 'year' by default.
#
- # ==== Examples
# # Generates a select field for years that defaults to the current year that
# # has ascending year values.
# select_year(Date.today, :start_year => 1992, :end_year => 2007)
@@ -608,14 +605,12 @@ module ActionView
# # Generates a select field for years with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_year(14, :prompt => 'Choose year')
- #
def select_year(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_year
end
# Returns an html time tag for the given date or time.
#
- # ==== Examples
# time_tag Date.today # =>
# <time datetime="2010-11-04">November 04, 2010</time>
# time_tag Time.now # =>
@@ -629,7 +624,6 @@ module ActionView
# <span>Right now</span>
# <% end %>
# # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time>
- #
def time_tag(date_or_time, *args, &block)
options = args.extract_options!
format = options.delete(:format) || :long
@@ -997,6 +991,8 @@ module ActionView
# Returns the separator for a given datetime component.
def separator(type)
+ return "" if @options[:use_hidden]
+
case type
when :year, :month, :day
@options[:"discard_#{type}"] ? "" : @options[:date_separator]
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index c0cc7d347c..878a8734a4 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -8,8 +8,6 @@ module ActionView
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
# Useful for inspecting an object at the time of rendering.
#
- # ==== Example
- #
# @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
# debug(@user)
# # =>
@@ -25,7 +23,6 @@ module ActionView
#
# new_record: true
# </pre>
-
def debug(object)
begin
Marshal::dump(object)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6219a7a924..6510610034 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -5,11 +5,13 @@ require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
require 'action_view/helpers/tags'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/array/extract_options'
require 'active_support/deprecation'
+require 'active_support/core_ext/string/inflections'
module ActionView
# = Action View Form Helpers
@@ -184,7 +186,7 @@ module ActionView
# First name: <%= f.text_field :first_name %>
# Last name : <%= f.text_field :last_name %>
# Biography : <%= text_area :person, :biography %>
- # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
+ # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
# <%= f.submit %>
# <% end %>
#
@@ -673,6 +675,19 @@ module ActionView
# <% end %>
# ...
# <% end %>
+ #
+ # When a collection is used you might want to know the index of each
+ # object into the array. For this purpose, the <tt>index</tt> method
+ # is available in the FormBuilder object.
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :projects do |project_fields| %>
+ # Project #<%= project_fields.index %>
+ # ...
+ # <% end %>
+ # ...
+ # <% end %>
def fields_for(record_name, record_object = nil, options = {}, &block)
builder = instantiate_builder(record_name, record_object, options)
output = capture(builder, &block)
@@ -885,11 +900,10 @@ module ActionView
# In that case it is preferable to either use +check_box_tag+ or to use
# hashes instead of arrays.
#
- # ==== Examples
# # Let's say that @post.validated? is 1:
# check_box("post", "validated")
# # => <input name="post[validated]" type="hidden" value="0" />
- # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
+ # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
#
# # Let's say that @puppy.gooddog is "no":
# check_box("puppy", "gooddog", {}, "yes", "no")
@@ -911,7 +925,6 @@ module ActionView
# To force the radio button to be checked pass <tt>:checked => true</tt> in the
# +options+ hash. You may pass HTML options there as well.
#
- # ==== Examples
# # Let's say that @post.category returns "rails":
# radio_button("post", "category", "rails")
# radio_button("post", "category", "java")
@@ -930,8 +943,6 @@ module ActionView
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
# some browsers.
#
- # ==== Examples
- #
# search_field(:user, :name)
# # => <input id="user_name" name="user[name]" type="search" />
# search_field(:user, :name, :autosave => false)
@@ -947,7 +958,6 @@ module ActionView
# # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
# search_field(:user, :name, :autosave => true, :onsearch => true)
# # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
- #
def search_field(object_name, method, options = {})
Tags::SearchField.new(object_name, method, self, options).render
end
@@ -980,6 +990,23 @@ module ActionView
Tags::DateField.new(object_name, method, self, options).render
end
+ # Returns a text_field of type "time".
+ #
+ # The default value is generated by trying to call +strftime+ with "%T.%L"
+ # on the objects's value. It is still possible to override that
+ # by passing the "value" option.
+ #
+ # === Options
+ # * Accepts same options as time_field_tag
+ #
+ # === Example
+ # time_field("task", "started_at")
+ # # => <input id="task_started_at" name="task[started_at]" type="time" />
+ #
+ def time_field(object_name, method, options = {})
+ Tags::TimeField.new(object_name, method, self, options).render
+ end
+
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
@@ -1026,9 +1053,14 @@ module ActionView
object_name = ActiveModel::Naming.param_key(object)
end
- builder = options[:builder] || ActionView::Base.default_form_builder
+ builder = options[:builder] || default_form_builder
builder.new(object_name, object, self, options)
end
+
+ def default_form_builder
+ builder = ActionView::Base.default_form_builder
+ builder.respond_to?(:constantize) ? builder.constantize : builder
+ end
end
class FormBuilder
@@ -1038,7 +1070,7 @@ module ActionView
attr_accessor :object_name, :object, :options
- attr_reader :multipart, :parent_builder
+ attr_reader :multipart, :parent_builder, :index
alias :multipart? :multipart
def multipart=(multipart)
@@ -1076,6 +1108,7 @@ module ActionView
end
end
@multipart = nil
+ @index = options[:index] || options[:child_index]
end
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
@@ -1107,12 +1140,14 @@ module ActionView
end
index = if options.has_key?(:index)
- "[#{options[:index]}]"
+ options[:index]
elsif defined?(@auto_index)
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
- "[#{@auto_index}]"
+ @auto_index
end
- record_name = "#{object_name}#{index}[#{record_name}]"
+
+ record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
+ fields_options[:child_index] = index
@template.fields_for(record_name, record_object, fields_options, &block)
end
@@ -1250,7 +1285,8 @@ module ActionView
explicit_child_index = options[:child_index]
output = ActiveSupport::SafeBuffer.new
association.each do |child|
- output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block)
+ options[:child_index] = nested_child_index(name) unless explicit_child_index
+ output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
end
output
elsif association
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index d61c2bbee2..eef426703d 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -264,7 +264,6 @@ module ActionView
# Finally, this method supports a <tt>:default</tt> option, which selects
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
#
- # Examples:
# time_zone_select( "user", "time_zone", nil, :include_blank => true)
#
# time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" )
@@ -288,38 +287,55 @@ module ActionView
#
# Examples (call, result):
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
- # <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
+ # # <option value="$">Dollar</option>
+ # # <option value="DKK">Kroner</option>
#
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
- # <option>VISA</option>\n<option selected="selected">MasterCard</option>
+ # # <option>VISA</option>
+ # # <option selected="selected">MasterCard</option>
#
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
- # <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
+ # # <option value="$20">Basic</option>
+ # # <option value="$40" selected="selected">Plus</option>
#
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
- # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
+ # # <option selected="selected">VISA</option>
+ # # <option>MasterCard</option>
+ # # <option selected="selected">Discover</option>
#
# You can optionally provide html attributes as the last element of the array.
#
# Examples:
# options_for_select([ "Denmark", ["USA", {:class => 'bold'}], "Sweden" ], ["USA", "Sweden"])
- # <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option>
+ # # <option value="Denmark">Denmark</option>
+ # # <option value="USA" class="bold" selected="selected">USA</option>
+ # # <option value="Sweden" selected="selected">Sweden</option>
#
# options_for_select([["Dollar", "$", {:class => "bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
- # <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option>
+ # # <option value="$" class="bold">Dollar</option>
+ # # <option value="DKK" onclick="alert('HI');">Kroner</option>
#
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
#
# Examples:
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced" disabled="disabled">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced" disabled="disabled">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
- # <option value="Free" selected="selected">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free" selected="selected">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def options_for_select(container, selected = nil)
@@ -444,8 +460,11 @@ module ActionView
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
# as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
- # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
+ #
+ # Options:
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
+ # * <tt>:divider</tt> - the divider for the options groups.
#
# Sample usage (Array):
# grouped_options = [
@@ -458,8 +477,8 @@ module ActionView
#
# Sample usage (Hash):
# grouped_options = {
- # 'North America' => [['United States','US'], 'Canada'],
- # 'Europe' => ['Denmark','Germany','France']
+ # 'North America' => [['United States','US'], 'Canada'],
+ # 'Europe' => ['Denmark','Germany','France']
# }
# grouped_options_for_select(grouped_options)
#
@@ -474,15 +493,50 @@ module ActionView
# <option value="Canada">Canada</option>
# </optgroup>
#
+ # Sample usage (divider):
+ # grouped_options = [
+ # [['United States','US'], 'Canada'],
+ # ['Denmark','Germany','France']
+ # ]
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ #
+ # Possible output:
+ # <optgroup label="---------">
+ # <option value="Denmark">Denmark</option>
+ # <option value="Germany">Germany</option>
+ # <option value="France">France</option>
+ # </optgroup>
+ # <optgroup label="---------">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
+ #
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
- def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
+ def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
+ if options.is_a?(Hash)
+ prompt = options[:prompt]
+ divider = options[:divider]
+ else
+ prompt = options
+ options = {}
+ ActiveSupport::Deprecation.warn "Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: #{prompt.inspect} }`."
+ end
+
body = "".html_safe
- body.safe_concat content_tag(:option, prompt, :value => "") if prompt
+
+ if prompt
+ body.safe_concat content_tag(:option, prompt_text(prompt), :value => "")
+ end
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
- grouped_options.each do |label, container|
+ grouped_options.each do |container|
+ if divider
+ label = divider
+ else
+ label, container = container
+ end
body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
end
@@ -698,6 +752,10 @@ module ActionView
def value_for_collection(item, value)
value.respond_to?(:call) ? value.call(item) : item.send(value)
end
+
+ def prompt_text(prompt)
+ prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ end
end
class FormBuilder
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index fb6dfff9b8..e65b4e3e95 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -45,7 +45,7 @@ module ActionView
# # => <form action="/posts" method="post">
#
# form_tag('/posts/1', :method => :put)
- # # => <form action="/posts/1" method="put">
+ # # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ...
#
# form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
@@ -101,7 +101,7 @@ module ActionView
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
# # <option>Green</option><option>Blue</option></select>
#
- # select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>".html_safe
+ # select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
# # <option>Out</option></select>
#
@@ -122,11 +122,11 @@ module ActionView
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
if options.delete(:include_blank)
- option_tags = "<option value=\"\"></option>".html_safe + option_tags
+ option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags)
end
if prompt = options.delete(:prompt)
- option_tags = "<option value=\"\">#{prompt}</option>".html_safe + option_tags
+ option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags)
end
content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
@@ -386,9 +386,6 @@ module ActionView
# drivers will provide a prompt with the question specified. If the user accepts,
# the form is processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
- # disabled version of the submit button when the form is submitted. This feature is
- # provided by the unobtrusive JavaScript driver.
# * Any other key creates standard HTML options for the tag.
#
# ==== Examples
@@ -401,14 +398,14 @@ module ActionView
# submit_tag "Save edits", :disabled => true
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
#
- # submit_tag "Complete sale", :disable_with => "Please wait..."
+ # submit_tag "Complete sale", :data => { :disable_with => "Please wait..." }
# # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" />
#
# submit_tag nil, :class => "form_submit"
# # => <input class="form_submit" name="commit" type="submit" />
#
- # submit_tag "Edit", :disable_with => "Editing...", :class => "edit_button"
- # # => <input class="edit_button" data-disable_with="Editing..." name="commit" type="submit" value="Edit" />
+ # submit_tag "Edit", :class => "edit_button"
+ # # => <input class="edit_button" name="commit" type="submit" value="Edit" />
#
# submit_tag "Save", :confirm => "Are you sure?"
# # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />
@@ -416,10 +413,6 @@ module ActionView
def submit_tag(value = "Save changes", options = {})
options = options.stringify_keys
- if disable_with = options.delete("disable_with")
- options["data-disable-with"] = disable_with
- end
-
if confirm = options.delete("confirm")
options["data-confirm"] = confirm
end
@@ -441,10 +434,6 @@ module ActionView
# processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If true, the user will not be able to
# use this input.
- # * <tt>:disable_with</tt> - Value of this parameter will be
- # used as the value for a disabled version of the submit
- # button when the form is submitted. This feature is provided
- # by the unobtrusive JavaScript driver.
# * Any other key creates standard HTML options for the tag.
#
# ==== Examples
@@ -458,18 +447,11 @@ module ActionView
# # <strong>Ask me!</strong>
# # </button>
#
- # button_tag "Checkout", :disable_with => "Please wait..."
- # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
- #
def button_tag(content_or_options = nil, options = nil, &block)
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
options ||= {}
options = options.stringify_keys
- if disable_with = options.delete("disable_with")
- options["data-disable-with"] = disable_with
- end
-
if confirm = options.delete("confirm")
options["data-confirm"] = confirm
end
@@ -502,6 +484,9 @@ module ActionView
#
# image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button")
# # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
+ #
+ # image_submit_tag("save.png", :confirm => "Are you sure?")
+ # # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
@@ -564,6 +549,17 @@ module ActionView
text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
end
+ # Creates a text field of type "time".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def time_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
+ end
+
# Creates a text field of type "url".
#
# ==== Options
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index d88f5babb9..cc20518b93 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -15,7 +15,6 @@ module ActionView
JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '&#x2029;'
-
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
@@ -37,7 +36,7 @@ module ActionView
# javascript_tag "alert('All is good')"
#
# Returns:
- # <script type="text/javascript">
+ # <script>
# //<![CDATA[
# alert('All is good')
# //]]>
@@ -46,7 +45,7 @@ module ActionView
# +html_options+ may be a hash of attributes for the <tt>\<script></tt>
# tag. Example:
# javascript_tag "alert('All is good')", :defer => 'defer'
- # # => <script defer="defer" type="text/javascript">alert('All is good')</script>
+ # # => <script defer="defer">alert('All is good')</script>
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
@@ -62,46 +61,12 @@ module ActionView
content_or_options_with_block
end
- content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
+ content_tag(:script, javascript_cdata_section(content), html_options)
end
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
-
- # Returns a button whose +onclick+ handler triggers the passed JavaScript.
- #
- # 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+.
- #
- # button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
- # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
- #
- 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 whose +onclick+ handler triggers the passed JavaScript.
- #
- # 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 +href+ attribute of the tag is set to "#" unless +html_options+ has one.
- #
- # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
- # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
- #
- def link_to_function(name, function, html_options={})
- onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
- href = html_options[:href] || '#'
-
- content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 485a9357be..dfc26acfad 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -28,17 +28,20 @@ module ActionView
end
end
- # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
- # in the +options+ hash.
+ # Formats a +number+ into a US phone number (e.g., (555)
+ # 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
#
- # * <tt>:area_code</tt> - Adds parentheses around the area code.
- # * <tt>:delimiter</tt> - Specifies the delimiter to use (defaults to "-").
- # * <tt>:extension</tt> - Specifies an extension to add to the end of the
- # generated number.
- # * <tt>:country_code</tt> - Sets the country code for the phone number.
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
+ # (defaults to "-").
+ # * <tt>:extension</tt> - Specifies an extension to add to the
+ # end of the generated number.
+ # * <tt>:country_code</tt> - Sets the country code for the phone
+ # number.
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
#
@@ -74,31 +77,38 @@ module ActionView
number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
end
- str = []
+ str = ''
str << "+#{country_code}#{delimiter}" unless country_code.blank?
str << number
str << " x #{extension}" unless extension.blank?
- ERB::Util.html_escape(str.join)
+ ERB::Util.html_escape(str)
end
- # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
- # in the +options+ hash.
+ # Formats a +number+ into a currency string (e.g., $13.65). You
+ # can customize the format in the +options+ hash.
#
# ==== Options
#
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
- # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
- # * <tt>:format</tt> - Sets the format for non-negative numbers (defaults to "%u%n").
- # Fields are <tt>%u</tt> for the currency, and <tt>%n</tt>
- # for the number.
- # * <tt>:negative_format</tt> - Sets the format for negative numbers (defaults to prepending
- # an hyphen to the formatted number given by <tt>:format</tt>).
- # Accepts the same fields than <tt>:format</tt>, except
- # <tt>%n</tt> is here the absolute value of the number.
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
+ # to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency
+ # (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units
+ # (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
+ # currency, and <tt>%n</tt> for the number.
+ # * <tt>:negative_format</tt> - Sets the format for negative
+ # numbers (defaults to prepending an hyphen to the formatted
+ # number given by <tt>:format</tt>). Accepts the same fields
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
+ # absolute value of the number.
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
#
@@ -137,34 +147,40 @@ module ActionView
begin
value = number_with_precision(number, options.merge(:raise => true))
- format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
+ format.gsub('%n', value).gsub('%u', unit).html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
- formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
+ formatted_number = format.gsub('%n', e.number).gsub('%u', unit)
e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
end
- # Formats a +number+ as a percentage string (e.g., 65%). You can customize the format in the +options+ hash.
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
+ # customize the format in the +options+ hash.
#
# ==== Options
#
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current
- # locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+,
- # the # of fractional digits (defaults to +false+).
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults
- # to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator
- # (defaults to +false+).
- # * <tt>:format</tt> - Specifies the format of the percentage string
- # The number field is <tt>%n</tt> (defaults to "%n%").
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
#
@@ -200,15 +216,20 @@ module ActionView
end
end
- # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
- # customize the format in the +options+ hash.
+ # Formats a +number+ with grouped thousands using +delimiter+
+ # (e.g., 12,324). You can customize the format in the +options+
+ # hash.
#
# ==== Options
#
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
#
@@ -233,26 +254,35 @@ module ActionView
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- parts.join(options[:separator]).html_safe
+ safe_join(parts, options[:separator])
end
- # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
- # of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+).
+ # Formats a +number+ with the specified level of
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
# You can customize the format in the +options+ hash.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+,
- # the # of fractional digits (defaults to +false+).
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults
- # to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator
- # (defaults to +false+).
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_with_precision(111.2345) # => 111.235
# number_with_precision(111.2345, :precision => 2) # => 111.23
# number_with_precision(13, :precision => 5) # => 13.00000
@@ -305,23 +335,37 @@ module ActionView
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
- # Formats the bytes in +number+ into a more understandable representation
- # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
- # reporting file sizes to users. You can customize the
- # format in the +options+ hash.
+ # Formats the bytes in +number+ into a more understandable
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
+ # method is useful for reporting file sizes to users. You can
+ # customize the format in the +options+ hash.
#
- # See <tt>number_to_human</tt> if you want to pretty-print a generic number.
+ # See <tt>number_to_human</tt> if you want to pretty-print a
+ # generic number.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary)
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
+ # prefix (defaults to :binary)
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
+ #
# ==== Examples
+ #
# number_to_human_size(123) # => 123 Bytes
# number_to_human_size(1234) # => 1.21 KB
# number_to_human_size(12345) # => 12.1 KB
@@ -332,8 +376,10 @@ module ActionView
# number_to_human_size(483989, :precision => 2) # => 470 KB
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
#
- # Non-significant zeros after the fractional separator are stripped out by default (set
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # 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"
def number_to_human_size(number, options = {})
@@ -371,33 +417,55 @@ module ActionView
DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
- # Pretty prints (formats and approximates) a number in a way it is more readable by humans
- # (eg.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that
- # can get very large (and too hard to read).
+ # Pretty prints (formats and approximates) a number in a way it
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
+ # Billion"). This is useful for numbers that can get very large
+ # (and too hard to read).
#
- # See <tt>number_to_human_size</tt> if you want to print a file size.
+ # See <tt>number_to_human_size</tt> if you want to print a file
+ # size.
#
- # You can also define you own unit-quantifier names if you want to use other decimal units
- # (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 milliliters", etc). You may define
- # a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc).
+ # You can also define you own unit-quantifier names if you want
+ # to use other decimal units (eg.: 1500 becomes "1.5
+ # kilometers", 0.150 becomes "150 milliliters", etc). You may
+ # define a wide range of unit quantifiers, even fractional ones
+ # (centi, deci, mili, etc).
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
- # * <tt>:units</tt> - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys:
- # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt>
- # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt>
- # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are:
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
- #
- # %u The quantifier (ex.: 'thousand')
- # %n The number
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
+ # string containing an i18n scope where to find this hash. It
+ # might have the following keys:
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
+ # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
+ # *<tt>:billion</tt>, <tt>:trillion</tt>,
+ # *<tt>:quadrillion</tt>
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # *<tt>:pico</tt>, <tt>:femto</tt>
+ # * <tt>:format</tt> - Sets the format of the output string
+ # (defaults to "%n %u"). The field types are:
+ # * %u - The quantifier (ex.: 'thousand')
+ # * %n - The number
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_to_human(123) # => "123"
# number_to_human(1234) # => "1.23 Thousand"
# number_to_human(12345) # => "12.3 Thousand"
@@ -414,8 +482,9 @@ module ActionView
# :separator => ',',
# :significant => false) # => "1,2 Million"
#
- # Unsignificant zeros after the decimal separator are stripped out by default (set
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # Non-significant 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"
#
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 1a15459406..9b35f076e5 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -94,10 +94,10 @@ module ActionView
# for each record.
def content_tag_for_single_record(tag_name, record, prefix, options, &block)
options = options ? options.dup : {}
- options.merge!(:class => "#{dom_class(record, prefix)} #{options[:class]}".rstrip, :id => dom_id(record, prefix))
+ options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip
+ options[:id] = dom_id(record, prefix)
- content = block.arity == 0 ? capture(&block) : capture(record, &block)
- content_tag(tag_name, content, options)
+ content_tag(tag_name, capture(record, &block), options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index 7768c8c151..a727b910e5 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -69,8 +69,6 @@ module ActionView
# html-scanner tokenizer and so its HTML parsing ability is limited by
# that of html-scanner.
#
- # ==== Examples
- #
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
#
@@ -85,7 +83,6 @@ module ActionView
# Strips all link tags from +text+ leaving just the link text.
#
- # ==== Examples
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
# # => Ruby on Rails
#
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 71be2955a8..498be596ad 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -14,7 +14,7 @@ module ActionView
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
defer reversed ismap seemless muted required
- autofocus novalidate formnovalidate open pubdate).to_set
+ autofocus novalidate formnovalidate open pubdate itemscope).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
PRE_CONTENT_STRINGS = {
@@ -103,19 +103,21 @@ module ActionView
# otherwise be recognized as markup. CDATA sections begin with the string
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
#
- # ==== Examples
# cdata_section("<hello world>")
# # => <![CDATA[<hello world>]]>
#
# cdata_section(File.read("hello_world.txt"))
# # => <![CDATA[<hello from a text file]]>
+ #
+ # cdata_section("hello]]>world")
+ # # => <![CDATA[hello]]]]><![CDATA[>world]]>
def cdata_section(content)
- "<![CDATA[#{content}]]>".html_safe
+ splitted = content.gsub(']]>', ']]]]><![CDATA[>')
+ "<![CDATA[#{splitted}]]>".html_safe
end
# Returns an escaped version of +html+ without affecting existing escaped entities.
#
- # ==== Examples
# escape_once("1 < 2 &amp; 3")
# # => "1 &lt; 2 &amp; 3"
#
@@ -152,8 +154,9 @@ module ActionView
def data_tag_option(key, value, escape)
key = "data-#{key.to_s.dasherize}"
- value = value.to_json if !value.is_a?(String) && !value.is_a?(Symbol)
-
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
+ value = value.to_json
+ end
tag_option(key, value, escape)
end
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
index 3cf762877f..5cd77c8ec3 100644
--- a/actionpack/lib/action_view/helpers/tags.rb
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -25,6 +25,7 @@ module ActionView
autoload :TelField
autoload :TextArea
autoload :TextField
+ autoload :TimeField
autoload :TimeSelect
autoload :TimeZoneSelect
autoload :UrlField
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
index 7c2f12d8e7..e077cd5b3c 100644
--- a/actionpack/lib/action_view/helpers/tags/base.rb
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -121,22 +121,26 @@ module ActionView
def select_content_tag(option_tags, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
+ options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
- if html_options["multiple"] && options.fetch(:include_hidden) { true }
+ if html_options["multiple"] && options.fetch(:include_hidden, true)
tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
else
select
end
end
+ def select_not_required?(html_options)
+ !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1
+ end
+
def add_options(option_tags, options, value = nil)
if options[:include_blank]
option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
end
if value.blank? && options[:prompt]
- prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
- option_tags = content_tag('option', prompt, :value => '') + "\n" + option_tags
+ option_tags = content_tag('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags
end
option_tags
end
diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb
index 1a4aebb936..9d17a1dde3 100644
--- a/actionpack/lib/action_view/helpers/tags/check_box.rb
+++ b/actionpack/lib/action_view/helpers/tags/check_box.rb
@@ -41,17 +41,15 @@ module ActionView
def checked?(value)
case value
when TrueClass, FalseClass
- value
+ value == !!@checked_value
when NilClass
false
- when Integer
- value != 0
when String
value == @checked_value
when Array
value.include?(@checked_value)
else
- value.to_i != 0
+ value.to_i == @checked_value.to_i
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
index 6a1479069f..4e33e79a36 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -49,7 +49,7 @@ module ActionView
accept = if current_value.respond_to?(:call)
current_value.call(item)
else
- Array(current_value).include?(value)
+ Array(current_value).map(&:to_s).include?(value.to_s)
end
if accept
diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb
index bb968e9f39..0e79609d52 100644
--- a/actionpack/lib/action_view/helpers/tags/date_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/date_field.rb
@@ -5,7 +5,6 @@ module ActionView
def render
options = @options.stringify_keys
options["value"] = @options.fetch("value") { value(object).try(:to_date) }
- options["size"] = nil
@options = options
super
end
diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb
index 56442e1c14..59f2ff71b4 100644
--- a/actionpack/lib/action_view/helpers/tags/file_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/file_field.rb
@@ -2,10 +2,6 @@ module ActionView
module Helpers
module Tags
class FileField < TextField #:nodoc:
- def render
- @options.update(:size => nil)
- super
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
index ea86596e0b..a8d13dc1b1 100644
--- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
@@ -2,10 +2,6 @@ module ActionView
module Helpers
module Tags
class HiddenField < TextField #:nodoc:
- def render
- @options.update(:size => nil)
- super
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb
index 1c8bf063ea..16135fcd5a 100644
--- a/actionpack/lib/action_view/helpers/tags/label.rb
+++ b/actionpack/lib/action_view/helpers/tags/label.rb
@@ -33,7 +33,7 @@ module ActionView
options["for"] = name_and_id["id"] unless options.key?("for")
if block_given?
- @template_object.label_tag(name_and_id["id"], options, &block)
+ content = @template_object.capture(&block)
else
content = if @content.blank?
@object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
@@ -55,9 +55,9 @@ module ActionView
end
content ||= @method_name.humanize
-
- label_tag(name_and_id["id"], content, options)
end
+
+ label_tag(name_and_id["id"], content, options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb
index e89fdbec46..9cd04434f0 100644
--- a/actionpack/lib/action_view/helpers/tags/number_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/number_field.rb
@@ -4,7 +4,6 @@ module ActionView
class NumberField < TextField #:nodoc:
def render
options = @options.stringify_keys
- options['size'] ||= nil
if range = options.delete("in") || options.delete("within")
options.update("min" => range.min, "max" => range.max)
diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb
new file mode 100644
index 0000000000..271dc00c54
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_field.rb
@@ -0,0 +1,14 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { value(object).try(:strftime, "%T.%L") }
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 3dc651501e..54a65ce5f8 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/filters'
+require 'active_support/core_ext/array/extract_options'
module ActionView
# = Action View Text Helpers
@@ -36,7 +37,6 @@ module ActionView
# do not operate as expected in an eRuby code block. If you absolutely must
# output text within a non-output code block (i.e., <% %>), you can use the concat method.
#
- # ==== Examples
# <%
# concat "hello"
# # is the equivalent of <%= "hello" %>
@@ -44,7 +44,7 @@ module ActionView
# if logged_in
# concat "Logged in!"
# else
- # concat link_to('login', :action => login)
+ # concat link_to('login', :action => :login)
# end
# # will either display "Logged in!" or a login link
# %>
@@ -66,8 +66,6 @@ module ActionView
# used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
# or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
#
- # ==== Examples
- #
# truncate("Once upon a time in a world far far away")
# # => "Once upon a time in a world..."
#
@@ -83,16 +81,14 @@ module ActionView
# truncate("<p>Once upon a time in a world far far away</p>")
# # => "<p>Once upon a time in a wo..."
def truncate(text, options = {})
- options.reverse_merge!(:length => 30)
- text.truncate(options.delete(:length), options) if text
+ text.truncate(options.fetch(:length, 30), options) if text
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
- # as a single-quoted string with \1 where the phrase is to be inserted (defaults to
+ # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
# '<mark>\1</mark>')
#
- # ==== Examples
# highlight('You searched for: rails', 'rails')
# # => You searched for: <mark>rails</mark>
#
@@ -104,23 +100,15 @@ module ActionView
#
# highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>')
# # => You searched for: <a href="search?q=rails">rails</a>
- #
- # You can still use <tt>highlight</tt> with the old API that accepts the
- # +highlighter+ as its optional third parameter:
- # highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a>
- def highlight(text, phrases, *args)
- options = args.extract_options!
- unless args.empty?
- options[:highlighter] = args[0] || '<mark>\1</mark>'
- end
- options.reverse_merge!(:highlighter => '<mark>\1</mark>')
-
- text = sanitize(text) unless options[:sanitize] == false
+ def highlight(text, phrases, options = {})
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
+
+ text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?![^<]*?>)/i, options[:highlighter])
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
end.html_safe
end
@@ -130,7 +118,6 @@ module ActionView
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string
# will be stripped in any case. If the +phrase+ isn't found, nil is returned.
#
- # ==== Examples
# excerpt('This is an example', 'an', :radius => 5)
# # => ...s is an exam...
#
@@ -145,39 +132,27 @@ module ActionView
#
# excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
# # => <chop> is also an example
- #
- # You can still use <tt>excerpt</tt> with the old API that accepts the
- # +radius+ as its optional third and the +ellipsis+ as its
- # optional forth parameter:
- # excerpt('This is an example', 'an', 5) # => ...s is an exam...
- # excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example
- def excerpt(text, phrase, *args)
+ def excerpt(text, phrase, options = {})
return unless text && phrase
-
- options = args.extract_options!
- unless args.empty?
- options[:radius] = args[0] || 100
- options[:omission] = args[1] || "..."
- end
- options.reverse_merge!(:radius => 100, :omission => "...")
+ radius = options.fetch(:radius, 100)
+ omission = options.fetch(:omission, "...")
phrase = Regexp.escape(phrase)
return unless found_pos = text =~ /(#{phrase})/i
- start_pos = [ found_pos - options[:radius], 0 ].max
- end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min
+ start_pos = [ found_pos - radius, 0 ].max
+ end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min
- prefix = start_pos > 0 ? options[:omission] : ""
- postfix = end_pos < text.length - 1 ? options[:omission] : ""
+ prefix = start_pos > 0 ? omission : ""
+ postfix = end_pos < text.length - 1 ? omission : ""
prefix + text[start_pos..end_pos].strip + postfix
end
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
# +plural+ is supplied, it will use that when count is > 1, otherwise
- # it will use the Inflector to determine the plural form
+ # it will use the Inflector to determine the plural form.
#
- # ==== Examples
# pluralize(1, 'person')
# # => 1 person
#
@@ -190,39 +165,35 @@ module ActionView
# pluralize(0, 'person')
# # => 0 people
def pluralize(count, singular, plural = nil)
- "#{count || 0} " + ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || singular.pluralize))
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
+ singular
+ else
+ plural || singular.pluralize
+ end
+
+ "#{count || 0} #{word}"
end
# Wraps the +text+ into lines no longer than +line_width+ width. This method
# breaks on the first whitespace character that does not exceed +line_width+
# (which is 80 by default).
#
- # ==== Examples
- #
# word_wrap('Once upon a time')
# # => Once upon a time
#
# word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
- # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined...
+ # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...
#
# word_wrap('Once upon a time', :line_width => 8)
- # # => Once upon\na time
+ # # => Once\nupon a\ntime
#
# word_wrap('Once upon a time', :line_width => 1)
# # => Once\nupon\na\ntime
- #
- # You can still use <tt>word_wrap</tt> with the old API that accepts the
- # +line_width+ as its optional second parameter:
- # word_wrap('Once upon a time', 8) # => Once upon\na time
- def word_wrap(text, *args)
- options = args.extract_options!
- unless args.blank?
- options[:line_width] = args[0] || 80
- end
- options.reverse_merge!(:line_width => 80)
+ def word_wrap(text, options = {})
+ line_width = options.fetch(:line_width, 80)
text.split("\n").collect do |line|
- line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
end * "\n"
end
@@ -237,6 +208,7 @@ module ActionView
#
# ==== Options
# * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
+ # * <tt>:wrapper_tag</tt> - String representing the tag wrapper, defaults to <tt>"p"</tt>
#
# ==== Examples
# my_text = "Here is some basic text...\n...with a line break."
@@ -254,17 +226,19 @@ module ActionView
#
# simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
# # => "<p><span>I'm allowed!</span> It's true.</p>"
- def simple_format(text, html_options={}, options={})
- text = '' if text.nil?
- text = text.dup
- start_tag = tag('p', html_options, true)
- text = sanitize(text) unless options[:sanitize] == false
- text = text.to_str
- text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
- text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
- text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
- text.insert 0, start_tag
- text.html_safe.safe_concat("</p>")
+ def simple_format(text, html_options = {}, options = {})
+ wrapper_tag = options.fetch(:wrapper_tag, :p)
+
+ text = sanitize(text) if options.fetch(:sanitize, true)
+ paragraphs = split_paragraphs(text)
+
+ if paragraphs.empty?
+ content_tag(wrapper_tag, nil, html_options)
+ else
+ paragraphs.map { |paragraph|
+ content_tag(wrapper_tag, paragraph, html_options, options[:sanitize])
+ }.join("\n\n").html_safe
+ end
end
# Creates a Cycle object whose _to_s_ method cycles through elements of an
@@ -276,7 +250,6 @@ module ActionView
# and passing the name of the cycle. The current cycle string can be obtained
# anytime using the current_cycle method.
#
- # ==== Examples
# # Alternate CSS classes for even and odd numbers...
# @items = [1,2,3,4]
# <table>
@@ -306,12 +279,9 @@ module ActionView
# </tr>
# <% end %>
def cycle(first_value, *values)
- if (values.last.instance_of? Hash)
- params = values.pop
- name = params[:name]
- else
- name = "default"
- end
+ options = values.extract_options!
+ name = options.fetch(:name, 'default')
+
values.unshift(first_value)
cycle = get_cycle(name)
@@ -325,7 +295,6 @@ module ActionView
# for complex table highlighting or any other design need which requires
# the current cycle string in more than one place.
#
- # ==== Example
# # Alternate background colors
# @items = [1,2,3,4]
# <% @items.each do |item| %>
@@ -341,7 +310,6 @@ module ActionView
# Resets a cycle so that it starts from the first element the next time
# it is called. Pass in +name+ to reset a named cycle.
#
- # ==== Example
# # Alternate CSS classes for even and odd numbers...
# @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
# <table>
@@ -412,6 +380,14 @@ module ActionView
@_cycles = Hash.new unless defined?(@_cycles)
@_cycles[name] = cycle_object
end
+
+ def split_paragraphs(text)
+ return [] if text.blank?
+
+ text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
+ t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index cc74eff53a..8171bea8ed 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -45,6 +45,7 @@ module ActionView
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
+ options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
if html_safe_translation_key?(key)
html_safe_options = options.dup
options.except(*I18n::RESERVED_KEYS).each do |name, value|
@@ -62,6 +63,9 @@ module ActionView
alias :t :translate
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
+ #
+ # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
+ # for more information.
def localize(*args)
I18n.localize(*args)
end
@@ -83,6 +87,21 @@ module ActionView
def html_safe_translation_key?(key)
key.to_s =~ /(\b|_|\.)html$/
end
+
+ def wrap_translate_defaults(defaults)
+ new_defaults = []
+ defaults = Array(defaults)
+ while key = defaults.shift
+ if key.is_a?(Symbol)
+ new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
+ break
+ else
+ new_defautls << key
+ end
+ end
+
+ new_defaults
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 4a641fada3..7e69547dab 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -108,7 +108,7 @@ module ActionView
options
when nil, Hash
options ||= {}
- options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
+ options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys)
super
when :back
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
@@ -303,7 +303,10 @@ module ActionView
#
# <%= button_to "Create", :action => "create", :remote => true, :form => { "data-type" => "json" } %>
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
- # # <div><input value="Create" type="submit" /></div>
+ # # <div>
+ # # <input value="Create" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
+ # # </div>
# # </form>"
#
#
@@ -312,17 +315,19 @@ module ActionView
# # => "<form method="post" action="/images/delete/1" class="button_to">
# # <div>
# # <input type="hidden" name="_method" value="delete" />
- # # <input data-confirm='Are you sure?' value="Delete" type="submit" />
+ # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </div>
# # </form>"
#
#
# <%= button_to('Destroy', 'http://www.example.com', :confirm => 'Are you sure?',
- # :method => "delete", :remote => true, :disable_with => 'loading...') %>
+ # :method => "delete", :remote => true) %>
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
# # <div>
# # <input name='_method' value='delete' type='hidden' />
- # # <input value='Destroy' type='submit' disable_with='loading...' data-confirm='Are you sure?' />
+ # # <input value='Destroy' type='submit' data-confirm='Are you sure?' />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </div>
# # </form>"
# #
@@ -481,7 +486,7 @@ module ActionView
# # => <a href="mailto:me@domain.com">me@domain.com</a>
#
# mail_to "me@domain.com", "My email", :encode => "javascript"
- # # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
+ # # => <script>eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
#
# mail_to "me@domain.com", "My email", :encode => "hex"
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
@@ -515,7 +520,7 @@ module ActionView
"document.write('#{html}');".each_byte do |c|
string << sprintf("%%%x", c)
end
- "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
+ "<script>eval(decodeURIComponent('#{string}'))</script>".html_safe
when "hex"
email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
sprintf("&#%d;", c)
@@ -611,11 +616,9 @@ module ActionView
html_options = html_options.stringify_keys
html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
- disable_with = html_options.delete("disable_with")
confirm = html_options.delete('confirm')
method = html_options.delete('method')
- html_options["data-disable-with"] = disable_with if disable_with
html_options["data-confirm"] = confirm if confirm
add_method_to_attributes!(html_options, method) if method
@@ -665,11 +668,11 @@ module ActionView
end
def token_tag(token=nil)
- if token == false || !protect_against_forgery?
- ''
- else
+ if token != false && protect_against_forgery?
token ||= form_authenticity_token
tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
+ else
+ ''
end
end
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index 52473cd222..72616b7463 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -14,12 +14,10 @@ module ActionView
protected
def extract_details(options)
- details = {}
- @lookup_context.registered_details.each do |key|
+ @lookup_context.registered_details.each_with_object({}) do |key, details|
next unless value = options[key]
details[key] = Array(value)
end
- details
end
def instrument(name, options={})
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 8b53867aea..9100545718 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -158,8 +158,8 @@ module ActionView
# Name: <%= user.name %>
# </div>
#
- # If a collection is given, the layout will be rendered once for each item in the collection. Just think
- # these two snippets have the same output:
+ # If a collection is given, the layout will be rendered once for each item in
+ # the collection. Just think these two snippets have the same output:
#
# <%# app/views/users/_user.html.erb %>
# Name: <%= user.name %>
@@ -184,7 +184,7 @@ module ActionView
# <%= render :partial => "user", :layout => "li_layout", :collection => users %>
# </ul>
#
- # Given two users whose names are Alice and Bob, these snippets return:
+ # Given two users whose names are Alice and Bob, these snippets return:
#
# <ul>
# <li>
@@ -195,6 +195,10 @@ module ActionView
# </li>
# </ul>
#
+ # The current object being rendered, as well as the object_counter, will be
+ # available as local variables inside the layout template under the same names
+ # as available in the partial.
+ #
# You can also apply a layout to a block within any template:
#
# <%# app/views/users/_chief.html.erb &>
@@ -279,26 +283,19 @@ module ActionView
return nil if @collection.blank?
if @options.key?(:spacer_template)
- spacer = find_template(@options[:spacer_template]).render(@view, @locals)
- end
-
- if layout = @options[:layout]
- layout = find_template(layout)
+ spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
end
result = @template ? collection_with_template : collection_without_template
-
- result.map!{|content| layout.render(@view, @locals) { content } } if layout
-
result.join(spacer).html_safe
end
def render_partial
- locals, view, block = @locals, @view, @block
+ view, locals, block = @view, @locals, @block
object, as = @object, @variable
if !block && (layout = @options[:layout])
- layout = find_template(layout)
+ layout = find_template(layout, @template_keys)
end
object ||= locals[as]
@@ -340,14 +337,15 @@ module ActionView
if @path
@variable, @variable_counter = retrieve_variable(@path)
+ @template_keys = retrieve_template_keys
else
paths.map! { |path| retrieve_variable(path).unshift(path) }
end
if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/
raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.")
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.")
end
self
@@ -361,56 +359,55 @@ module ActionView
end
def collection_from_object
- if @object.respond_to?(:to_ary)
- @object.to_ary
- end
+ @object.to_ary if @object.respond_to?(:to_ary)
end
def find_partial
if path = @path
- locals = @locals.keys
- locals << @variable
- locals << @variable_counter if @collection
- find_template(path, locals)
+ find_template(path, @template_keys)
end
end
- def find_template(path=@path, locals=@locals.keys)
+ def find_template(path, locals)
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
@lookup_context.find_template(path, prefixes, true, locals, @details)
end
def collection_with_template
- segments, locals, template = [], @locals, @template
+ view, locals, template = @view, @locals, @template
as, counter = @variable, @variable_counter
- locals[counter] = -1
+ if layout = @options[:layout]
+ layout = find_template(layout, @template_keys)
+ end
+
+ index = -1
+ @collection.map do |object|
+ locals[as] = object
+ locals[counter] = (index += 1)
- @collection.each do |object|
- locals[counter] += 1
- locals[as] = object
- segments << template.render(@view, locals)
+ content = template.render(view, locals)
+ content = layout.render(view, locals) { content } if layout
+ content
end
-
- segments
end
-
def collection_without_template
- segments, locals, collection_data = [], @locals, @collection_data
- index, template, cache = -1, nil, {}
- keys = @locals.keys
+ view, locals, collection_data = @view, @locals, @collection_data
+ cache = {}
+ keys = @locals.keys
- @collection.each_with_index do |object, i|
- path, *data = collection_data[i]
- template = (cache[path] ||= find_template(path, keys + data))
- locals[data[0]] = object
- locals[data[1]] = (index += 1)
- segments << template.render(@view, locals)
- end
+ index = -1
+ @collection.map do |object|
+ index += 1
+ path, as, counter = collection_data[index]
- @template = template
- segments
+ locals[as] = object
+ locals[counter] = index
+
+ template = (cache[path] ||= find_template(path, keys + [as, counter]))
+ template.render(view, locals)
+ end
end
def partial_path(object = @object)
@@ -450,8 +447,15 @@ module ActionView
end
end
+ def retrieve_template_keys
+ keys = @locals.keys
+ keys << @variable
+ keys << @variable_counter if @collection
+ keys
+ end
+
def retrieve_variable(path)
- variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
+ variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(:to_sym)
variable_counter = :"#{variable}_counter" if @collection
[variable, variable_counter]
end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 4e22bec6cc..41b14373a3 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -4,10 +4,12 @@ module ActionView #:nodoc:
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
autoload :Builder, 'action_view/template/handlers/builder'
+ autoload :Raw, 'action_view/template/handlers/raw'
def self.extended(base)
base.register_default_template_handler :erb, ERB.new
base.register_template_handler :builder, Builder.new
+ base.register_template_handler :raw, Raw.new
end
@@template_handlers = {}
diff --git a/actionpack/lib/action_view/template/handlers/raw.rb b/actionpack/lib/action_view/template/handlers/raw.rb
new file mode 100644
index 0000000000..0c0d1fffcb
--- /dev/null
+++ b/actionpack/lib/action_view/template/handlers/raw.rb
@@ -0,0 +1,11 @@
+module ActionView
+ module Template::Handlers
+ class Raw
+ def call(template)
+ escaped = template.source.gsub(':', '\:')
+
+ '%q:' + escaped + ':;'
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 8ea2e5bfe4..fa2038f78d 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -1,5 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
+require "active_support/core_ext/class/attribute_accessors"
require "action_view/template"
module ActionView
@@ -170,7 +171,9 @@ module ActionView
def extract_handler_and_format(path, default_formats)
pieces = File.basename(path).split(".")
pieces.shift
- handler = Template.handler_for_extension(pieces.pop)
+ extension = pieces.pop
+ ActiveSupport::Deprecation.warn "The file #{path} did not specify a template handler. The default is currently ERB, but will change to RAW in the future." unless extension
+ handler = Template.handler_for_extension(extension)
format = pieces.last && Mime[pieces.last]
[handler, format]
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index b1a5356ddd..ba06bcae51 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,11 +1,5 @@
require File.expand_path('../../../load_paths', __FILE__)
-lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
$:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
@@ -125,11 +119,11 @@ module ActiveSupport
# have been loaded.
setup_once do
SharedTestRoutes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
ActionDispatch::IntegrationTest.app.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
end
end
diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb
index 2fe7959f5a..275f25f597 100644
--- a/actionpack/test/activerecord/active_record_store_test.rb
+++ b/actionpack/test/activerecord/active_record_store_test.rb
@@ -254,12 +254,19 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
end
end
+ def test_session_store_with_all_domains
+ with_test_route_set(:domain => :all) do
+ get '/set_session_value'
+ assert_response :success
+ end
+ end
+
private
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
- match ':action', :to => 'active_record_store_test/test'
+ get ':action', :to => 'active_record_store_test/test'
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
index 8187eb72d5..409370104d 100644
--- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -16,7 +16,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
end
def render_with_has_many_through_association
- @developer = Developer.find(:first)
+ @developer = Developer.first
render :partial => @developer.topics
end
@@ -31,7 +31,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
end
def render_with_record
- @developer = Developer.find(:first)
+ @developer = Developer.first
render :partial => @developer
end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 01cafe1aca..9b0d4d0f4c 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -76,6 +76,11 @@ class ActionPackAssertionsController < ActionController::Base
render "test/hello_world", :layout => "layouts/standard"
end
+ def render_with_layout_and_partial
+ @variable_for_layout = nil
+ render "test/hello_world_with_partial", :layout => "layouts/standard"
+ end
+
def session_stuffing
session['xmas'] = 'turkey'
render :text => "ho ho ho"
@@ -162,7 +167,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_string_constraint
with_routing do |set|
set.draw do
- match "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
+ get "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
end
end
end
@@ -170,15 +175,18 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_assert_redirect_to_named_route_failure
with_routing do |set|
set.draw do
- match 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
- match 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
- match ':controller/:action'
+ get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
+ get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
+ get ':controller/:action'
end
process :redirect_to_named_route
assert_raise(ActiveSupport::TestCase::Assertion) do
assert_redirected_to 'http://test.host/route_two'
end
assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to %r(^http://test.host/route_two)
+ end
+ assert_raise(ActiveSupport::TestCase::Assertion) do
assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
end
assert_raise(ActiveSupport::TestCase::Assertion) do
@@ -192,8 +200,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
- match 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
- match ':controller/:action'
+ get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
+ get ':controller/:action'
end
process :redirect_to_index
# redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}>
@@ -206,12 +214,13 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
- match '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
- match ':controller/:action'
+ get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
+ get ':controller/:action'
end
process :redirect_to_top_level_named_route
# assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
assert_redirected_to "/action_pack_assertions/foo"
+ assert_redirected_to %r(/action_pack_assertions/foo)
end
end
@@ -221,8 +230,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
# this controller exists in the admin namespace as well which is the only difference from previous test
- match '/user/:id', :to => 'user#index', :as => :top_level
- match ':controller/:action'
+ get '/user/:id', :to => 'user#index', :as => :top_level
+ get ':controller/:action'
end
process :redirect_to_top_level_named_route
# assert_redirected_to top_level_url('foo') would pass because of exact match early return
@@ -469,11 +478,43 @@ class AssertTemplateTest < ActionController::TestCase
end
end
+ def test_fails_expecting_no_layout
+ get :render_with_layout
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :layout => nil
+ end
+ end
+
def test_passes_with_correct_layout
get :render_with_layout
assert_template :layout => "layouts/standard"
end
+ def test_passes_with_layout_and_partial
+ get :render_with_layout_and_partial
+ assert_template :layout => "layouts/standard"
+ end
+
+ def test_passed_with_no_layout
+ get :hello_world
+ assert_template :layout => nil
+ end
+
+ def test_passed_with_no_layout_false
+ get :hello_world
+ assert_template :layout => false
+ end
+
+ def test_passes_with_correct_layout_without_layouts_prefix
+ get :render_with_layout
+ assert_template :layout => "standard"
+ end
+
+ def test_passes_with_correct_layout_symbol
+ get :render_with_layout
+ assert_template :layout => :standard
+ end
+
def test_assert_template_reset_between_requests
get :hello_world
assert_template 'test/hello_world'
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 2d4083252e..b9513ccff4 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -45,7 +45,7 @@ class DefaultUrlOptionsController < ActionController::Base
render :inline => "<%= #{params[:route]} %>"
end
- def default_url_options(options = nil)
+ def default_url_options
{ :host => 'www.override.com', :action => 'new', :locale => 'en' }
end
end
@@ -158,7 +158,7 @@ class UrlOptionsTest < ActionController::TestCase
def test_url_for_query_params_included
rs = ActionDispatch::Routing::RouteSet.new
rs.draw do
- match 'home' => 'pages#home'
+ get 'home' => 'pages#home'
end
options = {
@@ -174,8 +174,8 @@ class UrlOptionsTest < ActionController::TestCase
def test_url_options_override
with_routing do |set|
set.draw do
- match 'from_view', :to => 'url_options#from_view', :as => :from_view
- match ':controller/:action'
+ get 'from_view', :to => 'url_options#from_view', :as => :from_view
+ get ':controller/:action'
end
get :from_view, :route => "from_view_url"
@@ -189,7 +189,7 @@ class UrlOptionsTest < ActionController::TestCase
def test_url_helpers_does_not_become_actions
with_routing do |set|
set.draw do
- match "account/overview"
+ get "account/overview"
end
assert !@controller.class.action_methods.include?("account_overview_path")
@@ -208,8 +208,8 @@ class DefaultUrlOptionsTest < ActionController::TestCase
def test_default_url_options_override
with_routing do |set|
set.draw do
- match 'from_view', :to => 'default_url_options#from_view', :as => :from_view
- match ':controller/:action'
+ get 'from_view', :to => 'default_url_options#from_view', :as => :from_view
+ get ':controller/:action'
end
get :from_view, :route => "from_view_url"
@@ -226,7 +226,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase
scope("/:locale") do
resources :descriptions
end
- match ':controller/:action'
+ get ':controller/:action'
end
get :from_view, :route => "description_path(1)"
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index a42c68a628..9efe328d62 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -102,8 +102,8 @@ class PageCachingTest < ActionController::TestCase
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
with_routing do |set|
set.draw do
- match 'posts.:format', :to => 'posts#index', :as => :formatted_posts
- match '/', :to => 'posts#index', :as => :main
+ get 'posts.:format', :to => 'posts#index', :as => :formatted_posts
+ get '/', :to => 'posts#index', :as => :main
end
@params[:format] = 'rss'
assert_equal '/posts.rss', @routes.url_for(@params)
@@ -236,6 +236,7 @@ class ActionCachingTestController < CachingController
caches_action :with_layout
caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } }
caches_action :layout_false, :layout => false
+ caches_action :with_layout_proc_param, :layout => Proc.new { |c| c.params[:layout] }
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
caches_action :streaming
@@ -282,6 +283,7 @@ class ActionCachingTestController < CachingController
alias_method :edit, :index
alias_method :destroy, :index
alias_method :layout_false, :with_layout
+ alias_method :with_layout_proc_param, :with_layout
def expire
expire_action :controller => 'action_caching_test', :action => 'index'
@@ -403,11 +405,40 @@ class ActionCacheTest < ActionController::TestCase
get :layout_false
assert_response :success
assert_not_equal cached_time, @response.body
-
body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false'))
assert_equal cached_time, body
end
+ def test_action_cache_with_layout_and_layout_cache_false_via_proc
+ get :with_layout_proc_param, :layout => false
+ assert_response :success
+ cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
+ reset!
+
+ get :with_layout_proc_param, :layout => false
+ assert_response :success
+ assert_not_equal cached_time, @response.body
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
+ assert_equal cached_time, body
+ end
+
+ def test_action_cache_with_layout_and_layout_cache_true_via_proc
+ get :with_layout_proc_param, :layout => true
+ assert_response :success
+ cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
+ reset!
+
+ get :with_layout_proc_param, :layout => true
+ assert_response :success
+ assert_not_equal cached_time, @response.body
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
+ assert_equal @response.body, body
+ end
+
def test_action_cache_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json'
get :index
@@ -560,7 +591,7 @@ class ActionCacheTest < ActionController::TestCase
def test_xml_version_of_resource_is_treated_as_different_cache
with_routing do |set|
set.draw do
- match ':controller(/:action(.:format))'
+ get ':controller(/:action(.:format))'
end
get :index, :format => 'xml'
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 65c853f6eb..ef7fbca675 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -510,6 +510,13 @@ class FilterTest < ActionController::TestCase
end
end
+ def test_sweeper_should_not_ignore_no_method_error
+ sweeper = ActionController::Caching::Sweeper.send(:new)
+ assert_raise NoMethodError do
+ sweeper.send_not_defined
+ end
+ end
+
def test_sweeper_should_not_block_rendering
response = test_process(SweeperTestController)
assert_equal 'hello world', response.body
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 37ccc7a4a5..e4b34125ad 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -277,7 +277,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match ':action', :to => FlashIntegrationTest::TestController
+ get ':action', :to => FlashIntegrationTest::TestController
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 7feeda25b3..5b423c8151 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -26,6 +26,14 @@ class ForceSSLExceptAction < ForceSSLController
force_ssl :except => :banana
end
+class ForceSSLIfCondition < ForceSSLController
+ force_ssl :if => :use_force_ssl?
+
+ def use_force_ssl?
+ action_name == 'cheeseburger'
+ end
+end
+
class ForceSSLFlash < ForceSSLController
force_ssl :except => [:banana, :set_flash, :use_flash]
@@ -109,6 +117,21 @@ class ForceSSLExceptActionTest < ActionController::TestCase
end
end
+class ForceSSLIfConditionTest < ActionController::TestCase
+ tests ForceSSLIfCondition
+
+ 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_if_condition/cheeseburger", redirect_to_url
+ end
+end
+
class ForceSSLFlashTest < ActionController::TestCase
tests ForceSSLFlash
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 44f033119d..fb41dcb33a 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -405,6 +405,15 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
end
+ def test_request_with_bad_format
+ with_test_route_set do
+ xhr :get, '/get.php'
+ assert_equal 406, status
+ assert_response 406
+ assert_response :not_acceptable
+ end
+ end
+
def test_get_with_query_string
with_test_route_set do
get '/get_with_params?foo=bar'
@@ -466,7 +475,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
set.draw do
- match ':action', :to => controller
+ match ':action', :to => controller, :via => [:get, :post]
get 'get/:action', :to => controller
end
@@ -530,10 +539,10 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
end
routes.draw do
- match '', :to => 'application_integration_test/test#index', :as => :empty_string
+ get '', :to => 'application_integration_test/test#index', :as => :empty_string
- match 'foo', :to => 'application_integration_test/test#index', :as => :foo
- match 'bar', :to => 'application_integration_test/test#index', :as => :bar
+ get 'foo', :to => 'application_integration_test/test#index', :as => :foo
+ get 'bar', :to => 'application_integration_test/test#index', :as => :bar
end
def app
@@ -606,3 +615,83 @@ class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
assert_equal '[FILTERED]', request.filtered_env['rack.request.form_vars']
end
end
+
+class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def index
+ render :text => "foo#index"
+ end
+
+ def show
+ render :text => "foo#show"
+ end
+
+ def edit
+ render :text => "foo#show"
+ end
+ end
+
+ class BarController < ActionController::Base
+ def default_url_options
+ { :host => "bar.com" }
+ end
+
+ def index
+ render :text => "foo#index"
+ end
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def app
+ self.class
+ end
+
+ routes.draw do
+ default_url_options :host => "foo.com"
+
+ scope :module => "url_options_integration_test" do
+ get "/foo" => "foo#index", :as => :foos
+ get "/foo/:id" => "foo#show", :as => :foo
+ get "/foo/:id/edit" => "foo#edit", :as => :edit_foo
+ get "/bar" => "bar#index", :as => :bars
+ end
+ end
+
+ test "session uses default url options from routes" do
+ assert_equal "http://foo.com/foo", foos_url
+ end
+
+ test "current host overrides default url options from routes" do
+ get "/foo"
+ assert_response :success
+ assert_equal "http://www.example.com/foo", foos_url
+ end
+
+ test "controller can override default url options from request" do
+ get "/bar"
+ assert_response :success
+ assert_equal "http://bar.com/foo", foos_url
+ end
+
+ test "test can override default url options" do
+ default_url_options[:host] = "foobar.com"
+ assert_equal "http://foobar.com/foo", foos_url
+
+ get "/bar"
+ assert_response :success
+ assert_equal "http://foobar.com/foo", foos_url
+ end
+
+ test "current request path parameters are recalled" do
+ get "/foo/1"
+ assert_response :success
+ assert_equal "/foo/1/edit", url_for(:action => 'edit', :only_path => true)
+ end
+end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 0e4099ddc6..bdcd5561a8 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -207,8 +207,9 @@ class RespondToControllerTest < ActionController::TestCase
get :html_or_xml
assert_equal 'HTML', @response.body
- get :just_xml
- assert_response 406
+ assert_raises(ActionController::UnknownFormat) do
+ get :just_xml
+ end
end
def test_all
@@ -239,8 +240,10 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal 'HTML', @response.body
@request.accept = "text/javascript, text/html"
- xhr :get, :just_xml
- assert_response 406
+
+ assert_raises(ActionController::UnknownFormat) do
+ xhr :get, :just_xml
+ end
end
def test_json_or_yaml_with_leading_star_star
@@ -495,9 +498,9 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_invalid_format
- get :using_defaults, :format => "invalidformat"
- assert_equal " ", @response.body
- assert_equal "text/html", @response.content_type
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_defaults, :format => "invalidformat"
+ end
end
end
@@ -701,12 +704,14 @@ class RespondWithControllerTest < ActionController::TestCase
def test_not_acceptable
@request.accept = "application/xml"
- get :using_resource_with_block
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_block
+ end
@request.accept = "text/javascript"
- get :using_resource_with_overwrite_block
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_overwrite_block
+ end
end
def test_using_resource_for_post_with_html_redirects_on_success
@@ -984,8 +989,9 @@ class RespondWithControllerTest < ActionController::TestCase
def test_clear_respond_to
@controller = InheritedRespondWithController.new
@request.accept = "text/html"
- get :index
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :index
+ end
end
def test_first_in_respond_to_has_higher_priority
@@ -1118,7 +1124,7 @@ class RespondWithControllerTest < ActionController::TestCase
resources :quiz_stores do
resources :customers
end
- match ":controller/:action"
+ get ":controller/:action"
end
yield
end
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index c424dcbd8d..5bcd79ebec 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -37,6 +37,36 @@ module BareMetalTest
def index
head :not_found
end
+
+ def continue
+ self.content_type = "text/html"
+ head 100
+ end
+
+ def switching_protocols
+ self.content_type = "text/html"
+ head 101
+ end
+
+ def processing
+ self.content_type = "text/html"
+ head 102
+ end
+
+ def no_content
+ self.content_type = "text/html"
+ head 204
+ end
+
+ def reset_content
+ self.content_type = "text/html"
+ head 205
+ end
+
+ def not_modified
+ self.content_type = "text/html"
+ head 304
+ end
end
class HeadTest < ActiveSupport::TestCase
@@ -44,6 +74,42 @@ module BareMetalTest
status = HeadController.action(:index).call(Rack::MockRequest.env_for("/")).first
assert_equal 404, status
end
+
+ test "head :continue (100) does not return a content-type header" do
+ headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :continue (101) does not return a content-type header" do
+ headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :processing (102) does not return a content-type header" do
+ headers = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :no_content (204) does not return a content-type header" do
+ headers = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :reset_content (205) does not return a content-type header" do
+ headers = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :not_modified (304) does not return a content-type header" do
+ headers = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
end
class BareControllerTest < ActionController::TestCase
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 4b70031c90..9b57641e75 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -43,7 +43,7 @@ module ContentType
test "default response is HTML and UTF8" do
with_routing do |set|
set.draw do
- match ':controller', :action => 'index'
+ get ':controller', :action => 'index'
end
get "/content_type/base"
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index 61ea68e3f7..bfca8c5c24 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -73,13 +73,13 @@ module RenderStreaming
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_body "d\r\n<body class=\"\r\n37\r\n\"><script>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_body "37\r\n\"><script>window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
assert_streaming!
end
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index ade204c387..00c7df2af8 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -164,7 +164,7 @@ module RenderTemplate
test "rendering with implicit layout" do
with_routing do |set|
- set.draw { match ':controller', :action => :index }
+ set.draw { get ':controller', :action => :index }
get "/render_template/with_layout"
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 60468bf5c7..cc7f12ac6d 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -57,7 +57,7 @@ module Render
test "render with blank" do
with_routing do |set|
set.draw do
- match ":controller", :action => 'index'
+ get ":controller", :action => 'index'
end
get "/render/blank_render"
@@ -70,7 +70,7 @@ module Render
test "rendering more than once raises an exception" do
with_routing do |set|
set.draw do
- match ":controller", :action => 'index'
+ get ":controller", :action => 'index'
end
assert_raises(AbstractController::DoubleRenderError) do
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 06d500cca7..f8d02e8b6c 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -65,9 +65,9 @@ module RenderText
class RenderTextTest < Rack::TestCase
describe "Rendering text using render :text"
- test "rendering text from a action with default options renders the text with the layout" do
+ test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { match ':controller', :action => 'index' }
+ set.draw { get ':controller', :action => 'index' }
get "/render_text/simple"
assert_body "hello david"
@@ -75,9 +75,9 @@ module RenderText
end
end
- test "rendering text from a action with default options renders the text without the layout" do
+ test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { match ':controller', :action => 'index' }
+ set.draw { get ':controller', :action => 'index' }
get "/render_text/with_layout"
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index c4d2614200..fa1608b9df 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -174,7 +174,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_accessible_wrapped_keys_from_matching_model
User.expects(:respond_to?).with(:accessible_attributes).returns(true)
- User.expects(:accessible_attributes).twice.returns(["username"])
+ User.expects(:accessible_attributes).with(:default).twice.returns(["username"])
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
@@ -186,7 +186,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_accessible_wrapped_keys_from_specified_model
with_default_wrapper_options do
Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
- Person.expects(:accessible_attributes).twice.returns(["username"])
+ Person.expects(:accessible_attributes).with(:default).twice.returns(["username"])
UsersController.wrap_parameters Person
@@ -195,6 +195,19 @@ class ParamsWrapperTest < ActionController::TestCase
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
+
+ def test_accessible_wrapped_keys_with_role_from_specified_model
+ with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
+ Person.expects(:accessible_attributes).with(:admin).twice.returns(["username"])
+
+ UsersController.wrap_parameters Person, :as => :admin
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
def test_not_wrapping_abstract_model
User.expects(:respond_to?).with(:accessible_attributes).returns(false)
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 6dab42d75d..4331333b98 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -262,7 +262,7 @@ class RedirectTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :workshops
- match ':controller/:action'
+ get ':controller/:action'
end
get :redirect_to_existing_record
@@ -296,7 +296,7 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_with_block_and_accepted_options
with_routing do |set|
set.draw do
- match ':controller/:action'
+ get ':controller/:action'
end
get :redirect_to_with_block_and_options
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index 75fed8e933..7c0a6bd67e 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -102,7 +102,7 @@ class RenderJsonTest < ActionController::TestCase
def test_render_json_with_callback
get :render_json_hello_world_with_callback
assert_equal 'alert({"hello":"world"})', @response.body
- assert_equal 'application/json', @response.content_type
+ assert_equal 'text/javascript', @response.content_type
end
def test_render_json_with_custom_content_type
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index fce13d096c..3d58c02338 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -102,12 +102,12 @@ class TestController < ActionController::Base
end
def conditional_hello_with_expires_in_with_public_with_more_keys
- expires_in 1.minute, :public => true, 'max-stale' => 5.hours
+ expires_in 1.minute, :public => true, 's-maxage' => 5.hours
render :action => 'hello_world'
end
def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
- expires_in 1.minute, :public => true, :private => nil, 'max-stale' => 5.hours
+ expires_in 1.minute, :public => true, :private => nil, 's-maxage' => 5.hours
render :action => 'hello_world'
end
@@ -505,6 +505,14 @@ class TestController < ActionController::Base
render :text => "hello world!"
end
+ def head_created
+ head :created
+ end
+
+ def head_created_with_application_json_content_type
+ head :created, :content_type => "application/json"
+ end
+
def head_with_location_header
head :location => "/foo"
end
@@ -1177,6 +1185,19 @@ class RenderTest < ActionController::TestCase
assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
end
+ def test_head_created
+ post :head_created
+ assert_blank @response.body
+ assert_response :created
+ end
+
+ def test_head_created_with_application_json_content_type
+ post :head_created_with_application_json_content_type
+ assert_blank @response.body
+ assert_equal "application/json", @response.content_type
+ assert_response :created
+ end
+
def test_head_with_location_header
get :head_with_location_header
assert_blank @response.body
@@ -1188,7 +1209,7 @@ class RenderTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- match ':controller/:action'
+ get ':controller/:action'
end
get :head_with_location_object
@@ -1478,12 +1499,12 @@ class ExpiresInRenderTest < ActionController::TestCase
def test_expires_in_header_with_additional_headers
get :conditional_hello_with_expires_in_with_public_with_more_keys
- assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
end
def test_expires_in_old_syntax
get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
- assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
end
def test_expires_now
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index 8b4f2f5349..4f280c4bec 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -72,7 +72,7 @@ class RenderXmlTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- match ':controller/:action'
+ get ':controller/:action'
end
get :render_with_object_location
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 9c51db135b..48e2d6491e 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -60,11 +60,6 @@ class RescueController < ActionController::Base
render :text => exception.message
end
- # This is a Dispatcher exception and should be in ApplicationController.
- rescue_from ActionController::RoutingError do
- render :text => 'no way'
- end
-
rescue_from ActionView::TemplateError do
render :text => 'action_view templater error'
end
@@ -343,9 +338,9 @@ class RescueTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match 'foo', :to => ::RescueTest::TestController.action(:foo)
- match 'invalid', :to => ::RescueTest::TestController.action(:invalid)
- match 'b00m', :to => ::RescueTest::TestController.action(:b00m)
+ get 'foo', :to => ::RescueTest::TestController.action(:foo)
+ get 'invalid', :to => ::RescueTest::TestController.action(:invalid)
+ get 'b00m', :to => ::RescueTest::TestController.action(:b00m)
end
yield
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 3c0a5d36ca..9fc875014c 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -680,7 +680,7 @@ class ResourcesTest < ActionController::TestCase
scope '/threads/:thread_id' do
resources :messages, :as => 'thread_messages' do
get :search, :on => :collection
- match :preview, :on => :new
+ get :preview, :on => :new
end
end
end
@@ -698,7 +698,7 @@ class ResourcesTest < ActionController::TestCase
scope '/admin' do
resource :account, :as => :admin_account do
get :login, :on => :member
- match :preview, :on => :new
+ get :preview, :on => :new
end
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 272a7da8c5..cd91064ab8 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -17,7 +17,7 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase
def setup
@set = ActionDispatch::Routing::RouteSet.new
@set.draw do
- match ':controller/:action/:variable/*additional'
+ get ':controller/:action/:variable/*additional'
end
safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ])
@@ -85,7 +85,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_symbols_with_dashes
rs.draw do
- match '/:artist/:song-omg', :to => lambda { |env|
+ get '/:artist/:song-omg', :to => lambda { |env|
resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
[200, {}, [resp]]
}
@@ -97,7 +97,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_id_with_dash
rs.draw do
- match '/journey/:id', :to => lambda { |env|
+ get '/journey/:id', :to => lambda { |env|
resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
[200, {}, [resp]]
}
@@ -109,7 +109,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_dash_with_custom_regexp
rs.draw do
- match '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env|
+ get '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env|
resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
[200, {}, [resp]]
}
@@ -122,7 +122,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_pre_dash
rs.draw do
- match '/:artist/omg-:song', :to => lambda { |env|
+ get '/:artist/omg-:song', :to => lambda { |env|
resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
[200, {}, [resp]]
}
@@ -134,7 +134,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_pre_dash_with_custom_regexp
rs.draw do
- match '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env|
+ get '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env|
resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
[200, {}, [resp]]
}
@@ -147,7 +147,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_star_paths_are_greedy
rs.draw do
- match "/*path", :to => lambda { |env|
+ get "/*path", :to => lambda { |env|
x = env["action_dispatch.request.path_parameters"][:path]
[200, {}, [x]]
}, :format => false
@@ -159,7 +159,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_star_paths_are_greedy_but_not_too_much
rs.draw do
- match "/*path", :to => lambda { |env|
+ get "/*path", :to => lambda { |env|
x = JSON.dump env["action_dispatch.request.path_parameters"]
[200, {}, [x]]
}
@@ -172,7 +172,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_optional_star_paths_are_greedy
rs.draw do
- match "/(*filters)", :to => lambda { |env|
+ get "/(*filters)", :to => lambda { |env|
x = env["action_dispatch.request.path_parameters"][:filters]
[200, {}, [x]]
}, :format => false
@@ -184,7 +184,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_optional_star_paths_are_greedy_but_not_too_much
rs.draw do
- match "/(*filters)", :to => lambda { |env|
+ get "/(*filters)", :to => lambda { |env|
x = JSON.dump env["action_dispatch.request.path_parameters"]
[200, {}, [x]]
}
@@ -198,11 +198,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_regexp_precidence
@rs.draw do
- match '/whois/:domain', :constraints => {
+ get '/whois/:domain', :constraints => {
:domain => /\w+\.[\w\.]+/ },
:to => lambda { |env| [200, {}, %w{regexp}] }
- match '/whois/:id', :to => lambda { |env| [200, {}, %w{id}] }
+ get '/whois/:id', :to => lambda { |env| [200, {}, %w{id}] }
end
assert_equal 'regexp', get(URI('http://example.org/whois/example.org'))
@@ -217,9 +217,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
}
@rs.draw do
- match '/', :constraints => subdomain.new,
+ get '/', :constraints => subdomain.new,
:to => lambda { |env| [200, {}, %w{default}] }
- match '/', :constraints => { :subdomain => 'clients' },
+ get '/', :constraints => { :subdomain => 'clients' },
:to => lambda { |env| [200, {}, %w{clients}] }
end
@@ -229,11 +229,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_lambda_constraints
@rs.draw do
- match '/', :constraints => lambda { |req|
+ get '/', :constraints => lambda { |req|
req.subdomain.present? and req.subdomain != "clients" },
:to => lambda { |env| [200, {}, %w{default}] }
- match '/', :constraints => lambda { |req|
+ get '/', :constraints => lambda { |req|
req.subdomain.present? && req.subdomain == "clients" },
:to => lambda { |env| [200, {}, %w{clients}] }
end
@@ -271,7 +271,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_default_setup
- @rs.draw { match '/:controller(/:action(/:id))' }
+ @rs.draw { get '/:controller(/:action(/:id))' }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list"))
assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10"))
@@ -289,21 +289,21 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_ignores_leading_slash
@rs.clear!
- @rs.draw { match '/:controller(/:action(/:id))'}
+ @rs.draw { get '/:controller(/:action(/:id))'}
test_default_setup
end
def test_route_with_colon_first
rs.draw do
- match '/:controller/:action/:id', :action => 'index', :id => nil
- match ':url', :controller => 'tiny_url', :action => 'translate'
+ get '/:controller/:action/:id', :action => 'index', :id => nil
+ get ':url', :controller => 'tiny_url', :action => 'translate'
end
end
def test_route_with_regexp_for_controller
rs.draw do
- match ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
- match '/:controller(/:action(/:id))'
+ get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
+ get '/:controller(/:action(/:id))'
end
assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"},
@@ -317,7 +317,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_and_captures_for_controller
rs.draw do
- match '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
+ get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
end
assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts"))
assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users"))
@@ -326,7 +326,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_and_dot
rs.draw do
- match ':controller/:action/:file',
+ get ':controller/:action/:file',
:controller => /admin|user/,
:action => /upload|download/,
:defaults => {:file => nil},
@@ -356,7 +356,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_with_option
rs.draw do
- match 'page/:title' => 'content#show_page', :as => 'page'
+ get 'page/:title' => 'content#show_page', :as => 'page'
end
assert_equal("http://test.host/page/new%20stuff",
@@ -365,7 +365,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_with_default
rs.draw do
- match 'page/:title' => 'content#show_page', :title => 'AboutPage', :as => 'page'
+ get 'page/:title' => 'content#show_page', :title => 'AboutPage', :as => 'page'
end
assert_equal("http://test.host/page/AboutRails",
@@ -375,7 +375,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_with_path_prefix
rs.draw do
scope "my" do
- match 'page' => 'content#show_page', :as => 'page'
+ get 'page' => 'content#show_page', :as => 'page'
end
end
@@ -386,7 +386,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_with_blank_path_prefix
rs.draw do
scope "" do
- match 'page' => 'content#show_page', :as => 'page'
+ get 'page' => 'content#show_page', :as => 'page'
end
end
@@ -396,7 +396,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_with_nested_controller
rs.draw do
- match 'admin/user' => 'admin/user#index', :as => "users"
+ get 'admin/user' => 'admin/user#index', :as => "users"
end
assert_equal("http://test.host/admin/user",
@@ -405,7 +405,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_optimised_named_route_with_host
rs.draw do
- match 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com'
+ get 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com'
end
routes = setup_for_named_route
routes.expects(:url_for).with({
@@ -424,7 +424,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_without_hash
rs.draw do
- match ':controller/:action/:id', :as => 'normal'
+ get ':controller/:action/:id', :as => 'normal'
end
end
@@ -448,9 +448,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_with_regexps
rs.draw do
- match 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
+ get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
:year => /\d+/, :month => /\d+/, :day => /\d+/
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
routes = setup_for_named_route
@@ -460,7 +460,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_changing_controller
- @rs.draw { match ':controller/:action/:id' }
+ @rs.draw { get ':controller/:action/:id' }
assert_equal '/admin/stuff/show/10',
url_for(rs, {:controller => 'stuff', :action => 'show', :id => 10},
@@ -469,8 +469,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_paths_escaped
rs.draw do
- match 'file/*path' => 'content#show_file', :as => 'path'
- match ':controller/:action/:id'
+ get 'file/*path' => 'content#show_file', :as => 'path'
+ get ':controller/:action/:id'
end
# No + to space in URI escaping, only for query params.
@@ -486,7 +486,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_paths_slashes_unescaped_with_ordered_parameters
rs.draw do
- match '/file/*path' => 'content#index', :as => 'path'
+ get '/file/*path' => 'content#index', :as => 'path'
end
# No / to %2F in URI, only for query params.
@@ -495,14 +495,14 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_non_controllers_cannot_be_matched
rs.draw do
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") }
end
def test_should_list_options_diff_when_routing_constraints_dont_match
rs.draw do
- match 'post/:id' => 'post#show', :constraints => { :id => /\d+/ }, :as => 'post'
+ get 'post/:id' => 'post#show', :constraints => { :id => /\d+/ }, :as => 'post'
end
assert_raise(ActionController::RoutingError) do
url_for(rs, { :controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post" })
@@ -511,7 +511,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_dynamic_path_allowed
rs.draw do
- match '*path' => 'content#show_file'
+ get '*path' => 'content#show_file'
end
assert_equal '/pages/boo',
@@ -520,7 +520,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_dynamic_recall_paths_allowed
rs.draw do
- match '*path' => 'content#show_file'
+ get '*path' => 'content#show_file'
end
assert_equal '/pages/boo',
@@ -529,8 +529,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_backwards
rs.draw do
- match 'page/:id(/:action)' => 'pages#show'
- match ':controller(/:action(/:id))'
+ get 'page/:id(/:action)' => 'pages#show'
+ get ':controller(/:action(/:id))'
end
assert_equal '/page/20', url_for(rs, { :id => 20 }, { :controller => 'pages', :action => 'show' })
@@ -540,8 +540,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_fixnum_default
rs.draw do
- match 'page(/:id)' => 'content#show_page', :id => 1
- match ':controller/:action/:id'
+ get 'page(/:id)' => 'content#show_page', :id => 1
+ get ':controller/:action/:id'
end
assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page' })
@@ -557,8 +557,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
# For newer revision
def test_route_with_text_default
rs.draw do
- match 'page/:id' => 'content#show_page', :id => 1
- match ':controller/:action/:id'
+ get 'page/:id' => 'content#show_page', :id => 1
+ get ':controller/:action/:id'
end
assert_equal '/page/foo', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 'foo' })
@@ -573,13 +573,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_action_expiry
- @rs.draw { match ':controller(/:action(/:id))' }
+ @rs.draw { get ':controller(/:action(/:id))' }
assert_equal '/content', url_for(rs, { :controller => 'content' }, { :controller => 'content', :action => 'show' })
end
def test_requirement_should_prevent_optional_id
rs.draw do
- match 'post/:id' => 'post#show', :constraints => {:id => /\d+/}, :as => 'post'
+ get 'post/:id' => 'post#show', :constraints => {:id => /\d+/}, :as => 'post'
end
assert_equal '/post/10', url_for(rs, { :controller => 'post', :action => 'show', :id => 10 })
@@ -591,11 +591,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_both_requirement_and_optional
rs.draw do
- match('test(/:year)' => 'post#show', :as => 'blog',
+ get('test(/:year)' => 'post#show', :as => 'blog',
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ }
)
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_equal '/test', url_for(rs, { :controller => 'post', :action => 'show' })
@@ -606,8 +606,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_set_to_nil_forgets
rs.draw do
- match 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil
- match ':controller/:action/:id'
+ get 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil
+ get ':controller/:action/:id'
end
assert_equal '/pages/2005',
@@ -649,8 +649,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_method
rs.draw do
- match 'categories' => 'content#categories', :as => 'categories'
- match ':controller(/:action(/:id))'
+ get 'categories' => 'content#categories', :as => 'categories'
+ get ':controller(/:action(/:id))'
end
assert_equal '/categories', url_for(rs, { :controller => 'content', :action => 'categories' })
@@ -664,9 +664,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_nil_defaults
rs.draw do
- match 'journal' => 'content#list_journal',
+ get 'journal' => 'content#list_journal',
:date => nil, :user_id => nil
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_equal '/journal', url_for(rs, {
@@ -698,7 +698,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_recognize_array_of_methods
rs.draw do
match '/match' => 'books#get_or_post', :via => [:get, :post]
- match '/match' => 'books#not_get_or_post'
+ put '/match' => 'books#not_get_or_post'
end
params = rs.recognize_path("/match", :method => :post)
@@ -710,10 +710,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_subpath_recognized
rs.draw do
- match '/books/:id/edit' => 'subpath_books#edit'
- match '/items/:id/:action' => 'subpath_books'
- match '/posts/new/:action' => 'subpath_books'
- match '/posts/:id' => 'subpath_books#show'
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
+ get '/posts/:id' => 'subpath_books#show'
end
hash = rs.recognize_path "/books/17/edit"
@@ -735,9 +735,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_subpath_generated
rs.draw do
- match '/books/:id/edit' => 'subpath_books#edit'
- match '/items/:id/:action' => 'subpath_books'
- match '/posts/new/:action' => 'subpath_books'
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
end
assert_equal "/books/7/edit", url_for(rs, { :controller => "subpath_books", :id => 7, :action => "edit" })
@@ -747,7 +747,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_failed_constraints_raises_exception_with_violated_constraints
rs.draw do
- match 'foos/:id' => 'foos#show', :as => 'foo_with_requirement', :constraints => { :id => /\d+/ }
+ get 'foos/:id' => 'foos#show', :as => 'foo_with_requirement', :constraints => { :id => /\d+/ }
end
assert_raise(ActionController::RoutingError) do
@@ -758,11 +758,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_routes_changed_correctly_after_clear
rs = ::ActionDispatch::Routing::RouteSet.new
rs.draw do
- match 'ca' => 'ca#aa'
- match 'cb' => 'cb#ab'
- match 'cc' => 'cc#ac'
- match ':controller/:action/:id'
- match ':controller/:action/:id.:format'
+ get 'ca' => 'ca#aa'
+ get 'cb' => 'cb#ab'
+ get 'cc' => 'cc#ac'
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
end
hash = rs.recognize_path "/cc"
@@ -771,10 +771,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal %w(cc ac), [hash[:controller], hash[:action]]
rs.draw do
- match 'cb' => 'cb#ab'
- match 'cc' => 'cc#ac'
- match ':controller/:action/:id'
- match ':controller/:action/:id.:format'
+ get 'cb' => 'cb#ab'
+ get 'cc' => 'cc#ac'
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
end
hash = rs.recognize_path "/cc"
@@ -799,29 +799,29 @@ class RouteSetTest < ActiveSupport::TestCase
@default_route_set ||= begin
set = ROUTING::RouteSet.new
set.draw do
- match '/:controller(/:action(/:id))'
+ get '/:controller(/:action(/:id))'
end
set
end
end
def test_generate_extras
- set.draw { match ':controller/(:action(/:id))' }
+ set.draw { get ':controller/(:action(/:id))' }
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
assert_equal %w(that this), extras.map { |e| e.to_s }.sort
end
def test_extra_keys
- set.draw { match ':controller/:action/:id' }
+ set.draw { get ':controller/:action/:id' }
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal %w(that this), extras.map { |e| e.to_s }.sort
end
def test_generate_extras_not_first
set.draw do
- match ':controller/:action/:id.:format'
- match ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
end
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
@@ -830,8 +830,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_not_first
set.draw do
- match ':controller/:action/:id.:format'
- match ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
end
assert_equal "/foo/bar/15?this=hello",
url_for(set, { :controller => "foo", :action => "bar", :id => 15, :this => "hello" })
@@ -839,8 +839,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_extra_keys_not_first
set.draw do
- match ':controller/:action/:id.:format'
- match ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
end
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal %w(that this), extras.map { |e| e.to_s }.sort
@@ -849,7 +849,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw
assert_equal 0, set.routes.size
set.draw do
- match '/hello/world' => 'a#b'
+ get '/hello/world' => 'a#b'
end
assert_equal 1, set.routes.size
end
@@ -857,7 +857,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw_symbol_controller_name
assert_equal 0, set.routes.size
set.draw do
- match '/users/index' => 'users#index'
+ get '/users/index' => 'users#index'
end
set.recognize_path('/users/index', :method => :get)
assert_equal 1, set.routes.size
@@ -866,7 +866,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_named_draw
assert_equal 0, set.routes.size
set.draw do
- match '/hello/world' => 'a#b', :as => 'hello'
+ get '/hello/world' => 'a#b', :as => 'hello'
end
assert_equal 1, set.routes.size
assert_equal set.routes.first, set.named_routes[:hello]
@@ -874,40 +874,23 @@ class RouteSetTest < ActiveSupport::TestCase
def test_earlier_named_routes_take_precedence
set.draw do
- match '/hello/world' => 'a#b', :as => 'hello'
- match '/hello' => 'a#b', :as => 'hello'
+ get '/hello/world' => 'a#b', :as => 'hello'
+ get '/hello' => 'a#b', :as => 'hello'
end
assert_equal set.routes.first, set.named_routes[:hello]
end
def setup_named_route_test
set.draw do
- match '/people(/:id)' => 'people#show', :as => 'show'
- match '/people' => 'people#index', :as => 'index'
- match '/people/go/:foo/:bar/joe(/:id)' => 'people#multi', :as => 'multi'
- match '/admin/users' => 'admin/users#index', :as => "users"
+ get '/people(/:id)' => 'people#show', :as => 'show'
+ get '/people' => 'people#index', :as => 'index'
+ get '/people/go/:foo/:bar/joe(/:id)' => 'people#multi', :as => 'multi'
+ get '/admin/users' => 'admin/users#index', :as => "users"
end
MockController.build(set.url_helpers).new
end
- def test_named_route_hash_access_method
- controller = setup_named_route_test
-
- assert_equal(
- { :controller => 'people', :action => 'show', :id => 5, :use_route => "show", :only_path => false },
- controller.send(:hash_for_show_url, :id => 5))
-
- assert_equal(
- { :controller => 'people', :action => 'index', :use_route => "index", :only_path => false },
- controller.send(:hash_for_index_url))
-
- assert_equal(
- { :controller => 'people', :action => 'show', :id => 5, :use_route => "show", :only_path => true },
- controller.send(:hash_for_show_path, :id => 5)
- )
- end
-
def test_named_route_url_method
controller = setup_named_route_test
@@ -919,7 +902,6 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "http://test.host/admin/users", controller.send(:users_url)
assert_equal '/admin/users', controller.send(:users_path)
- assert_equal '/admin/users', url_for(set, controller.send(:hash_for_users_url), { :controller => 'users', :action => 'index' })
end
def test_named_route_url_method_with_anchor
@@ -985,7 +967,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw_default_route
set.draw do
- match '/:controller/:action/:id'
+ get '/:controller/:action/:id'
end
assert_equal 1, set.routes.size
@@ -999,8 +981,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_with_parameter_shell
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+/
- match '/:controller(/:action(/:id))'
+ get 'page/:id' => 'pages#show', :id => /\d+/
+ get '/:controller(/:action(/:id))'
end
assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
@@ -1014,7 +996,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_on_request_object_with_anchors_are_valid
assert_nothing_raised do
set.draw do
- match 'page/:id' => 'pages#show', :constraints => { :host => /^foo$/ }
+ get 'page/:id' => 'pages#show', :constraints => { :host => /^foo$/ }
end
end
end
@@ -1022,27 +1004,27 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_with_anchor_chars_are_invalid
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /^\d+/
+ get 'page/:id' => 'pages#show', :id => /^\d+/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\A\d+/
+ get 'page/:id' => 'pages#show', :id => /\A\d+/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+$/
+ get 'page/:id' => 'pages#show', :id => /\d+$/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+\Z/
+ get 'page/:id' => 'pages#show', :id => /\d+\Z/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+\z/
+ get 'page/:id' => 'pages#show', :id => /\d+\z/
end
end
end
@@ -1057,7 +1039,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_recognize_with_encoded_id_and_regex
set.draw do
- match 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/
+ get 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/
end
assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
@@ -1128,7 +1110,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_typo_recognition
set.draw do
- match 'articles/:year/:month/:day/:title' => 'articles#permalink',
+ get 'articles/:year/:month/:day/:title' => 'articles#permalink',
:year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/
end
@@ -1143,7 +1125,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_routing_traversal_does_not_load_extra_classes
assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded"
set.draw do
- match '/profile' => 'profile#index'
+ get '/profile' => 'profile#index'
end
set.recognize_path("/profile") rescue nil
@@ -1177,8 +1159,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_default_action
set.draw do
- match "/people", :controller => "people", :action => "index"
- match "/people/list", :controller => "people", :action => "list"
+ get "/people", :controller => "people", :action => "index"
+ get "/people/list", :controller => "people", :action => "list"
end
url = url_for(set, { :controller => "people", :action => "list" })
@@ -1197,7 +1179,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
namespace 'api' do
- match 'inventory' => 'products#inventory'
+ get 'inventory' => 'products#inventory'
end
end
@@ -1222,7 +1204,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_namespace_with_path_prefix
set.draw do
scope :module => "api", :path => "prefix" do
- match 'inventory' => 'products#inventory'
+ get 'inventory' => 'products#inventory'
end
end
@@ -1234,7 +1216,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_namespace_with_blank_path_prefix
set.draw do
scope :module => "api", :path => "" do
- match 'inventory' => 'products#inventory'
+ get 'inventory' => 'products#inventory'
end
end
@@ -1244,7 +1226,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_generate_changes_controller_module
- set.draw { match ':controller/:action/:id' }
+ set.draw { get ':controller/:action/:id' }
current = { :controller => "bling/bloop", :action => "bap", :id => 9 }
assert_equal "/foo/bar/baz/7",
@@ -1253,7 +1235,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_id_is_sticky_when_it_ought_to_be
set.draw do
- match ':controller/:id/:action'
+ get ':controller/:id/:action'
end
url = url_for(set, { :action => "destroy" }, { :controller => "people", :action => "show", :id => "7" })
@@ -1262,8 +1244,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_use_static_path_when_possible
set.draw do
- match 'about' => "welcome#about"
- match ':controller/:action/:id'
+ get 'about' => "welcome#about"
+ get ':controller/:action/:id'
end
url = url_for(set, { :controller => "welcome", :action => "about" },
@@ -1273,7 +1255,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_generate
- set.draw { match ':controller/:action/:id' }
+ set.draw { get ':controller/:action/:id' }
args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" }
assert_equal "/foo/bar/7?x=y", url_for(set, args)
@@ -1284,7 +1266,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_path_prefix
set.draw do
scope "my" do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
@@ -1295,7 +1277,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_blank_path_prefix
set.draw do
scope "" do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
@@ -1305,9 +1287,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_named_routes_are_never_relative_to_modules
set.draw do
- match "/connection/manage(/:action)" => 'connection/manage#index'
- match "/connection/connection" => "connection/connection#index"
- match '/connection' => 'connection#index', :as => 'family_connection'
+ get "/connection/manage(/:action)" => 'connection/manage#index'
+ get "/connection/connection" => "connection/connection#index"
+ get '/connection' => 'connection#index', :as => 'family_connection'
end
url = url_for(set, { :controller => "connection" }, { :controller => 'connection/manage' })
@@ -1319,7 +1301,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_action_left_off_when_id_is_recalled
set.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
assert_equal '/books', url_for(set,
{:controller => 'books', :action => 'index'},
@@ -1329,8 +1311,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_query_params_will_be_shown_when_recalled
set.draw do
- match 'show_weblog/:parameter' => 'weblog#show'
- match ':controller(/:action(/:id))'
+ get 'show_weblog/:parameter' => 'weblog#show'
+ get ':controller(/:action(/:id))'
end
assert_equal '/weblog/edit?parameter=1', url_for(set,
{:action => 'edit', :parameter => 1},
@@ -1340,7 +1322,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_format_is_not_inherit
set.draw do
- match '/posts(.:format)' => 'posts#index'
+ get '/posts(.:format)' => 'posts#index'
end
assert_equal '/posts', url_for(set,
@@ -1355,7 +1337,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_expiry_determination_should_consider_values_with_to_param
- set.draw { match 'projects/:project_id/:controller/:action' }
+ set.draw { get 'projects/:project_id/:controller/:action' }
assert_equal '/projects/1/weblog/show', url_for(set,
{ :action => 'show', :project_id => 1 },
{ :controller => 'weblog', :action => 'show', :project_id => '1' })
@@ -1365,7 +1347,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
resources :projects do
member do
- match 'milestones' => 'milestones#index', :as => 'milestones'
+ get 'milestones' => 'milestones#index', :as => 'milestones'
end
end
end
@@ -1398,7 +1380,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_with_unsupported_regexp_options_must_error
assert_raise ArgumentError do
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => { :name => /(david|jamis)/m }
end
end
@@ -1407,13 +1389,13 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_with_supported_options_must_not_error
assert_nothing_raised do
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => { :name => /(david|jamis)/i }
end
end
assert_nothing_raised do
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => { :name => / # Desperately overcommented regexp
( #Either
david #The Creator
@@ -1423,11 +1405,11 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
end
-
+
def test_route_with_subdomain_and_constraints_must_receive_params
name_param = nil
set.draw do
- match 'page/:name' => 'pages#show', :constraints => lambda {|request|
+ get 'page/:name' => 'pages#show', :constraints => lambda {|request|
name_param = request.params[:name]
return true
}
@@ -1436,10 +1418,10 @@ class RouteSetTest < ActiveSupport::TestCase
set.recognize_path('http://subdomain.example.org/page/mypage'))
assert_equal(name_param, 'mypage')
end
-
+
def test_route_requirement_recognize_with_ignore_case
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => /(david|jamis)/i}
end
assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis'))
@@ -1451,7 +1433,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_generate_with_ignore_case
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => /(david|jamis)/i}
end
@@ -1466,7 +1448,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_recognize_with_extended_syntax
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => / # Desperately overcommented regexp
( #Either
david #The Creator
@@ -1486,7 +1468,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_with_xi_modifiers
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => / # Desperately overcommented regexp
( #Either
david #The Creator
@@ -1504,8 +1486,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_routes_with_symbols
set.draw do
- match 'unnamed', :controller => :pages, :action => :show, :name => :as_symbol
- match 'named' , :controller => :pages, :action => :show, :name => :as_symbol, :as => :named
+ get 'unnamed', :controller => :pages, :action => :show, :name => :as_symbol
+ get 'named' , :controller => :pages, :action => :show, :name => :as_symbol, :as => :named
end
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/unnamed'))
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named'))
@@ -1513,8 +1495,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_regexp_chunk_should_add_question_mark_for_optionals
set.draw do
- match '/' => 'foo#index'
- match '/hello' => 'bar#index'
+ get '/' => 'foo#index'
+ get '/hello' => 'bar#index'
end
assert_equal '/', url_for(set, { :controller => 'foo' })
@@ -1526,7 +1508,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_assign_route_options_with_anchor_chars
set.draw do
- match '/cars/:action/:person/:car/', :controller => 'cars'
+ get '/cars/:action/:person/:car/', :controller => 'cars'
end
assert_equal '/cars/buy/1/2', url_for(set, { :controller => 'cars', :action => 'buy', :person => '1', :car => '2' })
@@ -1536,7 +1518,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dot_path
set.draw do
- match '/books/:action.rss', :controller => 'books'
+ get '/books/:action.rss', :controller => 'books'
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list' })
@@ -1546,7 +1528,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dynamic_dot_path
set.draw do
- match '/books(/:action(.:format))', :controller => 'books'
+ get '/books(/:action(.:format))', :controller => 'books'
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list', :format => 'rss' })
@@ -1562,7 +1544,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_slashes_are_implied
@set = nil
- set.draw { match("/:controller(/:action(/:id))") }
+ set.draw { get("/:controller(/:action(/:id))") }
assert_equal '/content', url_for(set, { :controller => 'content', :action => 'index' })
assert_equal '/content/list', url_for(set, { :controller => 'content', :action => 'list' })
@@ -1647,13 +1629,13 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_default_params
set.draw do
- match 'dummy/page/:page' => 'dummy#show'
- match 'dummy/dots/page.:page' => 'dummy#dots'
- match 'ibocorp(/:page)' => 'ibocorp#show',
+ get 'dummy/page/:page' => 'dummy#show'
+ get 'dummy/dots/page.:page' => 'dummy#dots'
+ get 'ibocorp(/:page)' => 'ibocorp#show',
:constraints => { :page => /\d+/ },
:defaults => { :page => 1 }
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 })
@@ -1661,17 +1643,17 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_optional_params_recalls_last_request
set.draw do
- match "blog/", :controller => "blog", :action => "index"
+ get "blog/", :controller => "blog", :action => "index"
- match "blog(/:year(/:month(/:day)))",
+ get "blog(/:year(/:month(/:day)))",
:controller => "blog",
:action => "show_date",
:constraints => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/ },
:day => nil, :month => nil
- match "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/
- match "blog/:controller/:action(/:id)"
- match "*anything", :controller => "blog", :action => "unknown_request"
+ get "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/
+ get "blog/:controller/:action(/:id)"
+ get "*anything", :controller => "blog", :action => "unknown_request"
end
assert_equal({:controller => "blog", :action => "index"}, set.recognize_path("/blog"))
@@ -1719,7 +1701,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
root :to => 'users#index'
end
- match '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
+ get '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
:constraints => {
:year => /(19|20)\d\d/,
:month => /[01]?\d/,
@@ -1728,37 +1710,37 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
:day => nil,
:month => nil
- match 'archive/:year', :controller => 'archive', :action => 'index',
+ get 'archive/:year', :controller => 'archive', :action => 'index',
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ },
:as => "blog"
resources :people
- match 'legacy/people' => "people#index", :legacy => "true"
+ get 'legacy/people' => "people#index", :legacy => "true"
- match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
- match 'id_default(/:id)' => "foo#id_default", :id => 1
+ get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ get 'id_default(/:id)' => "foo#id_default", :id => 1
match 'get_or_post' => "foo#get_or_post", :via => [:get, :post]
- match 'optional/:optional' => "posts#index"
- match 'projects/:project_id' => "project#index", :as => "project"
- match 'clients' => "projects#index"
+ get 'optional/:optional' => "posts#index"
+ get 'projects/:project_id' => "project#index", :as => "project"
+ get 'clients' => "projects#index"
- match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
- match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
+ get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
:postalcode => /# Postcode format
\d{5} #Prefix
(-\d{4})? #Suffix
/x
}, :as => "geocode"
- match 'news(.:format)' => "news#index"
+ get 'news(.:format)' => "news#index"
- match 'comment/:id(/:action)' => "comments#show"
- match 'ws/:controller(/:action(/:id))', :ws => true
- match 'account(/:action)' => "account#subscription"
- match 'pages/:page_id/:controller(/:action(/:id))'
- match ':controller/ping', :action => 'ping'
- match ':controller(/:action(/:id))(.:format)'
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ match ':controller(/:action(/:id))(.:format)', :via => :all
root :to => "news#index"
}
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 3af17f495c..6fc3556e31 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -154,6 +154,17 @@ class SendFileTest < ActionController::TestCase
end
end
+ def test_send_file_with_default_content_disposition_header
+ process('data')
+ assert_equal 'attachment', @controller.headers['Content-Disposition']
+ end
+
+ def test_send_file_without_content_disposition_header
+ @controller.options = {:disposition => nil}
+ process('data')
+ assert_nil @controller.headers['Content-Disposition']
+ end
+
%w(file data).each do |method|
define_method "test_send_#{method}_status" do
@controller.options = { :stream => false, :status => 500 }
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index ecba9fed22..0d6d303b51 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -45,6 +45,10 @@ class TestCaseTest < ActionController::TestCase
render :text => request.fullpath
end
+ def test_format
+ render :text => request.format
+ end
+
def test_query_string
render :text => request.query_string
end
@@ -138,7 +142,7 @@ XML
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
end
@@ -224,6 +228,26 @@ XML
assert_equal 'value2', session[:symbol]
end
+ def test_process_merges_session_arg
+ session[:foo] = 'bar'
+ get :no_op, nil, { :bar => 'baz' }
+ assert_equal 'bar', session[:foo]
+ assert_equal 'baz', session[:bar]
+ end
+
+ def test_merged_session_arg_is_retained_across_requests
+ get :no_op, nil, { :foo => 'bar' }
+ assert_equal 'bar', session[:foo]
+ get :no_op
+ assert_equal 'bar', session[:foo]
+ end
+
+ def test_process_overwrites_existing_session_arg
+ session[:foo] = 'bar'
+ get :no_op, nil, { :foo => 'baz' }
+ assert_equal 'baz', session[:foo]
+ end
+
def test_session_is_cleared_from_controller_after_reset_session
process :set_session
process :reset_the_session
@@ -524,7 +548,7 @@ XML
with_routing do |set|
set.draw do
namespace :admin do
- match 'user' => 'user#index'
+ get 'user' => 'user#index'
end
end
@@ -534,7 +558,7 @@ XML
def test_assert_routing_with_glob
with_routing do |set|
- set.draw { match('*path' => "pages#show") }
+ set.draw { get('*path' => "pages#show") }
assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' })
end
end
@@ -559,14 +583,34 @@ XML
)
end
+ def test_params_passing_with_fixnums_when_not_html_request
+ get :test_params, :format => 'json', :count => 999
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'count' => 999 },
+ parsed_params
+ )
+ end
+
+ def test_params_passing_path_parameter_is_string_when_not_html_request
+ get :test_params, :format => 'json', :id => 1
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'id' => '1' },
+ parsed_params
+ )
+ end
+
def test_params_passing_with_frozen_values
assert_nothing_raised do
- get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze
+ get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze, :deepfreeze => { :frozen => 'icy'.freeze }.freeze
end
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
- 'frozen' => 'icy', 'frozens' => ['icy']},
+ 'frozen' => 'icy', 'frozens' => ['icy'], 'deepfreeze' => { 'frozen' => 'icy' }},
parsed_params
)
end
@@ -585,8 +629,8 @@ XML
def test_array_path_parameter_handled_properly
with_routing do |set|
set.draw do
- match 'file/*path', :to => 'test_case_test/test#test_params'
- match ':controller/:action'
+ get 'file/*path', :to => 'test_case_test/test#test_params'
+ get ':controller/:action'
end
get :test_params, :path => ['hello', 'world']
@@ -667,6 +711,20 @@ XML
assert_equal "http://", @response.body
end
+ def test_request_format
+ get :test_format, :format => 'html'
+ assert_equal 'text/html', @response.body
+
+ get :test_format, :format => 'json'
+ assert_equal 'application/json', @response.body
+
+ get :test_format, :format => 'xml'
+ assert_equal 'application/xml', @response.body
+
+ get :test_format
+ assert_equal 'text/html', @response.body
+ end
+
def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
cookies['foo'] = 'bar'
get :no_op
@@ -828,3 +886,24 @@ class NamedRoutesControllerTest < ActionController::TestCase
end
end
end
+
+class AnonymousControllerTest < ActionController::TestCase
+ def setup
+ @controller = Class.new(ActionController::Base) do
+ def index
+ render :text => params[:controller]
+ end
+ end.new
+
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
+ r.draw do
+ get ':controller(/:action(/:id))'
+ end
+ end
+ end
+
+ def test_controller_name
+ get :index
+ assert_equal 'anonymous', @response.body
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index 451ea6027d..6c2311e7a5 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -18,7 +18,7 @@ module ActionPack
root :to => 'users#index'
end
- match '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
+ get '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
:constraints => {
:year => /(19|20)\d\d/,
:month => /[01]?\d/,
@@ -27,7 +27,7 @@ module ActionPack
:day => nil,
:month => nil
- match 'archive/:year', :controller => 'archive', :action => 'index',
+ get 'archive/:year', :controller => 'archive', :action => 'index',
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ },
:as => "blog"
@@ -35,29 +35,29 @@ module ActionPack
resources :people
#match 'legacy/people' => "people#index", :legacy => "true"
- match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
- match 'id_default(/:id)' => "foo#id_default", :id => 1
+ get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ get 'id_default(/:id)' => "foo#id_default", :id => 1
match 'get_or_post' => "foo#get_or_post", :via => [:get, :post]
- match 'optional/:optional' => "posts#index"
- match 'projects/:project_id' => "project#index", :as => "project"
- match 'clients' => "projects#index"
+ get 'optional/:optional' => "posts#index"
+ get 'projects/:project_id' => "project#index", :as => "project"
+ get 'clients' => "projects#index"
- match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
- match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
+ get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
:postalcode => /# Postcode format
\d{5} #Prefix
(-\d{4})? #Suffix
/x
}, :as => "geocode"
- match 'news(.:format)' => "news#index"
+ get 'news(.:format)' => "news#index"
- match 'comment/:id(/:action)' => "comments#show"
- match 'ws/:controller(/:action(/:id))', :ws => true
- match 'account(/:action)' => "account#subscription"
- match 'pages/:page_id/:controller(/:action(/:id))'
- match ':controller/ping', :action => 'ping'
- match ':controller(/:action(/:id))(.:format)'
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ get ':controller(/:action(/:id))(.:format)'
root :to => "news#index"
}
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index aa233d6135..b2cb5f80d5 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -5,7 +5,7 @@ module AbstractController
class UrlForTests < ActionController::TestCase
class W
- include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { match ':controller(/:action(/:id(.:format)))' } }.url_helpers
+ include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers
end
def teardown
@@ -210,8 +210,8 @@ module AbstractController
def test_named_routes
with_routing do |set|
set.draw do
- match 'this/is/verbose', :to => 'home#index', :as => :no_args
- match 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ get 'this/is/verbose', :to => 'home#index', :as => :no_args
+ get 'home/sweet/home/:user', :to => 'home#index', :as => :home
end
# We need to create a new class in order to install the new named route.
@@ -231,7 +231,7 @@ module AbstractController
def test_relative_url_root_is_respected_for_named_routes
with_routing do |set|
set.draw do
- match '/home/sweet/home/:user', :to => 'home#index', :as => :home
+ get '/home/sweet/home/:user', :to => 'home#index', :as => :home
end
kls = Class.new { include set.url_helpers }
@@ -245,8 +245,8 @@ module AbstractController
def test_only_path
with_routing do |set|
set.draw do
- match 'home/sweet/home/:user', :to => 'home#index', :as => :home
- match ':controller/:action/:id'
+ get 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ get ':controller/:action/:id'
end
# We need to create a new class in order to install the new named route.
@@ -313,8 +313,8 @@ module AbstractController
def test_named_routes_with_nil_keys
with_routing do |set|
set.draw do
- match 'posts.:format', :to => 'posts#index', :as => :posts
- match '/', :to => 'posts#index', :as => :main
+ get 'posts.:format', :to => 'posts#index', :as => :posts
+ get '/', :to => 'posts#index', :as => :main
end
# We need to create a new class in order to install the new named route.
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index f88903b10e..cc3706aeee 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -21,7 +21,7 @@ class UrlRewriterTests < ActionController::TestCase
@rewriter = Rewriter.new(@request) #.new(@request, @params)
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
end
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 351e61eeae..c0b9833603 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -254,7 +254,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match '/', :to => 'web_service_test/test#assign_parameters'
+ match '/', :to => 'web_service_test/test#assign_parameters', :via => :all
end
yield
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 3e48d97e67..2467654a70 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -38,6 +38,8 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ alias delete_cookie logout
+
def delete_cookie_with_path
cookies.delete("user_name", :path => '/beaten')
head :ok
@@ -179,6 +181,18 @@ class CookiesTest < ActionController::TestCase
assert_equal({"user_name" => "david"}, @response.cookies)
end
+ def test_setting_the_same_value_to_cookie
+ request.cookies[:user_name] = 'david'
+ get :authenticate
+ assert response.cookies.empty?
+ end
+
+ def test_setting_the_same_value_to_permanent_cookie
+ request.cookies[:user_name] = 'Jamie'
+ get :set_permanent_cookie
+ assert response.cookies, 'user_name' => 'Jamie'
+ end
+
def test_setting_with_escapable_characters
get :set_with_with_escapable_characters
assert_cookie_header "that+%26+guy=foo+%26+bar+%3D%3E+baz; path=/"
@@ -235,23 +249,33 @@ class CookiesTest < ActionController::TestCase
end
def test_expiring_cookie
+ request.cookies[:user_name] = 'Joe'
get :logout
assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
assert_equal({"user_name" => nil}, @response.cookies)
end
def test_delete_cookie_with_path
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_path
assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
+ def test_delete_unexisting_cookie
+ request.cookies.clear
+ get :delete_cookie
+ assert @response.cookies.empty?
+ end
+
def test_deleted_cookie_predicate
+ cookies[:user_name] = 'Joe'
cookies.delete("user_name")
assert cookies.deleted?("user_name")
assert_equal false, cookies.deleted?("another")
end
def test_deleted_cookie_predicate_with_mismatching_options
+ cookies[:user_name] = 'Joe'
cookies.delete("user_name", :path => "/path")
assert_equal false, cookies.deleted?("user_name", :path => "/different")
end
@@ -284,6 +308,7 @@ class CookiesTest < ActionController::TestCase
end
def test_delete_and_set_cookie
+ request.cookies[:user_name] = 'Joe'
get :delete_and_set_cookie
assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"
assert_equal({"user_name" => "david"}, @response.cookies)
@@ -387,6 +412,7 @@ class CookiesTest < ActionController::TestCase
end
def test_deleting_cookie_with_all_domain_option
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domain
assert_response :success
assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
@@ -413,6 +439,7 @@ class CookiesTest < ActionController::TestCase
end
def test_deleting_cookie_with_all_domain_option_and_tld_length
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domain_and_tld
assert_response :success
assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
@@ -441,6 +468,7 @@ class CookiesTest < ActionController::TestCase
def test_deletings_cookie_with_several_preset_domains_using_one_of_these_domains
@request.host = "example2.com"
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=; domain=example2.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
@@ -448,19 +476,19 @@ class CookiesTest < ActionController::TestCase
def test_deletings_cookie_with_several_preset_domains_using_other_domain
@request.host = "other-domain.com"
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
-
def test_cookies_hash_is_indifferent_access
- get :symbol_key
- assert_equal "david", cookies[:user_name]
- assert_equal "david", cookies['user_name']
- get :string_key
- assert_equal "dhh", cookies[:user_name]
- assert_equal "dhh", cookies['user_name']
+ get :symbol_key
+ assert_equal "david", cookies[:user_name]
+ assert_equal "david", cookies['user_name']
+ get :string_key
+ assert_equal "dhh", cookies[:user_name]
+ assert_equal "dhh", cookies['user_name']
end
@@ -575,4 +603,4 @@ class CookiesTest < ActionController::TestCase
assert_not_equal expected.split("\n"), header
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
index ec6ba494dc..bc7cad8db5 100644
--- a/actionpack/test/dispatch/header_test.rb
+++ b/actionpack/test/dispatch/header_test.rb
@@ -13,4 +13,9 @@ class HeaderTest < ActiveSupport::TestCase
assert_equal "text/plain", @headers["CONTENT_TYPE"]
assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"]
end
+
+ test "fetch" do
+ assert_equal "text/plain", @headers.fetch("content-type", nil)
+ assert_equal "not found", @headers.fetch('not-found', 'not found')
+ end
end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index d3465589c1..bd078d2b21 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -4,11 +4,12 @@ module ActionDispatch
module Routing
class MapperTest < ActiveSupport::TestCase
class FakeSet
- attr_reader :routes
+ attr_reader :routes, :draw_paths
alias :set :routes
def initialize
@routes = []
+ @draw_paths = []
end
def resources_path_names
@@ -37,7 +38,7 @@ module ActionDispatch
end
def test_mapping_requirements
- options = { :controller => 'foo', :action => 'bar' }
+ options = { :controller => 'foo', :action => 'bar', :via => :get }
m = Mapper::Mapping.new FakeSet.new, {}, '/store/:name(*rest)', options
_, _, requirements, _ = m.to_route
assert_equal(/.+?/, requirements[:rest])
@@ -46,7 +47,7 @@ module ActionDispatch
def test_map_slash
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/', :to => 'posts#index', :as => :main
+ mapper.get '/', :to => 'posts#index', :as => :main
assert_equal '/', fakeset.conditions.first[:path_info]
end
@@ -55,14 +56,14 @@ module ActionDispatch
mapper = Mapper.new fakeset
# FIXME: is this a desired behavior?
- mapper.match '/one/two/', :to => 'posts#index', :as => :main
+ mapper.get '/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'
+ mapper.get '/*path', :to => 'pages#show'
assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info]
assert_equal(/.+?/, fakeset.requirements.first[:path])
end
@@ -70,7 +71,7 @@ module ActionDispatch
def test_map_wildcard_with_other_element
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path/foo/:bar', :to => 'pages#show'
+ mapper.get '/*path/foo/:bar', :to => 'pages#show'
assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
assert_nil fakeset.requirements.first[:path]
end
@@ -78,7 +79,7 @@ module ActionDispatch
def test_map_wildcard_with_multiple_wildcard
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*foo/*bar', :to => 'pages#show'
+ mapper.get '/*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])
@@ -87,7 +88,7 @@ module ActionDispatch
def test_map_wildcard_with_format_false
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path', :to => 'pages#show', :format => false
+ mapper.get '/*path', :to => 'pages#show', :format => false
assert_equal '/*path', fakeset.conditions.first[:path_info]
assert_nil fakeset.requirements.first[:path]
end
@@ -95,7 +96,7 @@ module ActionDispatch
def test_map_wildcard_with_format_true
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path', :to => 'pages#show', :format => true
+ mapper.get '/*path', :to => 'pages#show', :format => true
assert_equal '/*path.:format', fakeset.conditions.first[:path_info]
end
end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 4191ed1ff4..948a690979 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -45,7 +45,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack.last.klass
assert_equal([true, {:foo => "bar"}], @stack.last.args)
end
-
+
test "use should push middleware class with block arguments onto the stack" do
proc = Proc.new {}
assert_difference "@stack.size" do
@@ -54,7 +54,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BlockMiddleware, @stack.last.klass
assert_equal proc, @stack.last.block
end
-
+
test "insert inserts middleware at the integer index" do
@stack.insert(1, BazMiddleware)
assert_equal BazMiddleware, @stack[1].klass
@@ -87,6 +87,11 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal FooMiddleware, @stack[0].klass
end
+ test "unshift adds a new middleware at the beginning of the stack" do
+ @stack.unshift :"MiddlewareStackTest::BazMiddleware"
+ assert_equal BazMiddleware, @stack.first.klass
+ end
+
test "raise an error on invalid index" do
assert_raise RuntimeError do
@stack.insert("HiyaMiddleware", BazMiddleware)
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index f7a746120e..536e35ab2e 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -37,6 +37,11 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
assert_equal "/sprockets -- /omg", response.body
end
+ def test_mounting_works_with_nested_script_name
+ get "/foo/sprockets/omg", {}, 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg'
+ assert_equal "/foo/sprockets -- /omg", response.body
+ end
+
def test_mounting_works_with_scope
get "/its_a/sprocket/omg"
assert_equal "/its_a/sprocket -- /omg", response.body
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index bd5b5edab0..ab2f7612ce 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -25,12 +25,12 @@ module TestGenerationPrefix
@routes ||= begin
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
- match "/posts/:id", :to => "inside_engine_generating#show", :as => :post
- match "/posts", :to => "inside_engine_generating#index", :as => :posts
- match "/url_to_application", :to => "inside_engine_generating#url_to_application"
- match "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine"
- match "/conflicting_url", :to => "inside_engine_generating#conflicting"
- match "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
+ get "/posts/:id", :to => "inside_engine_generating#show", :as => :post
+ get "/posts", :to => "inside_engine_generating#index", :as => :posts
+ get "/url_to_application", :to => "inside_engine_generating#url_to_application"
+ get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine"
+ get "/conflicting_url", :to => "inside_engine_generating#conflicting"
+ get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
end
routes
@@ -51,12 +51,12 @@ module TestGenerationPrefix
scope "/:omg", :omg => "awesome" do
mount BlogEngine => "/blog", :as => "blog_engine"
end
- match "/posts/:id", :to => "outside_engine_generating#post", :as => :post
- match "/generate", :to => "outside_engine_generating#index"
- match "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
- match "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
- match "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
- match "/conflicting_url", :to => "outside_engine_generating#conflicting"
+ get "/posts/:id", :to => "outside_engine_generating#post", :as => :post
+ get "/generate", :to => "outside_engine_generating#index"
+ get "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
+ get "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
+ get "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
+ get "/conflicting_url", :to => "outside_engine_generating#conflicting"
root :to => "outside_engine_generating#index"
end
@@ -282,7 +282,7 @@ module TestGenerationPrefix
@routes ||= begin
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
- match "/posts/:id", :to => "posts#show", :as => :post
+ get "/posts/:id", :to => "posts#show", :as => :post
end
routes
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index ae425dd406..302bff0696 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -65,7 +65,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::JsonParamsParsingTest::TestController
+ post ':action', :to => ::JsonParamsParsingTest::TestController
end
yield
end
@@ -118,7 +118,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing(controller)
with_routing do |set|
set.draw do
- match ':action', :to => controller
+ post ':action', :to => controller
end
yield
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index d144f013f5..63c5ea26a6 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -144,7 +144,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => 'multipart_params_parsing_test/test'
+ post ':action', :to => 'multipart_params_parsing_test/test'
end
yield
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index f6a1475d04..d14f188e30 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -109,7 +109,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
def assert_parses(expected, actual)
with_routing do |set|
set.draw do
- match ':action', :to => ::QueryStringParsingTest::TestController
+ get ':action', :to => ::QueryStringParsingTest::TestController
end
get "/parse", actual
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
new file mode 100644
index 0000000000..4d24456ba6
--- /dev/null
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -0,0 +1,48 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ class Request
+ class SessionTest < ActiveSupport::TestCase
+ def test_create_adds_itself_to_env
+ env = {}
+ s = Session.create(store, env, {})
+ assert_equal s, env[Rack::Session::Abstract::ENV_SESSION_KEY]
+ end
+
+ def test_to_hash
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+ assert_equal 'bar', s['foo']
+ assert_equal({'foo' => 'bar'}, s.to_hash)
+ end
+
+ def test_create_merges_old
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+
+ s1 = Session.create(store, env, {})
+ refute_equal s, s1
+ assert_equal 'bar', s1['foo']
+ end
+
+ def test_find
+ env = {}
+ assert_nil Session.find(env)
+
+ s = Session.create(store, env, {})
+ assert_equal s, Session.find(env)
+ end
+
+ private
+ def store
+ Class.new {
+ def load_session(env); [1, {}]; end
+ def session_exists?(env); true; end
+ }.new
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 05569561d2..568e220b15 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -130,7 +130,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::UrlEncodedParamsParsingTest::TestController
+ post ':action', :to => ::UrlEncodedParamsParsingTest::TestController
end
yield
end
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index afd400c2a9..84823e2896 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -106,7 +106,7 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::XmlParamsParsingTest::TestController
+ post ':action', :to => ::XmlParamsParsingTest::TestController
end
yield
end
@@ -155,7 +155,7 @@ class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::RootLessXmlParamsParsingTest::TestController
+ post ':action', :to => ::RootLessXmlParamsParsingTest::TestController
end
yield
end
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
index 4b98cd32f2..a8050b4fab 100644
--- a/actionpack/test/dispatch/request_id_test.rb
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -52,7 +52,7 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match '/', :to => ::RequestIdResponseTest::TestController.action(:index)
+ get '/', :to => ::RequestIdResponseTest::TestController.action(:index)
end
@app = self.class.build_app(set) do |middleware|
@@ -62,4 +62,4 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest
yield
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 6c8b22c47f..94d0e09842 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -35,37 +35,40 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '1.2.3.4', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '1.2.3.4',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '127.0.0.1',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
- assert_equal 'unknown', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4'
assert_equal '3.4.5.6', request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
+ assert_equal nil, request.remote_ip
+
request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
'HTTP_CLIENT_IP' => '2.2.2.2'
e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
@@ -89,6 +92,68 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '9.9.9.9', request.remote_ip
end
+ test "remote ip v6" do
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '::1',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1, ::1, fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
+ 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
+ request.remote_ip
+ }
+ assert_match(/IP spoofing attack/, e.message)
+ assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message)
+ assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message)
+
+ # Turn IP Spoofing detection off.
+ # This is useful for sites that are aimed at non-IP clients. The typical
+ # example is WAP. Since the cellular network is not IP based, it's a
+ # leap of faith to assume that their proxies are ever going to set the
+ # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly.
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
+ 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ :ip_spoofing_check => false
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+ end
+
test "remote ip when the remote ip middleware returns nil" do
request = stub_request 'REMOTE_ADDR' => '127.0.0.1'
assert_equal '127.0.0.1', request.remote_ip
@@ -97,29 +162,47 @@ class RequestTest < ActiveSupport::TestCase
test "remote ip with user specified trusted proxies String" do
@trusted_proxies = "67.205.106.73"
- request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ request = stub_request 'REMOTE_ADDR' => '3.4.5.6',
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
+ assert_equal '172.16.0.1', request.remote_ip
- request = stub_request 'REMOTE_ADDR' => '67.205.106.73,172.16.0.1',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
-
- request = stub_request 'REMOTE_ADDR' => '67.205.106.74,172.16.0.1',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6',
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73'
- assert_equal 'unknown', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
end
+ test "remote ip v6 with user specified trusted proxies String" do
+ @trusted_proxies = 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal nil, request.remote_ip
+ end
+
test "remote ip with user specified trusted proxies Regexp" do
@trusted_proxies = /^67\.205\.106\.73$/i
@@ -128,7 +211,18 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6'
- assert_equal '10.0.0.1', request.remote_ip
+ assert_equal nil, request.remote_ip
+ end
+
+ test "remote ip v6 with user specified trusted proxies Regexp" do
+ @trusted_proxies = /^fe80:0000:0000:0000:0202:b3ff:fe1e:8329$/i
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal nil, request.remote_ip
end
test "domains" do
diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb
index e953029456..517354ae58 100644
--- a/actionpack/test/dispatch/routing_assertions_test.rb
+++ b/actionpack/test/dispatch/routing_assertions_test.rb
@@ -47,7 +47,7 @@ class RoutingAssertionsTest < ActionController::TestCase
def test_assert_recognizes_with_extras
assert_recognizes({ :controller => 'articles', :action => 'index', :page => '1' }, '/articles', { :page => '1' })
end
-
+
def test_assert_recognizes_with_method
assert_recognizes({ :controller => 'articles', :action => 'create' }, { :path => '/articles', :method => :post })
assert_recognizes({ :controller => 'articles', :action => 'update', :id => '1' }, { :path => '/articles/1', :method => :put })
@@ -57,7 +57,7 @@ class RoutingAssertionsTest < ActionController::TestCase
assert_raise(ActionController::RoutingError) do
assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles')
end
- assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'https://test.host/secure/articles')
+ assert_recognizes({ :controller => 'secure_articles', :action => 'index', :protocol => 'https://' }, 'https://test.host/secure/articles')
end
def test_assert_recognizes_with_block_constraint
@@ -90,7 +90,7 @@ class RoutingAssertionsTest < ActionController::TestCase
assert_raise(ActionController::RoutingError) do
assert_routing('http://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' })
end
- assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' })
+ assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index', :protocol => 'https://' })
end
def test_assert_routing_with_block_constraint
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index cc4279d9dd..1a8f40037f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -58,41 +58,46 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "remove", :action => :destroy, :as => :remove
end
- match 'account/logout' => redirect("/logout"), :as => :logout_redirect
- match 'account/login', :to => redirect("/login")
- match 'secure', :to => redirect("/secure/login")
+ get 'account/logout' => redirect("/logout"), :as => :logout_redirect
+ get 'account/login', :to => redirect("/login")
+ get 'secure', :to => redirect("/secure/login")
- match 'mobile', :to => redirect(:subdomain => 'mobile')
- match 'super_new_documentation', :to => redirect(:host => 'super-docs.com')
+ get 'mobile', :to => redirect(:subdomain => 'mobile')
+ get 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '')
+ get 'new_documentation', :to => redirect(:path => '/documentation/new')
+ get 'super_new_documentation', :to => redirect(:host => 'super-docs.com')
- match 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector)
+ get 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
+ get 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}')
+
+ get 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector)
constraints(lambda { |req| true }) do
- match 'account/overview'
+ get 'account/overview'
end
- match '/account/nested/overview'
- match 'sign_in' => "sessions#new"
+ get '/account/nested/overview'
+ get 'sign_in' => "sessions#new"
- match 'account/modulo/:name', :to => redirect("/%{name}s")
- match 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" }
- match 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
+ get 'account/modulo/:name', :to => redirect("/%{name}s")
+ get 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" }
+ get 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
- match 'account/google' => redirect('http://www.google.com/', :status => 302)
+ get 'account/google' => redirect('http://www.google.com/', :status => 302)
match 'openid/login', :via => [:get, :post], :to => "openid#login"
controller(:global) do
get 'global/hide_notice'
- match 'global/export', :to => :export, :as => :export_request
- match '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
- match 'global/:action'
+ get 'global/export', :to => :export, :as => :export_request
+ get '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ get 'global/:action'
end
- match "/local/:action", :controller => "local"
+ get "/local/:action", :controller => "local"
- match "/projects/status(.:format)"
- match "/404", :to => lambda { |env| [404, {"Content-Type" => "text/plain"}, ["NOT FOUND"]] }
+ get "/projects/status(.:format)"
+ get "/404", :to => lambda { |env| [404, {"Content-Type" => "text/plain"}, ["NOT FOUND"]] }
constraints(:ip => /192\.168\.1\.\d\d\d/) do
get 'admin' => "queenbee#index"
@@ -166,6 +171,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
post :preview, :on => :collection
end
end
+
+ post 'new', :action => 'new', :on => :collection, :as => :new
end
resources :replies do
@@ -277,25 +284,25 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
+ get 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
- match 'people/:id/update', :to => 'people#update', :as => :update_person
- match '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
+ get 'people/:id/update', :to => 'people#update', :as => :update_person
+ get '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
# misc
- match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
+ get 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
# default params
- match 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
- match 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
+ get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
+ get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
defaults :id => 'home' do
- match 'scoped_pages/(:id)', :to => 'pages#show'
+ get 'scoped_pages/(:id)', :to => 'pages#show'
end
namespace :account do
- match 'shorthand'
- match 'description', :to => :description, :as => "description"
- match ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
+ get 'shorthand'
+ get 'description', :to => :description, :as => "description"
+ get ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
resource :subscription, :credit, :credit_card
root :to => "account#index"
@@ -318,7 +325,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
controller :articles do
scope '/articles', :as => 'article' do
scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
- match '/:id', :to => :with_id, :as => ""
+ get '/:id', :to => :with_id, :as => ""
end
end
end
@@ -327,7 +334,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :rooms
end
- match '/info' => 'projects#info', :as => 'info'
+ get '/info' => 'projects#info', :as => 'info'
namespace :admin do
scope '(:locale)', :locale => /en|pl/ do
@@ -361,7 +368,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
scope :path => 'api' do
resource :me
- match '/' => 'mes#index'
+ get '/' => 'mes#index'
end
get "(/:username)/followers" => "followers#index"
@@ -374,7 +381,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- match "whatever/:controller(/:action(/:id))", :id => /\d+/
+ get "whatever/:controller(/:action(/:id))", :id => /\d+/
resource :profile do
get :settings
@@ -407,7 +414,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
namespace :private do
root :to => redirect('/private/index')
- match "index", :to => 'private#index'
+ get "index", :to => 'private#index'
end
scope :only => [:index, :show] do
@@ -489,7 +496,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
end
- match '/purchases/:token/:filename',
+ get '/purchases/:token/:filename',
:to => 'purchases#fetch',
:token => /[[:alnum:]]{10}/,
:filename => /(.+)/,
@@ -500,18 +507,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
scope '/countries/:country', :constraints => lambda { |params, req| params[:country].in?(["all", "France"]) } do
- match '/', :to => 'countries#index'
- match '/cities', :to => 'countries#cities'
+ get '/', :to => 'countries#index'
+ get '/cities', :to => 'countries#cities'
end
- match '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
+ get '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
- match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
+ get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
scope '/italians' do
- match '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
- match '/sculptors', :to => 'italians#sculptors'
- match '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
+ get '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
+ get '/sculptors', :to => 'italians#sculptors'
+ get '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
end
end
end
@@ -627,7 +634,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
self.class.stub_controllers do |routes|
routes.draw do
namespace :admin do
- match '/:controller(/:action(/:id(.:format)))'
+ get '/:controller(/:action(/:id(.:format)))'
end
end
end
@@ -693,11 +700,31 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
verify_redirect 'http://mobile.example.com/mobile'
end
+ def test_redirect_hash_with_domain_and_path
+ get '/documentation'
+ verify_redirect 'http://www.example-documentation.com'
+ end
+
+ def test_redirect_hash_with_path
+ get '/new_documentation'
+ verify_redirect 'http://www.example.com/documentation/new'
+ end
+
def test_redirect_hash_with_host
get '/super_new_documentation?section=top'
verify_redirect 'http://super-docs.com/super_new_documentation?section=top'
end
+ def test_redirect_hash_path_substitution
+ get '/stores/iernest'
+ verify_redirect 'http://stores.example.com/iernest'
+ end
+
+ def test_redirect_hash_path_substitution_with_catch_all
+ get '/stores/iernest/products'
+ verify_redirect 'http://stores.example.com/iernest/products'
+ end
+
def test_redirect_class
get '/youtube_favorites/oHg5SJYRHA0/rick-rolld'
verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
@@ -802,6 +829,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal original_options, options
end
+ def test_url_for_does_not_modify_controller
+ controller = '/projects'
+ options = {:controller => controller, :action => 'status', :only_path => true}
+ url = url_for(options)
+
+ assert_equal '/projects/status', url
+ assert_equal '/projects', controller
+ end
+
# tests the arguments modification free version of define_hash_access
def test_named_route_with_no_side_effects
original_options = { :host => 'test.host' }
@@ -851,6 +887,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/projects/1/edit', edit_project_path(:id => '1')
end
+ def test_projects_with_post_action_and_new_path_on_collection
+ post '/projects/new'
+ assert_equal "project#new", @response.body
+ assert_equal "/projects/new", new_projects_path
+ end
+
def test_projects_involvements
get '/projects/1/involvements'
assert_equal 'involvements#index', @response.body
@@ -2231,12 +2273,12 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest
s = self
@app = ActionDispatch::Routing::RouteSet.new
@app.append do
- match '/hello' => s.simple_app('fail')
- match '/goodbye' => s.simple_app('goodbye')
+ get '/hello' => s.simple_app('fail')
+ get '/goodbye' => s.simple_app('goodbye')
end
@app.draw do
- match '/hello' => s.simple_app('hello')
+ get '/hello' => s.simple_app('hello')
end
end
@@ -2282,6 +2324,55 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
end
end
+class TestDrawExternalFile < ActionDispatch::IntegrationTest
+ class ExternalController < ActionController::Base
+ def index
+ render :text => "external#index"
+ end
+ end
+
+ DRAW_PATH = Pathname.new(File.expand_path('../../fixtures/routes', __FILE__))
+
+ DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw_paths << DRAW_PATH
+ end
+
+ def app
+ DefaultScopeRoutes
+ end
+
+ def test_draw_external_file
+ DefaultScopeRoutes.draw do
+ scope :module => 'test_draw_external_file' do
+ draw :external
+ end
+ end
+
+ get '/external'
+ assert_equal "external#index", @response.body
+ end
+
+ def test_draw_nonexistent_file
+ exception = assert_raise ArgumentError do
+ DefaultScopeRoutes.draw do
+ draw :nonexistent
+ end
+ end
+ assert_match 'Your router tried to #draw the external file nonexistent.rb', exception.message
+ assert_match DRAW_PATH.to_s, exception.message
+ end
+
+ def test_draw_bogus_file
+ exception = assert_raise NoMethodError do
+ DefaultScopeRoutes.draw do
+ draw :bogus
+ end
+ end
+ assert_match "undefined method `wrong'", exception.message
+ assert_match 'test/fixtures/routes/bogus.rb:1', exception.backtrace.first
+ end
+end
+
class TestDefaultScope < ActionDispatch::IntegrationTest
module ::Blog
class PostsController < ActionController::Base
@@ -2344,12 +2435,12 @@ end
class TestUriPathEscaping < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
- match '/:segment' => lambda { |env|
+ get '/:segment' => lambda { |env|
path_params = env['action_dispatch.request.path_parameters']
[200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]]
}, :as => :segment
- match '/*splat' => lambda { |env|
+ get '/*splat' => lambda { |env|
path_params = env['action_dispatch.request.path_parameters']
[200, { 'Content-Type' => 'text/plain' }, [path_params[:splat]]]
}, :as => :splat
@@ -2381,7 +2472,7 @@ end
class TestUnicodePaths < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
- match "/#{Rack::Utils.escape("ほげ")}" => lambda { |env|
+ get "/#{Rack::Utils.escape("ほげ")}" => lambda { |env|
[200, { 'Content-Type' => 'text/plain' }, []]
}, :as => :unicode_path
end
@@ -2411,10 +2502,10 @@ class TestMultipleNestedController < ActionDispatch::IntegrationTest
app.draw do
namespace :foo do
namespace :bar do
- match "baz" => "baz#index"
+ get "baz" => "baz#index"
end
end
- match "pooh" => "pooh#index"
+ get "pooh" => "pooh#index"
end
end
@@ -2425,7 +2516,6 @@ class TestMultipleNestedController < ActionDispatch::IntegrationTest
get "/foo/bar/baz"
assert_equal "/pooh", @response.body
end
-
end
class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest
@@ -2433,8 +2523,8 @@ class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest
app.draw do
ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
- match "/~user" => ok
- match "/young-and-fine" => ok
+ get "/~user" => ok
+ get "/young-and-fine" => ok
end
end
@@ -2452,3 +2542,158 @@ class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest
end
end
+
+class TestRedirectInterpolation < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/foo/:id" => redirect("/foo/bar/%{id}")
+ get "/bar/:id" => redirect(:path => "/foo/bar/%{id}")
+ get "/foo/bar/:id" => ok
+ end
+ end
+
+ def app; Routes end
+
+ test "redirect escapes interpolated parameters with redirect proc" do
+ get "/foo/1%3E"
+ verify_redirect "http://www.example.com/foo/bar/1%3E"
+ end
+
+ test "redirect escapes interpolated parameters with option proc" do
+ get "/bar/1%3E"
+ verify_redirect "http://www.example.com/foo/bar/1%3E"
+ end
+
+private
+ def verify_redirect(url, status=301)
+ assert_equal status, @response.status
+ assert_equal url, @response.headers['Location']
+ assert_equal expected_redirect_body(url), @response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
+ end
+end
+
+class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/:foo" => ok, :constraints => lambda { |r| r.params[:foo] == 'foo' }
+ get "/:bar" => ok
+ end
+ end
+
+ def app; Routes end
+
+ test "parameters are reset between constraint checks" do
+ get "/bar"
+ assert_equal nil, @request.params[:foo]
+ assert_equal "bar", @request.params[:bar]
+ end
+end
+
+class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ get '/foo' => ok, as: :foo
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'enabled when not mounted and default_url_options is empty' do
+ assert Routes.url_helpers.optimize_routes_generation?
+ end
+
+ test 'named route called as singleton method' do
+ assert_equal '/foo', Routes.url_helpers.foo_path
+ end
+
+ test 'named route called on included module' do
+ assert_equal '/foo', foo_path
+ end
+end
+
+class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
+ class CategoriesController < ActionController::Base
+ def show
+ render :text => "categories#show"
+ end
+ end
+
+ class ProductsController < ActionController::Base
+ def show
+ render :text => "products#show"
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ scope :module => "test_named_route_url_helpers" do
+ get "/categories/:id" => 'categories#show', :as => :category
+ get "/products/:id" => 'products#show', :as => :product
+ end
+ end
+ end
+
+ def app; Routes end
+
+ include Routes.url_helpers
+
+ test "url helpers do not ignore nil parameters when using non-optimized routes" do
+ Routes.stubs(:optimize_routes_generation?).returns(false)
+
+ get "/categories/1"
+ assert_response :success
+ assert_raises(ActionController::RoutingError) { product_path(nil) }
+ end
+end
+
+class TestUrlConstraints < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ constraints :subdomain => 'admin' do
+ get '/' => ok, :as => :admin_root
+ end
+
+ scope :constraints => { :protocol => 'https://' } do
+ get '/' => ok, :as => :secure_root
+ end
+
+ get '/' => ok, :as => :alternate_root, :constraints => { :port => 8080 }
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test "constraints are copied to defaults when using constraints method" do
+ assert_equal 'http://admin.example.com/', admin_root_url
+
+ get 'http://admin.example.com/'
+ assert_response :success
+ end
+
+ test "constraints are copied to defaults when using scope constraints hash" do
+ assert_equal 'https://www.example.com/', secure_root_url
+
+ get 'https://www.example.com/'
+ assert_response :success
+ end
+
+ test "constraints are copied to defaults when using route constraints hash" do
+ assert_equal 'http://www.example.com:8080/', alternate_root_url
+
+ get 'http://www.example.com:8080/'
+ assert_response :success
+ end
+end
diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb
new file mode 100644
index 0000000000..8daf3d3f5e
--- /dev/null
+++ b/actionpack/test/dispatch/session/abstract_store_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ module Session
+ class AbstractStoreTest < ActiveSupport::TestCase
+ class MemoryStore < AbstractStore
+ def initialize(app)
+ @sessions = {}
+ super
+ end
+
+ def get_session(env, sid)
+ sid ||= 1
+ session = @sessions[sid] ||= {}
+ [sid, session]
+ end
+
+ def set_session(env, sid, session, options)
+ @sessions[sid] = session
+ end
+ end
+
+ def test_session_is_set
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ assert Request::Session.find @env
+ end
+
+ def test_new_session_object_is_merged_with_old
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ session = Request::Session.find @env
+ session['foo'] = 'bar'
+
+ as.call(@env)
+ session1 = Request::Session.find @env
+
+ refute_equal session, session1
+ assert_equal session.to_hash, session1.to_hash
+ end
+
+ private
+ def app(&block)
+ @env = nil
+ lambda { |env| @env = env }
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index 12405bf45d..a74e165826 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -164,7 +164,7 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match ':action', :to => ::CacheStoreTest::TestController
+ get ':action', :to => ::CacheStoreTest::TestController
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 19969394cd..631974d6c4 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -317,7 +317,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
- match ':action', :to => ::CookieStoreTest::TestController
+ get ':action', :to => ::CookieStoreTest::TestController
end
options = { :key => SessionKey }.merge!(options)
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 5277c92b55..03234612ab 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -173,7 +173,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match ':action', :to => ::MemCacheStoreTest::TestController
+ get ':action', :to => ::MemCacheStoreTest::TestController
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index 2b54bc62b0..e56e8ddc57 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module TestUrlGeneration
class WithMountPoint < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
- Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo }
+ include Routes.url_helpers
class ::MyRouteGeneratingController < ActionController::Base
include Routes.url_helpers
@@ -12,7 +12,11 @@ module TestUrlGeneration
end
end
- include Routes.url_helpers
+ Routes.draw do
+ get "/foo", :to => "my_route_generating#index", :as => :foo
+
+ mount MyRouteGeneratingController.action(:index), at: '/bar'
+ end
def _routes
Routes
@@ -30,11 +34,16 @@ module TestUrlGeneration
assert_equal "/bar/foo", foo_path(:script_name => "/bar")
end
- test "the request's SCRIPT_NAME takes precedence over the routes'" do
+ test "the request's SCRIPT_NAME takes precedence over the route" do
get "/foo", {}, 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes
assert_equal "/new/foo", response.body
end
+ test "the request's SCRIPT_NAME wraps the mounted app's" do
+ get '/new/bar/foo', {}, 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes
+ assert_equal "/new/bar/foo", response.body
+ end
+
test "handling http protocol with https set" do
https!
assert_equal "http://www.example.com/foo", foo_url(:protocol => "http")
diff --git a/actionpack/test/fixtures/plain_text.raw b/actionpack/test/fixtures/plain_text.raw
new file mode 100644
index 0000000000..b13985337f
--- /dev/null
+++ b/actionpack/test/fixtures/plain_text.raw
@@ -0,0 +1 @@
+<%= hello_world %>
diff --git a/actionpack/test/fixtures/plain_text_with_characters.raw b/actionpack/test/fixtures/plain_text_with_characters.raw
new file mode 100644
index 0000000000..1e86e44fb4
--- /dev/null
+++ b/actionpack/test/fixtures/plain_text_with_characters.raw
@@ -0,0 +1 @@
+Here are some characters: !@#$%^&*()-="'}{`
diff --git a/actionpack/test/fixtures/routes/bogus.rb b/actionpack/test/fixtures/routes/bogus.rb
new file mode 100644
index 0000000000..41fbf0cd64
--- /dev/null
+++ b/actionpack/test/fixtures/routes/bogus.rb
@@ -0,0 +1 @@
+wrong :route
diff --git a/actionpack/test/fixtures/routes/external.rb b/actionpack/test/fixtures/routes/external.rb
new file mode 100644
index 0000000000..d103c39f53
--- /dev/null
+++ b/actionpack/test/fixtures/routes/external.rb
@@ -0,0 +1 @@
+get '/external' => 'external#index'
diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb
new file mode 100644
index 0000000000..bdd53014cd
--- /dev/null
+++ b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb
@@ -0,0 +1 @@
+<b class="<%= customer.name.downcase %>"><%= yield %></b> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb
new file mode 100644
index 0000000000..44d6121297
--- /dev/null
+++ b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb
@@ -0,0 +1 @@
+<b data-counter="<%= customer_counter %>"><%= yield %></b> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_world_with_partial.html.erb b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
new file mode 100644
index 0000000000..ec31545356
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
@@ -0,0 +1,2 @@
+Hello world!
+<%= render '/test/partial' %>
diff --git a/actionpack/test/fixtures/translations/templates/default.erb b/actionpack/test/fixtures/translations/templates/default.erb
new file mode 100644
index 0000000000..8b70031071
--- /dev/null
+++ b/actionpack/test/fixtures/translations/templates/default.erb
@@ -0,0 +1 @@
+<%= t('.missing', :default => :'.foo') %>
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 58ff055fc2..7cc567c72d 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -102,20 +102,20 @@ class AssetTagHelperTest < ActionView::TestCase
}
JavascriptIncludeToTag = {
- %(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>),
- %(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>),
- %(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" type="text/javascript"></script>),
- %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
- %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:defaults, "application")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
-
- %(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>),
+ %(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" ></script>),
+ %(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" ></script>),
+ %(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" ></script>),
+ %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" ></script>\n<script src="/elsewhere/cools.js" ></script>),
+ %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" ></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:defaults, "application")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>),
+
+ %(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all"></script>),
+ %(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js"></script>),
+ %(javascript_include_tag("//example.com/all.js")) => %(<script src="//example.com/all.js"></script>),
}
StylePathToTag = {
@@ -147,19 +147,19 @@ class AssetTagHelperTest < ActionView::TestCase
}
StyleLinkToTag = {
- %(stylesheet_link_tag("bank")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("bank.css")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("random.styles", "/elsewhere/file")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />\n<link href="/elsewhere/file.css" media="screen" rel="stylesheet" type="text/css" />),
-
- %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" 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" />),
+ %(stylesheet_link_tag("bank")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("bank.css")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />),
+ %(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" />),
+ %(stylesheet_link_tag("random.styles", "/elsewhere/file")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />\n<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />),
+
+ %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("//www.example.com/styles/style.css")) => %(<link href="//www.example.com/styles/style.css" media="screen" rel="stylesheet" />),
}
ImagePathToTag = {
@@ -203,9 +203,9 @@ class AssetTagHelperTest < ActionView::TestCase
%(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" />)
+ %(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />),
+ %(image_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :alt => nil)) => %(<img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" />),
+ %(image_tag("")) => %(<img src="" />)
}
FaviconLinkToTag = {
@@ -340,7 +340,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_javascript_include_tag_with_given_asset_id
ENV["RAILS_ASSET_ID"] = "1"
- assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/rails.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))
+ assert_dom_equal(%(<script src="/javascripts/prototype.js?1"></script>\n<script src="/javascripts/effects.js?1"></script>\n<script src="/javascripts/dragdrop.js?1"></script>\n<script src="/javascripts/controls.js?1"></script>\n<script src="/javascripts/rails.js?1"></script>\n<script src="/javascripts/application.js?1"></script>), javascript_include_tag(:defaults))
end
def test_javascript_include_tag_is_html_safe
@@ -351,35 +351,35 @@ class AssetTagHelperTest < ActionView::TestCase
def test_custom_javascript_expansions
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>), javascript_include_tag('controls', :robbery, 'effects')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects')
end
def test_custom_javascript_expansions_return_unique_set
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => %w(prototype effects dragdrop controls rails application)
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
+ assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults)
end
def test_custom_javascript_expansions_and_defaults_puts_application_js_at_the_end
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects')
end
def test_javascript_include_tag_should_not_output_the_same_asset_twice
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('prototype', 'effects', :defaults)
+ assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('prototype', 'effects', :defaults)
end
def test_javascript_include_tag_should_not_output_the_same_expansion_twice
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults, :defaults)
+ assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults, :defaults)
end
def test_single_javascript_asset_keys_should_take_precedence_over_expansions
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls', :defaults, 'effects')
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls', 'effects', :defaults)
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', :defaults, 'effects')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', 'effects', :defaults)
end
def test_registering_javascript_expansions_merges_with_existing_expansions
@@ -387,7 +387,7 @@ class AssetTagHelperTest < ActionView::TestCase
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank']
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['robber']
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank']
- assert_dom_equal %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>), javascript_include_tag(:can_merge)
+ assert_dom_equal %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>), javascript_include_tag(:can_merge)
end
def test_custom_javascript_expansions_with_undefined_symbol
@@ -396,20 +396,20 @@ class AssetTagHelperTest < ActionView::TestCase
def test_custom_javascript_expansions_with_nil_value
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => nil
- assert_dom_equal %(<script src="/javascripts/first.js" type="text/javascript"></script>\n<script src="/javascripts/last.js" type="text/javascript"></script>), javascript_include_tag('first', :monkey, 'last')
+ assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last')
end
def test_custom_javascript_expansions_with_empty_array_value
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => []
- assert_dom_equal %(<script src="/javascripts/first.js" type="text/javascript"></script>\n<script src="/javascripts/last.js" type="text/javascript"></script>), javascript_include_tag('first', :monkey, 'last')
+ assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last')
end
def test_custom_javascript_and_stylesheet_expansion_with_same_name
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["money", "security"]
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>), javascript_include_tag('controls', :robbery, 'effects')
- assert_dom_equal %(<link href="/stylesheets/style.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/money.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/security.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/print.css" rel="stylesheet" type="text/css" media="screen" />), stylesheet_link_tag('style', :robbery, 'print')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects')
+ assert_dom_equal %(<link href="/stylesheets/style.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/money.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/security.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/print.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('style', :robbery, 'print')
end
def test_reset_javascript_expansions
@@ -457,36 +457,36 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_stylesheet_link_tag_escapes_options
- assert_dom_equal %(<link href="/file.css" media="&lt;script&gt;" rel="stylesheet" type="text/css" />), stylesheet_link_tag('/file', :media => '<script>')
+ assert_dom_equal %(<link href="/file.css" media="&lt;script&gt;" rel="stylesheet" />), stylesheet_link_tag('/file', :media => '<script>')
end
def test_custom_stylesheet_expansions
ENV["RAILS_ASSET_ID"] = ''
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["bank", "robber"]
- assert_dom_equal %(<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag('version.1.0', :robbery, 'subdir/subdir')
+ assert_dom_equal %(<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('version.1.0', :robbery, 'subdir/subdir')
end
def test_custom_stylesheet_expansions_return_unique_set
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london)
- assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:cities)
+ assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities)
end
def test_stylesheet_link_tag_should_not_output_the_same_asset_twice
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam')
+ assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam')
end
def test_stylesheet_link_tag_should_not_output_the_same_expansion_twice
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london)
- assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:cities, :cities)
+ assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities, :cities)
end
def test_single_stylesheet_asset_keys_should_take_precedence_over_expansions
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london)
- assert_dom_equal %(<link href="/stylesheets/london.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag('london', :cities)
+ assert_dom_equal %(<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('london', :cities)
end
def test_custom_stylesheet_expansions_with_unknown_symbol
@@ -495,12 +495,12 @@ class AssetTagHelperTest < ActionView::TestCase
def test_custom_stylesheet_expansions_with_nil_value
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => nil
- assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" type="text/css" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
+ assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
end
def test_custom_stylesheet_expansions_with_empty_array_value
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => []
- assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" type="text/css" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
+ assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
end
def test_registering_stylesheet_expansions_merges_with_existing_expansions
@@ -508,7 +508,7 @@ class AssetTagHelperTest < ActionView::TestCase
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank']
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['robber']
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank']
- assert_dom_equal %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:can_merge)
+ assert_dom_equal %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:can_merge)
end
def test_image_path
@@ -703,21 +703,21 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/all.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/all.js"></script>),
javascript_include_tag(:all, :cache => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/money.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/money.js"></script>),
javascript_include_tag(:all, :cache => "money")
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
assert_dom_equal(
- %(<script src="http://a0.example.com/absolute/test.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/absolute/test.js"></script>),
javascript_include_tag(:all, :cache => "/absolute/test")
)
@@ -736,7 +736,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/scripts.js'.length, 23
assert_dom_equal(
- %(<script src="http://a23.example.com/javascripts/scripts.js" type="text/javascript"></script>),
+ %(<script src="http://a23.example.com/javascripts/scripts.js"></script>),
javascript_include_tag(:all, :cache => 'scripts')
)
@@ -759,7 +759,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/vanilla.js'.length, 23
assert_dom_equal(
- %(<script src="http://assets23.example.com/javascripts/vanilla.js" type="text/javascript"></script>),
+ %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>),
javascript_include_tag(:all, :cache => 'vanilla')
)
@@ -772,7 +772,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/secure.js'.length, 22
assert_dom_equal(
- %(<script src="https://localhost/javascripts/secure.js" type="text/javascript"></script>),
+ %(<script src="https://localhost/javascripts/secure.js"></script>),
javascript_include_tag(:all, :cache => 'secure')
)
@@ -799,7 +799,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/vanilla.js'.length, 23
assert_dom_equal(
- %(<script src="http://assets23.example.com/javascripts/vanilla.js" type="text/javascript"></script>),
+ %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>),
javascript_include_tag(:all, :cache => 'vanilla')
)
@@ -812,7 +812,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/secure.js'.length, 22
assert_dom_equal(
- %(<script src="https://localhost/javascripts/secure.js" type="text/javascript"></script>),
+ %(<script src="https://localhost/javascripts/secure.js"></script>),
javascript_include_tag(:all, :cache => 'secure')
)
@@ -830,7 +830,7 @@ class AssetTagHelperTest < ActionView::TestCase
number = Zlib.crc32('/javascripts/cache/money.js') % 4
assert_dom_equal(
- %(<script src="http://a#{number}.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
+ %(<script src="http://a#{number}.example.com/javascripts/cache/money.js"></script>),
javascript_include_tag(:all, :cache => "cache/money")
)
@@ -845,7 +845,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/combined.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/combined.js"></script>),
javascript_include_tag(:all, :cache => "combined", :recursive => true)
)
@@ -866,7 +866,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/combined.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/combined.js"></script>),
javascript_include_tag(:all, :cache => "combined")
)
@@ -887,14 +887,14 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/all.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/all.js"></script>),
javascript_include_tag(:all, :cache => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/money.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/money.js"></script>),
javascript_include_tag(:all, :cache => "money")
)
@@ -912,14 +912,14 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/all.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/all.js"></script>),
javascript_include_tag(:all, :cache => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/money.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/money.js"></script>),
javascript_include_tag(:all, :cache => "money")
)
@@ -936,14 +936,14 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
javascript_include_tag('robber', :cache => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
javascript_include_tag('robber', :cache => "money", :recursive => true)
)
@@ -957,14 +957,14 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
javascript_include_tag('robber', :cache => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
javascript_include_tag('robber', :cache => "money", :recursive => true)
)
@@ -976,24 +976,24 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => true)
)
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => true, :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => "money")
)
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => "money", :recursive => true)
)
@@ -1051,7 +1051,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<link href="http://a0.example.com/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a0.example.com/stylesheets/all.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true)
)
@@ -1065,14 +1065,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal expected_size, File.size(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money")
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
assert_dom_equal(
- %(<link href="http://a0.example.com/absolute/test.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a0.example.com/absolute/test.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "/absolute/test")
)
@@ -1087,7 +1087,7 @@ class AssetTagHelperTest < ActionView::TestCase
ENV["RAILS_ASSET_ID"] = ""
assert_dom_equal(
- %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :concat => true)
)
@@ -1095,14 +1095,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal expected, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/money.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :concat => "money")
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
assert_dom_equal(
- %(<link href="/absolute/test.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/absolute/test.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :concat => "/absolute/test")
)
@@ -1162,7 +1162,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/stylesheets/styles.css'.length, 23
assert_dom_equal(
- %(<link href="http://a23.example.com/stylesheets/styles.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a23.example.com/stylesheets/styles.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => 'styles')
)
@@ -1178,7 +1178,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true)
)
@@ -1188,7 +1188,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money")
)
@@ -1205,7 +1205,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true)
)
@@ -1215,7 +1215,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money")
)
@@ -1232,14 +1232,14 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag('robber', :cache => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag('robber', :cache => "money")
)
@@ -1253,14 +1253,14 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag('robber', :cache => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag('robber', :cache => "money")
)
@@ -1275,24 +1275,24 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true)
)
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true, :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money")
)
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money", :recursive => true)
)
@@ -1323,8 +1323,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
assert_dom_equal(%(/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
assert_dom_equal(%(/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='/collaboration/hieraki/images/mouse.png'" src="/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
- assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='/collaboration/hieraki/images/mouse2.png'" src="/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
def test_should_ignore_relative_root_path_on_complete_url
@@ -1337,8 +1335,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
- assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
def test_should_compute_proper_path_with_asset_host_and_default_protocol
@@ -1347,8 +1343,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
- assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
def test_should_compute_proper_url_with_asset_host
@@ -1369,12 +1363,12 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
def test_should_ignore_asset_host_on_complete_url
@controller.config.asset_host = "http://assets.example.com"
- assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css"))
+ assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" />), 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"))
+ assert_dom_equal(%(<link href="//bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("//bar.example.com/stylesheets/style.css"))
end
def test_should_wildcard_asset_host_between_zero_and_four
diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb
index d26aa9aa85..89aae4ac56 100644
--- a/actionpack/test/template/atom_feed_helper_test.rb
+++ b/actionpack/test/template/atom_feed_helper_test.rb
@@ -45,6 +45,23 @@ class ScrollsController < ActionController::Base
end
end
EOT
+ FEEDS["entry_type_options"] = <<-EOT
+ atom_feed(:schema_date => '2008') do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ @scrolls.each do |scroll|
+ feed.entry(scroll, :type => 'text/xml') do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
FEEDS["xml_block"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
@@ -306,6 +323,20 @@ class AtomFeedTest < ActionController::TestCase
end
end
+ def test_feed_entry_type_option_default_to_text_html
+ with_restful_routing(:scrolls) do
+ get :index, :id => 'defaults'
+ assert_select "entry link[rel=alternate][type=text/html]"
+ end
+ end
+
+ def test_feed_entry_type_option_specified
+ with_restful_routing(:scrolls) do
+ get :index, :id => 'entry_type_options'
+ assert_select "entry link[rel=alternate][type=text/xml]"
+ end
+ end
+
private
def with_restful_routing(resources)
with_routing do |set|
diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb
index 1bdda22959..8c198d2562 100644
--- a/actionpack/test/template/benchmark_helper_test.rb
+++ b/actionpack/test/template/benchmark_helper_test.rb
@@ -19,6 +19,6 @@ class BenchmarkHelperTest < ActionView::TestCase
log = StringIO.new
self.stubs(:logger).returns(Logger.new(log))
benchmark {}
- assert_match(log.rewind && log.read, /Benchmarking \(\d+.\d+ms\)/)
+ assert_match(/Benchmarking \(\d+.\d+ms\)/, log.rewind && log.read)
end
end
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index 82b62e64f3..63066d40cd 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -12,24 +12,24 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase
def test_distance_of_time_in_words_calls_i18n
{ # with include_seconds
- [2.seconds, true] => [:'less_than_x_seconds', 5],
- [9.seconds, true] => [:'less_than_x_seconds', 10],
- [19.seconds, true] => [:'less_than_x_seconds', 20],
- [30.seconds, true] => [:'half_a_minute', nil],
- [59.seconds, true] => [:'less_than_x_minutes', 1],
- [60.seconds, true] => [:'x_minutes', 1],
+ [2.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 5],
+ [9.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 10],
+ [19.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 20],
+ [30.seconds, { :include_seconds => true }] => [:'half_a_minute', nil],
+ [59.seconds, { :include_seconds => true }] => [:'less_than_x_minutes', 1],
+ [60.seconds, { :include_seconds => true }] => [:'x_minutes', 1],
# without include_seconds
- [29.seconds, false] => [:'less_than_x_minutes', 1],
- [60.seconds, false] => [:'x_minutes', 1],
- [44.minutes, false] => [:'x_minutes', 44],
- [61.minutes, false] => [:'about_x_hours', 1],
- [24.hours, false] => [:'x_days', 1],
- [30.days, false] => [:'about_x_months', 1],
- [60.days, false] => [:'x_months', 2],
- [1.year, false] => [:'about_x_years', 1],
- [3.years + 6.months, false] => [:'over_x_years', 3],
- [3.years + 10.months, false] => [:'almost_x_years', 4]
+ [29.seconds, { :include_seconds => false }] => [:'less_than_x_minutes', 1],
+ [60.seconds, { :include_seconds => false }] => [:'x_minutes', 1],
+ [44.minutes, { :include_seconds => false }] => [:'x_minutes', 44],
+ [61.minutes, { :include_seconds => false }] => [:'about_x_hours', 1],
+ [24.hours, { :include_seconds => false }] => [:'x_days', 1],
+ [30.days, { :include_seconds => false }] => [:'about_x_months', 1],
+ [60.days, { :include_seconds => false }] => [:'x_months', 2],
+ [1.year, { :include_seconds => false }] => [:'about_x_years', 1],
+ [3.years + 6.months, { :include_seconds => false }] => [:'over_x_years', 3],
+ [3.years + 10.months, { :include_seconds => false }] => [:'almost_x_years', 4]
}.each do |passed, expected|
assert_distance_of_time_in_words_translates_key passed, expected
@@ -37,7 +37,7 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase
end
def assert_distance_of_time_in_words_translates_key(passed, expected)
- diff, include_seconds = *passed
+ diff, passed_options = *passed
key, count = *expected
to = @from + diff
@@ -45,7 +45,12 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase
options[:count] = count if count
I18n.expects(:t).with(key, options)
- distance_of_time_in_words(@from, to, include_seconds, :locale => 'en')
+ distance_of_time_in_words(@from, to, passed_options.merge(:locale => 'en'))
+ end
+
+ def test_time_ago_in_words_passes_locale
+ I18n.expects(:t).with(:less_than_x_minutes, :scope => :'datetime.distance_in_words', :count => 1, :locale => 'ru')
+ time_ago_in_words(15.seconds.ago, :locale => 'ru')
end
def test_distance_of_time_pluralizations
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index c9b8a5bb70..ff85a675a2 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -21,56 +21,80 @@ class DateHelperTest < ActionView::TestCase
def assert_distance_of_time_in_words(from, to=nil)
to ||= from
- # 0..1 with include_seconds
- assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, true)
- assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, true)
- assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, true)
- assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, true)
- assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, true)
- assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, true)
- assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, true)
- assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, true)
- assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, true)
- assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, true)
- assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, true)
- assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, true)
-
- # First case 0..1
+ # 0..1 minute with :include_seconds => true
+ assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => true)
+ assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => true)
+ assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => true)
+ assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => true)
+ assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => true)
+ assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => true)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => true)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => true)
+
+ # 0..1 minute with :include_seconds => false
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => false)
+
+ # Note that we are including a 30-second boundary around the interval we
+ # want to test. For instance, "1 minute" is actually 30s to 1m29s. The
+ # reason for doing this is simple -- in `distance_of_time_to_words`, when we
+ # take the distance between our two Time objects in seconds and convert it
+ # to minutes, we round the number. So 29s gets rounded down to 0m, 30s gets
+ # rounded up to 1m, and 1m29s gets rounded down to 1m. A similar thing
+ # happens with the other cases.
+
+ # First case 0..1 minute
assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds)
assert_equal "less than a minute", distance_of_time_in_words(from, to + 29.seconds)
assert_equal "1 minute", distance_of_time_in_words(from, to + 30.seconds)
assert_equal "1 minute", distance_of_time_in_words(from, to + 1.minutes + 29.seconds)
- # 2..44
+ # 2 minutes up to 45 minutes
assert_equal "2 minutes", distance_of_time_in_words(from, to + 1.minutes + 30.seconds)
assert_equal "44 minutes", distance_of_time_in_words(from, to + 44.minutes + 29.seconds)
- # 45..89
+ # 45 minutes up to 90 minutes
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 44.minutes + 30.seconds)
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 89.minutes + 29.seconds)
- # 90..1439
+ # 90 minutes up to 24 hours
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..2519
+ # 24 hours up to 42 hours
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)
- # 2520..43199
+ # 42 hours up to 30 days
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)
- # 43200..86399
+ # 30 days up to 60 days
assert_equal "about 1 month", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 30.seconds)
- assert_equal "about 1 month", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 29.seconds)
+ assert_equal "about 1 month", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 29.seconds)
+ assert_equal "about 2 months", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 30.seconds)
+ assert_equal "about 2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 29.seconds)
- # 86400..525599
+ # 60 days up to 365 days
assert_equal "2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 30.seconds)
assert_equal "12 months", distance_of_time_in_words(from, to + 1.years - 31.seconds)
- # > 525599
+ # >= 365 days
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years - 30.seconds)
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years + 3.months - 1.day)
assert_equal "over 1 year", distance_of_time_in_words(from, to + 1.years + 6.months)
@@ -95,7 +119,8 @@ class DateHelperTest < ActionView::TestCase
# test to < from
assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to)
- assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => false)
end
def test_distance_in_words
@@ -103,6 +128,11 @@ class DateHelperTest < ActionView::TestCase
assert_distance_of_time_in_words(from)
end
+ def test_time_ago_in_words_passes_include_seconds
+ assert_equal "less than 20 seconds", time_ago_in_words(15.seconds.ago, :include_seconds => true)
+ assert_equal "less than a minute", time_ago_in_words(15.seconds.ago, :include_seconds => false)
+ end
+
def test_distance_in_words_with_time_zones
from = Time.mktime(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from.in_time_zone('Alaska'))
@@ -125,13 +155,33 @@ class DateHelperTest < ActionView::TestCase
start_date = Date.new 1982, 12, 3
end_date = Date.new 2010, 11, 30
assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date))
+ assert_equal("almost 28 years", distance_of_time_in_words(end_date, start_date))
end
def test_distance_in_words_with_integers
- assert_equal "less than a minute", distance_of_time_in_words(59)
+ assert_equal "1 minute", distance_of_time_in_words(59)
assert_equal "about 1 hour", distance_of_time_in_words(60*60)
- assert_equal "less than a minute", distance_of_time_in_words(0, 59)
+ assert_equal "1 minute", distance_of_time_in_words(0, 59)
assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0)
+ assert_equal "about 3 years", distance_of_time_in_words(10**8)
+ assert_equal "about 3 years", distance_of_time_in_words(0, 10**8)
+ end
+
+ def test_distance_in_words_with_times
+ assert_equal "1 minute", distance_of_time_in_words(30.seconds)
+ assert_equal "1 minute", distance_of_time_in_words(59.seconds)
+ assert_equal "2 minutes", distance_of_time_in_words(119.seconds)
+ assert_equal "2 minutes", distance_of_time_in_words(1.minute + 59.seconds)
+ assert_equal "3 minutes", distance_of_time_in_words(2.minute + 30.seconds)
+ assert_equal "44 minutes", distance_of_time_in_words(44.minutes + 29.seconds)
+ assert_equal "about 1 hour", distance_of_time_in_words(44.minutes + 30.seconds)
+ assert_equal "about 1 hour", distance_of_time_in_words(60.minutes)
+
+ # include seconds
+ assert_equal "half a minute", distance_of_time_in_words(39.seconds, 0, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(40.seconds, 0, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(59.seconds, 0, :include_seconds => true)
+ assert_equal "1 minute", distance_of_time_in_words(60.seconds, 0, :include_seconds => true)
end
def test_time_ago_in_words
@@ -567,7 +617,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_html_options
- expected = expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n)
+ expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -948,6 +998,15 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
end
+ def test_select_date_with_hidden
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003"/>\n)
+ expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :prefix => "date[first]", :use_hidden => true })
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :prefix => "date[first]", :use_hidden => true })
+ end
+
def test_select_datetime
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1184,6 +1243,18 @@ class DateHelperTest < ActionView::TestCase
:prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'})
end
+ def test_select_datetime_with_hidden
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
+ expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+ expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" />\n)
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :prefix => "date[first]", :use_hidden => true)
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :datetime_separator => "&mdash;", :date_separator => "/",
+ :time_separator => ":", :prefix => "date[first]", :use_hidden => true)
+ end
+
def test_select_time
expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
@@ -1359,6 +1430,17 @@ class DateHelperTest < ActionView::TestCase
:prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
end
+ def test_select_time_with_hidden
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
+ expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+ expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" />\n)
+
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :prefix => "date[first]", :use_hidden => true)
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :time_separator => ":", :prefix => "date[first]", :use_hidden => true)
+ end
+
def test_date_select
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb
index a384e94766..1724d6432d 100644
--- a/actionpack/test/template/erb/tag_helper_test.rb
+++ b/actionpack/test/template/erb/tag_helper_test.rb
@@ -11,12 +11,12 @@ module ERBTest
end
test "percent equals works for javascript_tag" do
- expected_output = "<script type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
+ expected_output = "<script>\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
assert_equal expected_output, render_content("javascript_tag", "alert('Hello')")
end
test "percent equals works for javascript_tag with options" do
- expected_output = "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
+ expected_output = "<script id=\"the_js_tag\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
assert_equal expected_output, render_content("javascript_tag(:id => 'the_js_tag')", "alert('Hello')")
end
@@ -30,4 +30,4 @@ module ERBTest
assert_equal expected_output, render_content("field_set_tag('foo')", "<%= 'hello' %>")
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionpack/test/template/form_collections_helper_test.rb
index 4d878635ef..c73e80ed88 100644
--- a/actionpack/test/template/form_collections_helper_test.rb
+++ b/actionpack/test/template/form_collections_helper_test.rb
@@ -195,6 +195,15 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=checkbox][value=2][checked=checked]'
end
+ test 'collection check boxes accepts selected string values as :checked option' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => ['1', '3']
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
test 'collection check boxes accepts a single checked value' do
collection = (1..3).map{|i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => 3
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 7b684822fc..27cc3ad48a 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -97,7 +97,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- match "/foo", :to => "controller#action"
+ get "/foo", :to => "controller#action"
root :to => "main#index"
end
@@ -350,36 +350,52 @@ class FormHelperTest < ActionView::TestCase
text_field("user", "email", :type => "email")
end
- def test_check_box
+ def test_check_box_is_html_safe
assert check_box("post", "secret").html_safe?
+ end
+
+ def test_check_box_checked_if_object_value_is_same_that_check_value
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+ end
+
+ def test_check_box_not_checked_if_object_value_is_same_that_unchecked_value
@post.secret = 0
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+ end
+
+ def test_check_box_checked_if_option_checked_is_present
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret" ,{"checked"=>"checked"})
)
+ end
+
+ def test_check_box_checked_if_object_value_is_true
@post.secret = true
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret?")
)
+ end
+ def test_check_box_checked_if_object_value_includes_checked_value
@post.secret = ['0']
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+
@post.secret = ['1']
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
@@ -392,12 +408,92 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret", :include_hidden => false))
end
- def test_check_box_with_explicit_checked_and_unchecked_values
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_string
@post.secret = "on"
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="off" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="on" />',
check_box("post", "secret", {}, "on", "off")
)
+
+ @post.secret = "off"
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="off" /><input id="post_secret" name="post[secret]" type="checkbox" value="on" />',
+ check_box("post", "secret", {}, "on", "off")
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_boolean
+ @post.secret = false
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="true" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="false" />',
+ check_box("post", "secret", {}, false, true)
+ )
+
+ @post.secret = true
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="true" /><input id="post_secret" name="post[secret]" type="checkbox" value="false" />',
+ check_box("post", "secret", {}, false, true)
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_integer
+ @post.secret = 0
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 1
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 2
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_float
+ @post.secret = 0.0
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 1.1
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 2.2
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_big_decimal
+ @post.secret = BigDecimal.new(0)
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = BigDecimal.new(1)
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = BigDecimal.new(2.2, 1)
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
end
def test_check_box_with_nil_unchecked_value
@@ -550,6 +646,32 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, date_field("post", "written_on"))
end
+ def test_time_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="00:00:00.000" />}
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
+ def test_time_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
+ def test_time_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />}
+ @post.written_on = Time.zone.parse('2004-06-15 01:02:03')
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_time_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
def test_url_field
expected = %{<input id="user_homepage" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))
@@ -1080,6 +1202,20 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_label_error_wrapping_block_and_non_block_versions
+ form_for(@post) do |f|
+ concat f.label(:author_name, 'Name', :class => 'label')
+ concat f.label(:author_name, :class => 'label') { 'Name' }
+ end
+
+ expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" +
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_namespace
form_for(@post, :namespace => 'namespace') do |f|
concat f.text_field(:title)
@@ -1821,6 +1957,56 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_nested_fields_for_index_method_with_existing_records_on_a_nested_attributes_collection_association
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_for(@post) do |f|
+ expected = 0
+ @post.comments.each do |comment|
+ f.fields_for(:comments, comment) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+ end
+
+ def test_nested_fields_for_index_method_with_existing_and_new_records_on_a_nested_attributes_collection_association
+ @post.comments = [Comment.new(321), Comment.new]
+
+ form_for(@post) do |f|
+ expected = 0
+ @post.comments.each do |comment|
+ f.fields_for(:comments, comment) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+ end
+
+ def test_nested_fields_for_index_method_with_existing_records_on_a_supplied_nested_attributes_collection
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_for(@post) do |f|
+ expected = 0
+ f.fields_for(:comments, @post.comments) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+
+ def test_nested_fields_for_index_method_with_child_index_option_override_on_a_nested_attributes_collection_association
+ @post.comments = []
+
+ form_for(@post) do |f|
+ f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf|
+ assert_equal cf.index, 'abc'
+ }
+ end
+ end
+
def test_nested_fields_uses_unique_indices_for_different_collection_associations
@post.comments = [Comment.new(321)]
@post.tags = [Tag.new(123), Tag.new(456)]
@@ -2105,6 +2291,23 @@ class FormHelperTest < ActionView::TestCase
ActionView::Base.default_form_builder = old_default_form_builder
end
+ def test_lazy_loading_default_form_builder
+ old_default_form_builder, ActionView::Base.default_form_builder =
+ ActionView::Base.default_form_builder, "FormHelperTest::LabelledFormBuilder"
+
+ form_for(@post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Base.default_form_builder = old_default_form_builder
+ end
+
def test_fields_for_with_labelled_builder
output_buffer = fields_for(:post, @post, :builder => LabelledFormBuilder) do |f|
concat f.text_field(:title)
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 2c0da8473a..2322fb0406 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -296,10 +296,34 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_grouped_options_for_select_with_selected_and_prompt
+ def test_grouped_options_for_select_with_optional_divider
assert_dom_equal(
+ "<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>",
+
+ grouped_options_for_select([['US',"Canada"] , ["GB", "Germany"]], nil, divider: "----------")
+ )
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt_deprecated
+ assert_deprecated 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: "Choose a product..." }`.' do
+ assert_dom_equal(
"<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...")
+ )
+ end
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt
+ assert_dom_equal(
+ "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: "Choose a product...")
+ )
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt_true
+ assert_dom_equal(
+ "<option value=\"\">Please select</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: true)
)
end
@@ -307,10 +331,18 @@ class FormOptionsHelperTest < ActionView::TestCase
assert grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]]).html_safe?
end
+ def test_grouped_options_for_select_with_prompt_returns_html_escaped_string_deprecated
+ ActiveSupport::Deprecation.silence do
+ assert_dom_equal(
+ "<option value=\"\">&lt;Choose One&gt;</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>'))
+ end
+ end
+
def test_grouped_options_for_select_with_prompt_returns_html_escaped_string
assert_dom_equal(
"<option value=\"\">&lt;Choose One&gt;</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
- grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>'))
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, prompt: '<Choose One>'))
end
def test_optgroups_with_with_options_with_hash
@@ -634,6 +666,48 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_required_select
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required"><option value=""></option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true)
+ )
+ end
+
+ def test_required_select_with_include_blank_prompt
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required"><option value="">Select one</option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), { include_blank: "Select one" }, required: true)
+ )
+ end
+
+ def test_required_select_with_prompt
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required"><option value="">Select one</option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), { prompt: "Select one" }, required: true)
+ )
+ end
+
+ def test_required_select_display_size_equals_to_one
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required" size="1"><option value=""></option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true, size: 1)
+ )
+ end
+
+ def test_required_select_with_display_size_bigger_than_one
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required" size="2"><option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true, size: 2)
+ )
+ end
+
+ def test_required_select_with_multiple_option
+ assert_dom_equal(
+ %(<input name="post[category][]" type="hidden" value=""/><select id="post_category" multiple="multiple" name="post[category][]" required="required"><option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true, multiple: true)
+ )
+ end
+
def test_select_with_fixnum
@post = Post.new
@post.category = ""
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 1e92ff99ff..6574e13558 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -375,14 +375,7 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag
assert_dom_equal(
%(<input name='commit' data-disable-with="Saving..." onclick="alert('hello!')" type="submit" value="Save" />),
- submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
- )
- end
-
- def test_submit_tag_with_no_onclick_options
- assert_dom_equal(
- %(<input name='commit' data-disable-with="Saving..." type="submit" value="Save" />),
- submit_tag("Save", :disable_with => "Saving...")
+ submit_tag("Save", 'data-disable-with' => "Saving...", :onclick => "alert('hello!')")
)
end
@@ -393,13 +386,6 @@ class FormTagHelperTest < ActionView::TestCase
)
end
- def test_submit_tag_with_confirmation_and_with_disable_with
- assert_dom_equal(
- %(<input name="commit" data-disable-with="Saving..." data-confirm="Are you sure?" type="submit" value="Save" />),
- submit_tag("Save", :disable_with => "Saving...", :confirm => "Are you sure?")
- )
- end
-
def test_button_tag
assert_dom_equal(
%(<button name="button" type="submit">Button</button>),
@@ -473,6 +459,11 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, date_field_tag("cell"))
end
+ def test_time_field_tag
+ expected = %{<input id="cell" name="cell" type="time" />}
+ assert_dom_equal(expected, time_field_tag("cell"))
+ end
+
def test_url_field_tag
expected = %{<input id="homepage" name="homepage" type="url" />}
assert_dom_equal(expected, url_field_tag("homepage"))
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 64898f7ad1..fe7607ee26 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -42,47 +42,17 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given))
end
- def test_button_to_function
- assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />),
- button_to_function("Greeting", "alert('Hello world!')")
- end
-
- def test_button_to_function_with_onclick
- assert_dom_equal "<input onclick=\"alert('Goodbye World :('); alert('Hello world!');\" type=\"button\" value=\"Greeting\" />",
- button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')")
- end
-
- def test_button_to_function_without_function
- assert_dom_equal "<input onclick=\";\" type=\"button\" value=\"Greeting\" />",
- button_to_function("Greeting")
- end
-
- def test_link_to_function
- assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
- link_to_function("Greeting", "alert('Hello world!')")
- end
-
- def test_link_to_function_with_existing_onclick
- assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>),
- link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
- end
-
- def test_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_javascript_tag
self.output_buffer = 'foo'
- assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
+ assert_dom_equal "<script>\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
javascript_tag("alert('hello')")
assert_equal 'foo', output_buffer, 'javascript_tag without a block should not concat to output_buffer'
end
def test_javascript_tag_with_options
- assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
+ assert_dom_equal "<script id=\"the_js_tag\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
javascript_tag("alert('hello')", :id => "the_js_tag")
end
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 5c6f23d70b..14ca6d9879 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -78,6 +78,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '12,345,678-05', number_with_delimiter(12345678.05, :separator => '-')
assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :separator => ',', :delimiter => '.')
assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',')
+ assert_equal '1&lt;script&gt;&lt;/script&gt;01', number_with_delimiter(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000', number_with_delimiter(1000, :delimiter => "<script></script>")
end
def test_number_with_precision
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 122b07d348..88ed8664c2 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -21,9 +21,7 @@ module RenderTestCases
end
def test_render_without_options
- @view.render()
- flunk "Render did not raise ArgumentError"
- rescue ArgumentError => e
+ e = assert_raises(ArgumentError) { @view.render() }
assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message
end
@@ -81,6 +79,14 @@ module RenderTestCases
assert_equal "<h1>No Comment</h1>\n", @view.render(:template => "comments/empty", :handlers => [:builder])
end
+ def test_render_raw_template_with_handlers
+ assert_equal "<%= hello_world %>\n", @view.render(:template => "plain_text")
+ end
+
+ def test_render_raw_template_with_quotes
+ assert_equal %q;Here are some characters: !@#$%^&*()-="'}{`; + "\n", @view.render(:template => "plain_text_with_characters")
+ end
+
def test_render_file_with_localization_on_context_level
old_locale, @view.locale = @view.locale, :da
assert_equal "Hey verden", @view.render(:file => "test/hello_world")
@@ -153,25 +159,26 @@ module RenderTestCases
end
def test_render_partial_with_invalid_name
- @view.render(:partial => "test/200")
- flunk "Render did not raise ArgumentError"
- rescue ArgumentError => e
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") }
assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.", e.message
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_missing_filename
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/") }
+ assert_equal "The partial name (test/) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
end
def test_render_partial_with_incompatible_object
- @view.render(:partial => nil)
- flunk "Render did not raise ArgumentError"
- rescue ArgumentError => e
+ e = assert_raises(ArgumentError) { @view.render(:partial => nil) }
assert_equal "'#{nil.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.", e.message
end
def test_render_partial_with_errors
- @view.render(:partial => "test/raise")
- flunk "Render did not raise Template::Error"
- rescue ActionView::Template::Error => e
+ e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise") }
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "", e.sub_template_message
assert_equal "1", e.line_number
@@ -180,9 +187,7 @@ module RenderTestCases
end
def test_render_sub_template_with_errors
- @view.render(:template => "test/sub_template_raise")
- flunk "Render did not raise Template::Error"
- rescue ActionView::Template::Error => e
+ e = assert_raises(ActionView::Template::Error) { @view.render(:template => "test/sub_template_raise") }
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "Trace of template inclusion: #{File.expand_path("#{FIXTURE_LOAD_PATH}/test/sub_template_raise.html.erb")}", e.sub_template_message
assert_equal "1", e.line_number
@@ -190,9 +195,7 @@ module RenderTestCases
end
def test_render_file_with_errors
- @view.render(:file => File.expand_path("test/_raise", FIXTURE_LOAD_PATH))
- flunk "Render did not raise Template::Error"
- rescue ActionView::Template::Error => e
+ e = assert_raises(ActionView::Template::Error) { @view.render(:file => File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) }
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "", e.sub_template_message
assert_equal "1", e.line_number
@@ -238,11 +241,26 @@ module RenderTestCases
def test_render_partial_with_nil_values_in_collection
assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ])
end
-
+
def test_render_partial_with_layout_using_collection_and_template
assert_equal "<b>Hello: Amazon</b><b>Hello: Yahoo</b>", @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ])
end
+ def test_render_partial_with_layout_using_collection_and_template_makes_current_item_available_in_layout
+ assert_equal '<b class="amazon">Hello: Amazon</b><b class="yahoo">Hello: Yahoo</b>',
+ @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ])
+ end
+
+ def test_render_partial_with_layout_using_collection_and_template_makes_current_item_counter_available_in_layout
+ assert_equal '<b data-counter="0">Hello: Amazon</b><b data-counter="1">Hello: Yahoo</b>',
+ @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object_counter', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ])
+ end
+
+ def test_render_partial_with_layout_using_object_and_template_makes_object_available_in_layout
+ assert_equal '<b class="amazon">Hello: Amazon</b>',
+ @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :object => Customer.new("Amazon"))
+ end
+
def test_render_partial_with_empty_array_should_return_nil
assert_nil @view.render(:partial => [])
end
@@ -274,7 +292,7 @@ module RenderTestCases
# TODO: The reason for this test is unclear, improve documentation
def test_render_missing_xml_partial_and_raise_missing_template
@view.formats = [:xml]
- assert_raise(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") }
+ assert_raises(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") }
ensure
@view.formats = nil
end
@@ -315,7 +333,7 @@ module RenderTestCases
ActionView::Template.register_template_handler :foo, CustomHandler
assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
-
+
def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization
ActionView::Template::Handlers.extensions
ActionView::Template.register_template_handler :foo, CustomHandler
@@ -325,7 +343,7 @@ module RenderTestCases
def test_render_ignores_templates_with_malformed_template_handlers
ActiveSupport::Deprecation.silence do
%w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
- assert_raise(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") }
+ assert_raises(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") }
end
end
end
@@ -450,23 +468,15 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_incompatible_external_encoding
with_external_encoding Encoding::SHIFT_JIS do
- begin
- @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield")
- flunk 'Should have raised incompatible encoding error'
- rescue ActionView::Template::Error => error
- assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message
- end
+ e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") }
+ assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message
end
end
def test_render_utf8_template_with_partial_with_incompatible_encoding
with_external_encoding Encoding::SHIFT_JIS do
- begin
- @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield")
- flunk 'Should have raised incompatible encoding error'
- rescue ActionView::Template::Error => error
- assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message
- end
+ e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") }
+ assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message
end
end
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
index 6c325d5abb..f0a7ce0bc9 100644
--- a/actionpack/test/template/tag_helper_test.rb
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -30,8 +30,8 @@ class TagHelperTest < ActionView::TestCase
end
def test_tag_options_converts_boolean_option
- assert_equal '<p disabled="disabled" multiple="multiple" readonly="readonly" />',
- tag("p", :disabled => true, :multiple => true, :readonly => true)
+ assert_equal '<p disabled="disabled" itemscope="itemscope" multiple="multiple" readonly="readonly" />',
+ tag("p", :disabled => true, :itemscope => true, :multiple => true, :readonly => true)
end
def test_content_tag
@@ -91,6 +91,11 @@ class TagHelperTest < ActionView::TestCase
assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
end
+ def test_cdata_section_splitted
+ assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]>", cdata_section("hello]]>world")
+ assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]]]><![CDATA[>again]]>", cdata_section("hello]]>world]]>again")
+ end
+
def test_escape_once
assert_equal '1 &lt; 2 &amp; 3', escape_once('1 < 2 &amp; 3')
end
@@ -113,8 +118,8 @@ class TagHelperTest < ActionView::TestCase
def test_data_attributes
['data', :data].each { |data|
- assert_dom_equal '<a data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string="hello" data-symbol="foo" />',
- tag('a', { data => { :a_number => 1, :string => 'hello', :symbol => :foo, :array => [1, 2, 3], :hash => { :key => 'value'} } })
+ assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string="hello" data-symbol="foo" />',
+ tag('a', { data => { :a_float => 3.14, :a_big_decimal => BigDecimal.new("-123.456"), :a_number => 1, :string => 'hello', :symbol => :foo, :array => [1, 2, 3], :hash => { :key => 'value'} } })
}
end
end
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 8c57ada587..322bea3fb0 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -48,7 +48,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def new_template(body = "<%= hello %>", details = {})
- ActionView::Template.new(body, "hello template", ERBHandler, {:virtual_path => "hello"}.merge!(details))
+ ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, {:virtual_path => "hello"}.merge!(details))
end
def render(locals = {})
@@ -64,6 +64,11 @@ class TestERBTemplate < ActiveSupport::TestCase
assert_equal "Hello", render
end
+ def test_raw_template
+ @template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new)
+ assert_equal "<%= hello %>", render
+ end
+
def test_template_loses_its_source_after_rendering
@template = new_template
render
diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb
index adcbf1447f..108a674d95 100644
--- a/actionpack/test/template/test_test.rb
+++ b/actionpack/test/template/test_test.rb
@@ -48,7 +48,7 @@ class PeopleHelperTest < ActionView::TestCase
def with_test_route_set
with_routing do |set|
set.draw do
- match 'people', :to => 'people#index', :as => :people
+ get 'people', :to => 'people#index', :as => :people
end
yield
end
diff --git a/actionpack/test/template/testing/fixture_resolver_test.rb b/actionpack/test/template/testing/fixture_resolver_test.rb
index de83540468..9649f349cb 100644
--- a/actionpack/test/template/testing/fixture_resolver_test.rb
+++ b/actionpack/test/template/testing/fixture_resolver_test.rb
@@ -8,8 +8,8 @@ class FixtureResolverTest < ActiveSupport::TestCase
end
def test_should_return_template_for_declared_path
- resolver = ActionView::FixtureResolver.new("arbitrary/path" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text")
+ templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
diff --git a/actionpack/test/template/testing/null_resolver_test.rb b/actionpack/test/template/testing/null_resolver_test.rb
index e142506e6a..535ad3ab14 100644
--- a/actionpack/test/template/testing/null_resolver_test.rb
+++ b/actionpack/test/template/testing/null_resolver_test.rb
@@ -3,10 +3,10 @@ require 'abstract_unit'
class NullResolverTest < ActiveSupport::TestCase
def test_should_return_template_for_any_path
resolver = ActionView::NullResolver.new()
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ templates = resolver.find_all("path.erb", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
assert_equal 1, templates.size, "expected one template"
assert_equal "Template generated by Null Resolver", templates.first.source
- assert_equal "arbitrary/path", templates.first.virtual_path
+ assert_equal "arbitrary/path.erb", templates.first.virtual_path
assert_equal [:html], templates.first.formats
end
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index 5865b7f23c..f58e474759 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -46,12 +46,34 @@ class TextHelperTest < ActionView::TestCase
assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false)
end
+ def test_simple_format_with_custom_wrapper
+ assert_equal "<div></div>", simple_format(nil, {}, :wrapper_tag => "div")
+ end
+
+ def test_simple_format_with_custom_wrapper_and_multi_line_breaks
+ assert_equal "<div>We want to put a wrapper...</div>\n\n<div>...right there.</div>", simple_format("We want to put a wrapper...\n\n...right there.", {}, :wrapper_tag => "div")
+ end
+
def test_simple_format_should_not_change_the_text_passed
text = "<b>Ok</b><script>code!</script>"
text_clone = text.dup
simple_format(text)
assert_equal text_clone, text
end
+
+ def test_simple_format_does_not_modify_the_html_options_hash
+ options = { :class => "foobar"}
+ passed_options = options.dup
+ simple_format("some text", passed_options)
+ assert_equal options, passed_options
+ end
+
+ def test_simple_format_does_not_modify_the_options_hash
+ options = { :wrapper_tag => :div, :sanitize => false }
+ passed_options = options.dup
+ simple_format("some text", {}, passed_options)
+ assert_equal options, passed_options
+ end
def test_truncate_should_not_be_html_safe
assert !truncate("Hello World!", :length => 12).html_safe?
@@ -84,6 +106,13 @@ class TextHelperTest < ActionView::TestCase
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10)
end
+
+ def test_truncate_does_not_modify_the_options_hash
+ options = { :length => 10 }
+ passed_options = options.dup
+ truncate("some text", passed_options)
+ assert_equal options, passed_options
+ end
def test_highlight_should_be_html_safe
assert highlight("This is a beautiful morning", "beautiful").html_safe?
@@ -102,7 +131,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal(
"This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
- highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>')
)
assert_equal(
@@ -145,14 +174,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_highlight_with_multiple_phrases_in_one_pass
- assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), '<em>\1</em>')
- end
-
- def test_highlight_with_options_hash
- assert_equal(
- "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
- highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>')
- )
+ assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), :highlighter => '<em>\1</em>')
end
def test_highlight_with_html
@@ -181,42 +203,48 @@ class TextHelperTest < ActionView::TestCase
highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>')
)
end
+
+ def test_highlight_does_not_modify_the_options_hash
+ options = { :highlighter => '<b>\1</b>', :sanitize => false }
+ passed_options = options.dup
+ highlight("<div>abc div</div>", "div", passed_options)
+ assert_equal options, passed_options
+ end
def test_excerpt
- assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5))
- assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
- assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
+ assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
+ assert_equal("This is a...", excerpt("This is a beautiful morning", "this", :radius => 5))
+ assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", :radius => 5))
assert_nil excerpt("This is a beautiful morning", "day")
end
def test_excerpt_should_not_be_html_safe
- assert !excerpt('This is a beautiful! morning', 'beautiful', 5).html_safe?
+ assert !excerpt('This is a beautiful! morning', 'beautiful', :radius => 5).html_safe?
end
def test_excerpt_in_borderline_cases
- assert_equal("", excerpt("", "", 0))
- assert_equal("a", excerpt("a", "a", 0))
- assert_equal("...b...", excerpt("abc", "b", 0))
- assert_equal("abc", excerpt("abc", "b", 1))
- assert_equal("abc...", excerpt("abcd", "b", 1))
- assert_equal("...abc", excerpt("zabc", "b", 1))
- assert_equal("...abc...", excerpt("zabcd", "b", 1))
- assert_equal("zabcd", excerpt("zabcd", "b", 2))
+ assert_equal("", excerpt("", "", :radius => 0))
+ assert_equal("a", excerpt("a", "a", :radius => 0))
+ assert_equal("...b...", excerpt("abc", "b", :radius => 0))
+ assert_equal("abc", excerpt("abc", "b", :radius => 1))
+ assert_equal("abc...", excerpt("abcd", "b", :radius => 1))
+ assert_equal("...abc", excerpt("zabc", "b", :radius => 1))
+ assert_equal("...abc...", excerpt("zabcd", "b", :radius => 1))
+ assert_equal("zabcd", excerpt("zabcd", "b", :radius => 2))
# excerpt strips the resulting string before ap-/prepending excerpt_string.
# whether this behavior is meaningful when excerpt_string is not to be
# appended is questionable.
- assert_equal("zabcd", excerpt(" zabcd ", "b", 4))
- assert_equal("...abc...", excerpt("z abc d", "b", 1))
+ assert_equal("zabcd", excerpt(" zabcd ", "b", :radius => 4))
+ assert_equal("...abc...", excerpt("z abc d", "b", :radius => 1))
end
def test_excerpt_with_regex
- assert_equal('...is a beautiful! mor...', excerpt('This is a beautiful! morning', 'beautiful', 5))
- assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', 5))
+ assert_equal('...is a beautiful! mor...', excerpt('This is a beautiful! morning', 'beautiful', :radius => 5))
+ assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', :radius => 5))
end
- def test_excerpt_with_options_hash
- assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
+ def test_excerpt_with_omission
assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", :omission => "[...]",:radius => 5))
assert_equal(
"This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]",
@@ -226,19 +254,29 @@ class TextHelperTest < ActionView::TestCase
end
def test_excerpt_with_utf8
- assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', 8))
+ assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8))
+ end
+
+ def test_excerpt_does_not_modify_the_options_hash
+ options = { :omission => "[...]",:radius => 5 }
+ passed_options = options.dup
+ excerpt("This is a beautiful morning", "beautiful", passed_options)
+ assert_equal options, passed_options
end
def test_word_wrap
- assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", 15))
+ assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
end
def test_word_wrap_with_extra_newlines
- assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", 15))
- end
-
- def test_word_wrap_with_options_hash
- assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
+ assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15))
+ end
+
+ def test_word_wrap_does_not_modify_the_options_hash
+ options = { :line_width => 15 }
+ passed_options = options.dup
+ word_wrap("some text", passed_options)
+ assert_equal options, passed_options
end
def test_pluralization
diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb
index 397de9c2ce..97777ccff0 100644
--- a/actionpack/test/template/translation_helper_test.rb
+++ b/actionpack/test/template/translation_helper_test.rb
@@ -11,7 +11,8 @@ class TranslationHelperTest < ActiveSupport::TestCase
:translations => {
:templates => {
:found => { :foo => 'Foo' },
- :array => { :foo => { :bar => 'Foo Bar' } }
+ :array => { :foo => { :bar => 'Foo Bar' } },
+ :default => { :foo => 'Foo' }
},
:foo => 'Foo',
:hello => '<a>Hello World</a>',
@@ -71,6 +72,10 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal 'Foo Bar', @view.render(:file => 'translations/templates/array').strip
end
+ def test_default_lookup_scoped_by_partial
+ assert_equal 'Foo', view.render(:file => 'translations/templates/default').strip
+ end
+
def test_missing_translation_scoped_by_partial
expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>'
assert_equal expected, view.render(:file => 'translations/templates/missing').strip
@@ -102,4 +107,22 @@ class TranslationHelperTest < ActiveSupport::TestCase
def test_translation_returning_an_array_ignores_html_suffix
assert_equal ["foo", "bar"], translate(:'translations.array_html')
end
+
+ def test_translate_with_default_named_html
+ translation = translate(:'translations.missing', :default => :'translations.hello_html')
+ assert_equal '<a>Hello World</a>', translation
+ assert translation.html_safe?
+ end
+
+ def test_translate_with_two_defaults_named_html
+ translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html'])
+ assert_equal '<a>Hello World</a>', translation
+ assert translation.html_safe?
+ end
+
+ def test_translate_with_last_default_named_html
+ translation = translate(:'translations.missing', :default => [:'translations.missing', :'translations.hello_html'])
+ assert_equal '<a>Hello World</a>', translation
+ assert translation.html_safe?
+ end
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index b482bd3251..fb5b35bac6 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -15,9 +15,9 @@ class UrlHelperTest < ActiveSupport::TestCase
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
- match "/" => "foo#bar"
- match "/other" => "foo#other"
- match "/article/:id" => "foo#article", :as => :article
+ get "/" => "foo#bar"
+ get "/other" => "foo#other"
+ get "/article/:id" => "foo#article", :as => :article
end
include routes.url_helpers
@@ -97,7 +97,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_button_to_with_javascript_disable_with
assert_dom_equal(
"<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>",
- button_to("Hello", "http://www.example.com", :disable_with => "Greeting...")
+ button_to("Hello", "http://www.example.com", 'data-disable-with' => "Greeting...")
)
end
@@ -112,20 +112,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_button_to_with_remote_and_javascript_disable_with
- assert_dom_equal(
- "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>",
- button_to("Hello", "http://www.example.com", :remote => true, :disable_with => "Greeting...")
- )
- end
-
- def test_button_to_with_remote_and_javascript_confirm_and_javascript_disable_with
- assert_dom_equal(
- "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>",
- button_to("Hello", "http://www.example.com", :remote => true, :confirm => "Are you sure?", :disable_with => "Greeting...")
- )
- end
-
def test_button_to_with_remote_false
assert_dom_equal(
"<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>",
@@ -408,12 +394,12 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_to_with_javascript
snippet = mail_to("me@domain.com", "My email", :encode => "javascript")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
end
def test_mail_to_with_javascript_unicode
snippet = mail_to("unicode@example.com", "únicode", :encode => "javascript")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
end
def test_mail_with_options
@@ -438,8 +424,8 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
def test_mail_to_returns_html_safe_string
@@ -471,25 +457,25 @@ end
class UrlHelperControllerTest < ActionController::TestCase
class UrlHelperController < ActionController::Base
test_routes do
- match 'url_helper_controller_test/url_helper/show/:id',
+ get 'url_helper_controller_test/url_helper/show/:id',
:to => 'url_helper_controller_test/url_helper#show',
:as => :show
- match 'url_helper_controller_test/url_helper/profile/:name',
+ get 'url_helper_controller_test/url_helper/profile/:name',
:to => 'url_helper_controller_test/url_helper#show',
:as => :profile
- match 'url_helper_controller_test/url_helper/show_named_route',
+ get 'url_helper_controller_test/url_helper/show_named_route',
:to => 'url_helper_controller_test/url_helper#show_named_route',
:as => :show_named_route
- match "/:controller(/:action(/:id))"
+ get "/:controller(/:action(/:id))"
- match 'url_helper_controller_test/url_helper/normalize_recall_params',
+ get 'url_helper_controller_test/url_helper/normalize_recall_params',
:to => UrlHelperController.action(:normalize_recall),
:as => :normalize_recall_params
- match '/url_helper_controller_test/url_helper/override_url_helper/default',
+ get '/url_helper_controller_test/url_helper/override_url_helper/default',
:to => 'url_helper_controller_test/url_helper#override_url_helper',
:as => :override_url_helper
end
@@ -566,7 +552,7 @@ class UrlHelperControllerTest < ActionController::TestCase
def test_named_route_should_show_host_and_path_using_controller_default_url_options
class << @controller
- def default_url_options(options = nil)
+ def default_url_options
{:host => 'testtwo.host'}
end
end
diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb
index ae2a0c95f6..595b4018e9 100644
--- a/actionpack/test/ts_isolated.rb
+++ b/actionpack/test/ts_isolated.rb
@@ -1,6 +1,3 @@
-$:.unshift(File.dirname(__FILE__))
-$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
-
require 'minitest/autorun'
require 'rbconfig'
require 'abstract_unit'
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index c6d8eae46c..789cff0673 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
+
* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev*
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 4861b0a002..b4565b5881 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -14,7 +14,7 @@ Model solves this by defining an explicit API. You can read more about the
API in ActiveModel::Lint::Tests.
Active Model provides a default module that implements the basic API required
-to integrate with Action Pack out of the box: +ActiveModel::Model+.
+to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
class Person
include ActiveModel::Model
@@ -25,12 +25,12 @@ to integrate with Action Pack out of the box: +ActiveModel::Model+.
person = Person.new(:name => 'bob', :age => '18')
person.name # => 'bob'
- person.age # => 18
- person.valid? # => false
+ person.age # => '18'
+ person.valid? # => true
It includes model name introspections, conversions, translations and
validations, resulting in a class suitable to be used with Action Pack.
-See +ActiveModel::Model+ for more examples.
+See <tt>ActiveModel::Model</tt> for more examples.
Active Model also provides the following functionality to have ORM-like
behavior out of the box:
@@ -41,7 +41,7 @@ behavior out of the box:
include ActiveModel::AttributeMethods
attribute_method_prefix 'clear_'
- define_attribute_methods [:name, :age]
+ define_attribute_methods :name, :age
attr_accessor :name, :age
@@ -71,7 +71,7 @@ behavior out of the box:
This generates +before_create+, +around_create+ and +after_create+
class methods that wrap your create method.
- {Learn more}[link:classes/ActiveModel/CallBacks.html]
+ {Learn more}[link:classes/ActiveModel/Callbacks.html]
* Tracking value changes
@@ -116,9 +116,6 @@ behavior out of the box:
person.errors.full_messages
# => ["Name can not be nil"]
- person.errors.full_messages
- # => ["Name can not be nil"]
-
{Learn more}[link:classes/ActiveModel/Errors.html]
* Model name introspection
@@ -138,6 +135,16 @@ behavior out of the box:
pattern in a Rails App and take advantage of all the standard observer
functions.
+ class PersonObserver < ActiveModel::Observer
+ def after_create(person)
+ person.logger.info("New person added!")
+ end
+
+ def after_destroy(person)
+ person.logger.warn("Person with an id of #{person.id} was destroyed!")
+ end
+ end
+
{Learn more}[link:classes/ActiveModel/Observer.html]
* Making objects serializable
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index fc5aaf9f8f..fc5aaf9f8f 100755..100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 2586147a20..ded1b752df 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -21,8 +21,6 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require 'active_support'
require 'active_model/version'
@@ -59,5 +57,6 @@ module ActiveModel
end
end
-require 'active_support/i18n'
-I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml'
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml'
+end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 97a83e58af..99918fdb96 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -28,7 +28,7 @@ module ActiveModel
# attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
# attribute_method_suffix '_contrived?'
# attribute_method_prefix 'clear_'
- # define_attribute_methods ['name']
+ # define_attribute_methods 'name'
#
# attr_accessor :name
#
@@ -86,7 +86,7 @@ module ActiveModel
# include ActiveModel::AttributeMethods
# attr_accessor :name
# attribute_method_prefix 'clear_'
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# private
#
@@ -124,7 +124,7 @@ module ActiveModel
# include ActiveModel::AttributeMethods
# attr_accessor :name
# attribute_method_suffix '_short?'
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# private
#
@@ -162,7 +162,7 @@ module ActiveModel
# include ActiveModel::AttributeMethods
# attr_accessor :name
# attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# private
#
@@ -180,6 +180,18 @@ module ActiveModel
undefine_attribute_methods
end
+
+ # Allows you to make aliases for attributes.
+ #
+ # class Person
+ # attr_accessor :name
+ # alias_attribute :nickname, :name
+ # end
+ #
+ # person = Person.new
+ # person.nickname = "Bob"
+ # person.nickname # => "Bob"
+ # person.name # => "Bob"
def alias_attribute(new_name, old_name)
attribute_method_matchers.each do |matcher|
matcher_new = matcher.method_name(new_name).to_s
@@ -204,7 +216,7 @@ module ActiveModel
# # Call to define_attribute_methods must appear after the
# # attribute_method_prefix, attribute_method_suffix or
# # attribute_method_affix declares.
- # define_attribute_methods [:name, :age, :address]
+ # define_attribute_methods :name, :age, :address
#
# private
#
@@ -212,8 +224,8 @@ module ActiveModel
# ...
# end
# end
- def define_attribute_methods(attr_names)
- attr_names.each { |attr_name| define_attribute_method(attr_name) }
+ def define_attribute_methods(*attr_names)
+ attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
end
def define_attribute_method(attr_name)
@@ -243,11 +255,7 @@ module ActiveModel
# Returns true if the attribute methods defined have been generated.
def generated_attribute_methods #:nodoc:
- @generated_attribute_methods ||= begin
- mod = Module.new
- include mod
- mod
- end
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
end
protected
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
index 1757c12ebf..ba5a6a2075 100644
--- a/activemodel/lib/active_model/configuration.rb
+++ b/activemodel/lib/active_model/configuration.rb
@@ -95,7 +95,7 @@ module ActiveModel
end
def define
- host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__
+ host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
attr_accessor :#{name}
def #{name}?; !!#{name}; end
CODE
@@ -107,7 +107,7 @@ module ActiveModel
define_method("#{name}?") { !!send(name) }
end
- host.class_eval <<-CODE
+ host.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
def #{name}?; !!#{name}; end
CODE
@@ -117,7 +117,7 @@ module ActiveModel
define_method("#{name}=") { |val| host.send("#{name}=", val) }
end
else
- class_methods.class_eval <<-CODE, __FILE__, __LINE__
+ class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}=(val)
singleton_class.class_eval do
remove_possible_method(:#{name})
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index d327913824..3f2fd12db7 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -30,7 +30,7 @@ module ActiveModel
#
# include ActiveModel::Dirty
#
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# def name
# @name
@@ -83,6 +83,7 @@ module ActiveModel
# in-place attributes.
#
# person.name_will_change!
+ # person.name_change # => ['Bill', 'Bill']
# person.name << 'y'
# person.name_change # => ['Bill', 'Billy']
module Dirty
@@ -151,13 +152,15 @@ module ActiveModel
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
+ return if attribute_changed?(attr)
+
begin
value = __send__(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
- changed_attributes[attr] = value unless changed_attributes.include?(attr)
+ changed_attributes[attr] = value
end
# Handle <tt>reset_*!</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 042f9cd7e2..aba6618b56 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -3,7 +3,6 @@
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/hash/reverse_merge'
module ActiveModel
# == Active Model Errors
@@ -130,12 +129,12 @@ module ActiveModel
# has more than one error message, yields once for each error message.
#
# p.errors.add(:name, "can't be blank")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
# p.errors.add(:name, "must be specified")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
@@ -202,12 +201,12 @@ module ActiveModel
# # <error>name must be specified</error>
# # </errors>
def to_xml(options={})
- to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
+ to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
end
# Returns an Hash that can be used as the JSON representation for this object.
# Options:
- # * <tt>:full_messages</tt> - determines if json object should contain
+ # * <tt>:full_messages</tt> - determines if json object should contain
# full messages or not. Default: <tt>false</tt>.
def as_json(options=nil)
to_hash(options && options[:full_messages])
@@ -217,7 +216,7 @@ module ActiveModel
if full_messages
messages = {}
self.messages.each do |attribute, array|
- messages[attribute] = array.map{|message| full_message(attribute, message) }
+ messages[attribute] = array.map { |message| full_message(attribute, message) }
end
messages
else
@@ -286,7 +285,7 @@ module ActiveModel
# "Name is invalid"
def full_message(attribute, message)
return message if attribute == :base
- attr_name = attribute.to_s.gsub('.', '_').humanize
+ attr_name = attribute.to_s.tr('.', '_').humanize
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
I18n.t(:"errors.format", {
:default => "%{attribute} %{message}",
@@ -347,7 +346,7 @@ module ActiveModel
:model => @base.class.model_name.human,
:attribute => @base.class.human_attribute_name(attribute),
:value => value
- }.merge(options)
+ }.merge!(options)
I18n.translate(key, options)
end
@@ -356,9 +355,10 @@ module ActiveModel
def normalize_message(attribute, message, options)
message ||= :invalid
- if message.is_a?(Symbol)
+ case message
+ when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- elsif message.is_a?(Proc)
+ when Proc
message.call
else
message
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index ba49c6beaa..d17848c861 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -9,7 +9,7 @@ en:
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
- confirmation: "doesn't match confirmation"
+ confirmation: "doesn't match %{attribute}"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 5e5405fe27..893fbf92c3 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -229,7 +229,7 @@ module ActiveModel
protected
def sanitize_for_mass_assignment(attributes, role = nil)
- _mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role))
+ _mass_assignment_sanitizer.sanitize(self.class, attributes, mass_assignment_authorizer(role))
end
def mass_assignment_authorizer(role)
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
index 4491e07a72..44ce5a489d 100644
--- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -2,18 +2,18 @@ module ActiveModel
module MassAssignmentSecurity
class Sanitizer
# Returns all attributes not denied by the authorizer.
- def sanitize(attributes, authorizer)
+ def sanitize(klass, attributes, authorizer)
rejected = []
sanitized_attributes = attributes.reject do |key, value|
rejected << key if authorizer.deny?(key)
end
- process_removed_attributes(rejected) unless rejected.empty?
+ process_removed_attributes(klass, rejected) unless rejected.empty?
sanitized_attributes
end
protected
- def process_removed_attributes(attrs)
+ def process_removed_attributes(klass, attrs)
raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten"
end
end
@@ -32,8 +32,21 @@ module ActiveModel
@target.respond_to?(:logger) && @target.logger
end
- def process_removed_attributes(attrs)
- logger.warn "Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger?
+ def backtrace
+ if defined? Rails
+ Rails.backtrace_cleaner.clean(caller)
+ else
+ caller
+ end
+ end
+
+ def process_removed_attributes(klass, attrs)
+ if logger?
+ logger.warn do
+ "WARNING: Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}\n" +
+ backtrace.map { |trace| "\t#{trace}" }.join("\n")
+ end
+ end
end
end
@@ -42,9 +55,9 @@ module ActiveModel
super()
end
- def process_removed_attributes(attrs)
+ def process_removed_attributes(klass, attrs)
return if (attrs - insensitive_attributes).empty?
- raise ActiveModel::MassAssignmentSecurity::Error.new(attrs)
+ raise ActiveModel::MassAssignmentSecurity::Error.new(klass, attrs)
end
def insensitive_attributes
@@ -53,8 +66,8 @@ module ActiveModel
end
class Error < StandardError
- def initialize(attrs)
- super("Can't mass-assign protected attributes: #{attrs.join(', ')}")
+ def initialize(klass, attrs)
+ super("Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}")
end
end
end
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index 6825fdc653..3af95b09b0 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -2,11 +2,11 @@ module ActiveModel
# == Active Model Basic Model
#
- # Includes the required interface for an object to interact with +ActionPack+,
- # using different +ActiveModel+ modules. It includes model name introspections,
+ # Includes the required interface for an object to interact with <tt>ActionPack</tt>,
+ # using different <tt>ActiveModel</tt> modules. It includes model name introspections,
# conversions, translations and validations. Besides that, it allows you to
# initialize the object with a hash of attributes, pretty much like
- # +ActiveRecord+ does.
+ # <tt>ActiveRecord</tt> does.
#
# A minimal implementation could be:
#
@@ -19,8 +19,8 @@ module ActiveModel
# person.name # => 'bob'
# person.age # => 18
#
- # Note that, by default, +ActiveModel::Model+ implements +persisted?+ to
- # return +false+, which is the most common case. You may want to override it
+ # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> to
+ # return <tt>false</tt>, which is the most common case. You may want to override it
# in your class to simulate a different scenario:
#
# class Person
@@ -35,14 +35,14 @@ module ActiveModel
# person = Person.new(:id => 1, :name => 'bob')
# person.persisted? # => true
#
- # Also, if for some reason you need to run code on +initialize+, make sure you
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make sure you
# call super if you want the attributes hash initialization to happen.
#
# class Person
# include ActiveModel::Model
# attr_accessor :id, :name, :omg
#
- # def initialize(attributes)
+ # def initialize(attributes={})
# super
# @omg ||= true
# end
@@ -52,7 +52,7 @@ module ActiveModel
# person.omg # => true
#
# For more detailed information on other functionalities available, please refer
- # to the specific modules included in +ActiveModel::Model+ (see below).
+ # to the specific modules included in <tt>ActiveModel::Model</tt> (see below).
module Model
def self.included(base)
base.class_eval do
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index adf000e53c..2b5fc57a3a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,7 +1,6 @@
require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
-require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
@@ -55,7 +54,7 @@ module ActiveModel
defaults << options[:default] if options[:default]
defaults << @human
- options = {:scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults}.merge(options.except(:default))
+ options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default))
I18n.translate(defaults.shift, options)
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 32f2aa46bd..f5ea285ccb 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -4,6 +4,8 @@ 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'
+require 'active_support/deprecation'
+require 'active_support/core_ext/object/try'
require 'active_support/descendants_tracker'
module ActiveModel
@@ -69,27 +71,34 @@ module ActiveModel
end
# Notify list of observers of a change.
- def notify_observers(*arg)
- observer_instances.each { |observer| observer.update(*arg) }
+ def notify_observers(*args)
+ observer_instances.each { |observer| observer.update(*args) }
end
# Total number of observers.
- def count_observers
+ def observers_count
observer_instances.size
end
+ def count_observers
+ msg = "count_observers is deprecated in favor of observers_count"
+ ActiveSupport::Deprecation.warn(msg)
+ observers_count
+ end
+
protected
def instantiate_observer(observer) #:nodoc:
# string/symbol
if observer.respond_to?(:to_sym)
- observer.to_s.camelize.constantize.instance
- elsif observer.respond_to?(:instance)
+ observer = observer.to_s.camelize.constantize
+ end
+ if 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 " +
+ "#{observer} must be a lowercase, underscored class name (or " +
+ "the class itself) responding to the method :instance. " +
+ "Example: Person.observers = :big_brother # calls " +
"BigBrother.instance"
end
end
@@ -101,17 +110,24 @@ module ActiveModel
end
end
- private
- # Fires notifications to model's observers
- #
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
- # end
- def notify_observers(method)
- self.class.notify_observers(method, self)
- end
+ # Fires notifications to model's observers
+ #
+ # def save
+ # notify_observers(:before_save)
+ # ...
+ # notify_observers(:after_save)
+ # end
+ #
+ # Custom notifications can be sent in a similar fashion:
+ #
+ # notify_observers(:custom_notification, :foo)
+ #
+ # This will call +custom_notification+, passing as arguments
+ # the current object and :foo.
+ #
+ def notify_observers(method, *extra_args)
+ self.class.notify_observers(method, self, *extra_args)
+ end
end
# == Active Model Observers
@@ -186,7 +202,7 @@ module ActiveModel
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- redefine_method(:observed_classes) { models }
+ singleton_class.redefine_method(:observed_classes) { models }
end
# Returns an array of Classes to observe.
@@ -205,15 +221,12 @@ module ActiveModel
# The class observed by default is inferred from the observer's class name:
# assert_equal Person, PersonObserver.observed_class
def observed_class
- if observed_class_name = name[/(.*)Observer/, 1]
- observed_class_name.constantize
- else
- nil
- end
+ name[/(.*)Observer/, 1].try :constantize
end
end
# Start observing the declared classes and their subclasses.
+ # Called automatically by the instance method.
def initialize
observed_classes.each { |klass| add_observer!(klass) }
end
@@ -224,10 +237,10 @@ module ActiveModel
# Send observed_method(object) if the method exists and
# the observer is enabled for the given object's class.
- def update(observed_method, object, &block) #:nodoc:
+ def update(observed_method, object, *extra_args, &block) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
- send(observed_method, object, &block)
+ send(observed_method, object, *extra_args, &block)
end
# Special method sent by the observed class when it is inherited.
@@ -242,6 +255,7 @@ module ActiveModel
klass.add_observer(self)
end
+ # Returns true if notifications are disabled for this object.
def disabled_for?(object)
klass = object.class
return false unless klass.respond_to?(:observers)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index e7a57cf691..3eab745c89 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -6,8 +6,9 @@ module ActiveModel
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a password_digest attribute.
#
- # Validations for presence of password, confirmation of password (using
+ # Validations for presence of password on create, confirmation of password (using
# a "password_confirmation" attribute) are automatically added.
+ # If you wish to turn off validations, pass 'validations: false' as an argument.
# You can add more validations by hand if need be.
#
# You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use has_secure_password:
@@ -31,16 +32,20 @@ module ActiveModel
# user.authenticate("mUc3m00RsqyRe") # => user
# User.find_by_name("david").try(:authenticate, "notright") # => false
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
- def has_secure_password
+ def has_secure_password(options = {})
# Load bcrypt-ruby only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework) being dependent on a binary library.
gem 'bcrypt-ruby', '~> 3.0.0'
require 'bcrypt'
attr_reader :password
-
- validates_confirmation_of :password
- validates_presence_of :password_digest
+
+ if options.fetch(:validations, true)
+ validates_confirmation_of :password
+ validates_presence_of :password, :on => :create
+ end
+
+ before_create { raise "Password digest missing on new record" if password_digest.blank? }
include InstanceMethodsOnActivation
@@ -55,17 +60,14 @@ module ActiveModel
module InstanceMethodsOnActivation
# Returns self if the password is correct, otherwise false.
def authenticate(unencrypted_password)
- if BCrypt::Password.new(password_digest) == unencrypted_password
- self
- else
- false
- end
+ BCrypt::Password.new(password_digest) == unencrypted_password && self
end
- # Encrypts the password into the password_digest attribute.
+ # Encrypts the password into the password_digest attribute, only if the
+ # new password is not blank.
def password=(unencrypted_password)
- @password = unencrypted_password
unless unencrypted_password.blank?
+ @password = unencrypted_password
self.password_digest = BCrypt::Password.create(unencrypted_password)
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index e9e80c4d2a..6d8fd21814 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -26,17 +26,18 @@ module ActiveModel
# person.serializable_hash # => {"name"=>"Bob"}
#
# You need to declare an attributes hash which contains the attributes
- # you want to serialize. When called, serializable hash will use
+ # you want to serialize. Attributes must be strings, not symbols.
+ # When called, serializable hash will use
# instance methods that match the name of the attributes hash's keys.
# In order to override this behavior, take a look at the private
- # method read_attribute_for_serialization.
+ # method +read_attribute_for_serialization+.
#
# Most of the time though, you will want to include the JSON or XML
# serializations. Both of these modules automatically include the
- # ActiveModel::Serialization module, so there is no need to explicitly
+ # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly
# include it.
#
- # So a minimal implementation including XML and JSON would be:
+ # A minimal implementation including XML and JSON would be:
#
# class Person
# include ActiveModel::Serializers::JSON
@@ -63,7 +64,12 @@ module ActiveModel
# person.to_json # => "{\"name\":\"Bob\"}"
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
- # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
+ # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and <tt>:include</tt>.
+ # The following are all valid examples:
+ #
+ # person.serializable_hash(:only => 'name')
+ # person.serializable_hash(:include => :address)
+ # person.serializable_hash(:include => { :address => { :only => 'city' }})
module Serialization
def serializable_hash(options = nil)
options ||= {}
@@ -78,12 +84,11 @@ module ActiveModel
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
- method_names = Array(options[:methods]).select { |n| respond_to?(n) }
- method_names.each { |n| hash[n.to_s] = send(n) }
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
serializable_add_includes(options) do |association, records, opts|
- hash[association.to_s] = if records.is_a?(Enumerable)
- records.map { |a| a.serializable_hash(opts) }
+ hash[association.to_s] = if records.respond_to?(:to_ary)
+ records.to_ary.map { |a| a.serializable_hash(opts) }
else
records.serializable_hash(opts)
end
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 5084298210..b78f1ff3f3 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -115,7 +115,9 @@ module ActiveModel
merged_options = opts.merge(options.slice(:builder, :indent))
merged_options[:skip_instruct] = true
- if records.is_a?(Enumerable)
+ if records.respond_to?(:to_ary)
+ records = records.to_ary
+
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
type = options[:skip_types] ? { } : {:type => "array"}
association_name = association.to_s.singularize
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 02b7c54d61..6f0ca92e2a 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/hash/reverse_merge'
-
module ActiveModel
# == Active Model Translation
@@ -43,19 +41,20 @@ module ActiveModel
#
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
- defaults = []
+ options = { :count => 1 }.merge!(options)
parts = attribute.to_s.split(".", 2)
attribute = parts.pop
namespace = parts.pop
+ attributes_scope = "#{self.i18n_scope}.attributes"
if namespace
- lookup_ancestors.each do |klass|
- defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
end
- defaults << :"#{self.i18n_scope}.attributes.#{namespace}.#{attribute}"
+ defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
else
- lookup_ancestors.each do |klass|
- defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
end
end
@@ -63,7 +62,7 @@ module ActiveModel
defaults << options.delete(:default) if options[:default]
defaults << attribute.humanize
- options.reverse_merge! :count => 1, :default => defaults
+ options[:default] = defaults
I18n.translate(defaults.shift, options)
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 0e15155b85..611e9ffd55 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -65,7 +65,7 @@ module ActiveModel
#
# attr_accessor :first_name, :last_name
#
- # validates_each :first_name, :last_name do |record, attr, value|
+ # validates_each :first_name, :last_name, :allow_blank => true do |record, attr, value|
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
# end
# end
@@ -128,6 +128,19 @@ module ActiveModel
# end
# end
#
+ # Options:
+ # * <tt>:on</tt> - Specifies the context where this validation is active
+ # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false value.
def validate(*args, &block)
options = args.extract_options!
if options.key?(:on)
@@ -145,7 +158,7 @@ module ActiveModel
_validators.values.flatten.uniq
end
- # List all validators that being used to validate a specific attribute.
+ # List all validators that are being used to validate a specific attribute.
def validators_on(*attributes)
attributes.map do |attribute|
_validators[attribute.to_sym]
@@ -165,6 +178,12 @@ module ActiveModel
end
end
+ # Clean the +Errors+ object if instance is duped
+ def initialize_dup(other) # :nodoc:
+ @errors = nil
+ super
+ end
+
# Returns the +Errors+ object that holds all information about attribute error messages.
def errors
@errors ||= Errors.new(self)
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index e628c6f306..38abd0c1fa 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -23,7 +23,7 @@ module ActiveModel
module HelperMethods
# Encapsulates the pattern of wanting to validate the acceptance of a
- # terms of service check box (or similar agreement). Example:
+ # terms of service check box (or similar agreement).
#
# class Person < ActiveRecord::Base
# validates_acceptance_of :terms_of_service
@@ -59,7 +59,7 @@ module ActiveModel
# The method, proc or string should return or evaluate to a true or
# false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index c39c85e1af..dbafd0bd1a 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -8,7 +8,8 @@ module ActiveModel
# Provides an interface for any class to have <tt>before_validation</tt> and
# <tt>after_validation</tt> callbacks.
#
- # First, extend ActiveModel::Callbacks from the class you are creating:
+ # First, include ActiveModel::Validations::Callbacks from the class you are
+ # creating:
#
# class MyModel
# include ActiveModel::Validations::Callbacks
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index e8526303e2..ede34d15bc 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -5,7 +5,8 @@ module ActiveModel
class ConfirmationValidator < EachValidator
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
- record.errors.add(attribute, :confirmation, options)
+ human_attribute_name = record.class.human_attribute_name(attribute)
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
end
end
@@ -18,7 +19,7 @@ module ActiveModel
module HelperMethods
# Encapsulates the pattern of wanting to validate a password or email
- # address field with a confirmation. For example:
+ # address field with a confirmation.
#
# Model:
# class Person < ActiveRecord::Base
@@ -59,7 +60,7 @@ module ActiveModel
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 5fedb1978b..4f09679541 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -15,34 +15,42 @@ module ActiveModel
end
module HelperMethods
- # Validates that the value of the specified attribute is not in a particular enumerable object.
+ # Validates that the value of the specified attribute is not in a
+ # particular enumerable object.
#
# class Person < ActiveRecord::Base
# 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"
+ # 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+).
+ # * <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+).
# * <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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index d3faa8c6a6..dd87e312f9 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -42,50 +42,62 @@ module ActiveModel
end
module HelperMethods
- # Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
- # You can require that the attribute matches the regular expression:
+ # Validates whether the value of the specified attribute is of the correct
+ # form, going by the regular expression provided.You can require that the
+ # attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
# validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
# end
#
- # Alternatively, you can require that the specified attribute does _not_ match the regular expression:
+ # Alternatively, you can require that the specified attribute does _not_
+ # match the regular expression:
#
# class Person < ActiveRecord::Base
# validates_format_of :email, :without => /NOSPAM/
# end
#
- # You can also provide a proc or lambda which will determine the regular expression that will be used to validate the attribute
+ # 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 }
+ # 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.
+ # 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 a proc or lambda, 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>: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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 15ae7b1959..ffdbed0fc1 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -15,7 +15,8 @@ module ActiveModel
end
module HelperMethods
- # Validates whether the value of the specified attribute is available in a particular enumerable object.
+ # Validates whether the value of the specified attribute is available in a
+ # particular enumerable object.
#
# class Person < ActiveRecord::Base
# validates_inclusion_of :gender, :in => %w( m f )
@@ -29,20 +30,25 @@ module ActiveModel
# 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+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * <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+).
+ # * <tt>:allow_blank</tt> - If set to true, skips this validation if the
+ # attribute is blank (default is +false+).
# * <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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 037f8c2db8..64b4fe2d74 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -72,35 +72,46 @@ module ActiveModel
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
# validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters"
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me."
- # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words.", :tokenizer => lambda { |str| str.scan(/\w+/) }
+ # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words.",
+ # :tokenizer => lambda { |str| str.scan(/\w+/) }
# end
#
# Configuration options:
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute.
# * <tt>:is</tt> - The exact size of the attribute.
- # * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
+ # * <tt>:within</tt> - A range specifying the minimum and maximum size of the
+ # attribute.
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
- # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %{count} characters)").
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
- # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %{count} characters)").
- # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
+ # * <tt>:too_long</tt> - The error message if the attribute goes over the
+ # maximum (default is: "is too long (maximum is %{count} characters)").
+ # * <tt>:too_short</tt> - The error message if the attribute goes under the
+ # minimum (default is: "is too short (min is %{count} characters)").
+ # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method
+ # and the attribute is the wrong size (default is: "is the wrong length
+ # (should be %{count} characters)").
+ # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
+ # <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
+ # <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
# * <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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
- # count words as in above example.)
- # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
+ # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
+ # (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to count words
+ # as in above example). Defaults to <tt>lambda{ |value| value.split(//) }</tt>
+ # which counts individual characters.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index bb9f9679fc..40b5b92b84 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -79,9 +79,10 @@ module ActiveModel
end
module HelperMethods
- # Validates whether the value of the specified attribute is numeric by trying to convert it to
- # a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
- # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
+ # Validates whether the value of the specified attribute is numeric by trying
+ # to convert it to a float with Kernel.Float (if <tt>only_integer</tt> is false)
+ # or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt> (if
+ # <tt>only_integer</tt> is set to true).
#
# class Person < ActiveRecord::Base
# validates_numericality_of :value, :on => :create
@@ -92,37 +93,50 @@ module ActiveModel
# * <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>.
- # * <tt>:only_integer</tt> - Specifies whether the value has to be an integer, e.g. an integral value (default is +false+).
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is +false+). Notice that for fixnum and float columns empty strings are converted to +nil+.
- # * <tt>:greater_than</tt> - Specifies the value must be greater than the supplied value.
- # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater than or equal the supplied value.
+ # * <tt>:only_integer</tt> - Specifies whether the value has to be an integer,
+ # e.g. an integral value (default is +false+).
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
+ # +false+). Notice that for fixnum and float columns empty strings are
+ # converted to +nil+.
+ # * <tt>:greater_than</tt> - Specifies the value must be greater than the
+ # supplied value.
+ # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater
+ # than or equal the supplied value.
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value.
- # * <tt>:less_than</tt> - Specifies the value must be less than the supplied value.
- # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or equal the supplied value.
- # * <tt>:other_than</tt> - Specifies the value must be other than the supplied value.
+ # * <tt>:less_than</tt> - Specifies the value must be less than the supplied
+ # value.
+ # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or
+ # equal the supplied value.
+ # * <tt>:other_than</tt> - Specifies the value must be other than the supplied
+ # value.
# * <tt>:odd</tt> - Specifies the value must be an odd number.
# * <tt>:even</tt> - Specifies the value must be an even number.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # The following checks can also be supplied with a proc or a symbol which
+ # corresponds to a method:
#
- # The following checks can also be supplied with a proc or a symbol which corresponds to a method:
# * <tt>:greater_than</tt>
# * <tt>:greater_than_or_equal_to</tt>
# * <tt>:equal_to</tt>
# * <tt>:less_than</tt>
# * <tt>:less_than_or_equal_to</tt>
#
+ # For example:
+ #
# class Person < ActiveRecord::Base
# validates_numericality_of :width, :less_than => Proc.new { |person| person.height }
# validates_numericality_of :width, :greater_than => :minimum_weight
# end
- #
def validates_numericality_of(*attr_names)
validates_with NumericalityValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 9a643a6f5c..018ef1e733 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -11,7 +11,8 @@ module ActiveModel
end
module HelperMethods
- # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
+ # Validates that the specified attributes are not blank (as defined by
+ # Object#blank?). Happens by default on save.
#
# class Person < ActiveRecord::Base
# validates_presence_of :first_name
@@ -19,25 +20,28 @@ module ActiveModel
#
# The first_name attribute must be in the object and it cannot be blank.
#
- # If you want to validate the presence of a boolean field (where the real values are true and false),
- # you will want to use <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
+ # If you want to validate the presence of a boolean field (where the real values
+ # are true and false), you will want to use
+ # <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
#
- # This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
+ # This is due to the way Object#blank? handles boolean values:
+ # <tt>false.blank? # => true</tt>.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
# * <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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
- #
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 72b8562b93..66cc9daa2c 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -62,8 +62,8 @@ module ActiveModel
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
-
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
# If you pass any additional configuration options, they will be passed
# to the class and available as <tt>options</tt>:
#
@@ -77,7 +77,6 @@ module ActiveModel
# options[:my_custom_key] # => "my custom value"
# end
# end
- #
def validates_with(*args, &block)
options = args.extract_options!
args.each do |klass|
@@ -126,14 +125,13 @@ module ActiveModel
# end
#
# Standard configuration options (:on, :if and :unless), which are
- # available on the class version of validates_with, should instead be
- # placed on the <tt>validates</tt> method as these are applied and tested
- # in the callback
+ # available on the class version of +validates_with+, should instead be
+ # placed on the +validates+ method as these are applied and tested
+ # in the callback.
#
# If you pass any additional configuration options, they will be passed
- # to the class and available as <tt>options</tt>, please refer to the
- # class version of this method for more information
- #
+ # to the class and available as +options+, please refer to the
+ # class version of this method for more information.
def validates_with(*args, &block)
options = args.extract_options!
args.each do |klass|
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index 34298d31c2..a9db29ee21 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -10,7 +10,7 @@ class ModelWithAttributes
end
def attributes
- { :foo => 'value of foo' }
+ { :foo => 'value of foo', :baz => 'value of baz' }
end
private
@@ -127,29 +127,36 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b')
end
+ test '#define_attribute_methods works passing multiple arguments' do
+ ModelWithAttributes.define_attribute_methods(:foo, :baz)
+
+ assert_equal "value of foo", ModelWithAttributes.new.foo
+ assert_equal "value of baz", ModelWithAttributes.new.baz
+ end
+
test '#define_attribute_methods generates attribute methods' do
- ModelWithAttributes.define_attribute_methods([:foo])
+ ModelWithAttributes.define_attribute_methods(:foo)
assert_respond_to ModelWithAttributes.new, :foo
assert_equal "value of foo", ModelWithAttributes.new.foo
end
test '#define_attribute_methods generates attribute methods with spaces in their names' do
- ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar'])
+ 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 '#alias_attribute works with attributes with spaces in their names' do
- ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar'])
+ ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar')
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
end
test '#undefine_attribute_methods removes attribute methods' do
- ModelWithAttributes.define_attribute_methods([:foo])
+ ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods
assert !ModelWithAttributes.new.respond_to?(:foo)
@@ -170,7 +177,7 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_deprecated { klass.attribute_method_suffix '' }
assert_deprecated { klass.attribute_method_prefix '' }
- klass.define_attribute_methods([:foo])
+ klass.define_attribute_methods(:foo)
assert_equal 'value of foo', klass.new.foo
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 98244a6290..eaaf910bac 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
class DirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Dirty
- define_attribute_methods [:name, :color]
+ define_attribute_methods :name, :color
def initialize
@name = nil
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 4347b17cbc..7d6f11b5a5 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -1,8 +1,5 @@
require File.expand_path('../../../../load_paths', __FILE__)
-lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
require 'config'
require 'active_model'
require 'active_support/core_ext/string/access'
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 3660b9b1e5..418a24294a 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -19,7 +19,7 @@ class SanitizerTest < ActiveModel::TestCase
test "sanitize attributes" do
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
- attributes = @logger_sanitizer.sanitize(original_attributes, @authorizer)
+ attributes = @logger_sanitizer.sanitize(self.class, original_attributes, @authorizer)
assert attributes.key?('first_name'), "Allowed key shouldn't be rejected"
assert !attributes.key?('admin'), "Denied key should be rejected"
@@ -29,14 +29,14 @@ class SanitizerTest < ActiveModel::TestCase
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
log = StringIO.new
self.logger = ActiveSupport::Logger.new(log)
- @logger_sanitizer.sanitize(original_attributes, @authorizer)
+ @logger_sanitizer.sanitize(self.class, original_attributes, @authorizer)
assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}")
end
test "debug mass assignment removal with StrictSanitizer" do
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
assert_raise ActiveModel::MassAssignmentSecurity::Error do
- @strict_sanitizer.sanitize(original_attributes, @authorizer)
+ @strict_sanitizer.sanitize(self.class, original_attributes, @authorizer)
end
end
@@ -44,7 +44,7 @@ class SanitizerTest < ActiveModel::TestCase
original_attributes = {'id' => 1, 'first_name' => 'allowed'}
assert_nothing_raised do
- @strict_sanitizer.sanitize(original_attributes, @authorizer)
+ @strict_sanitizer.sanitize(self.class, original_attributes, @authorizer)
end
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index a197dbe748..0c6352cd71 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -4,7 +4,7 @@ require 'models/mass_assignment_specific'
class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer
- def process_removed_attributes(attrs)
+ def process_removed_attributes(klass, attrs)
raise StandardError
end
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index f6ec24ae57..ade6026602 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -14,8 +14,8 @@ class FooObserver < ActiveModel::Observer
attr_accessor :stub
- def on_spec(record)
- stub.event_with(record) if stub
+ def on_spec(record, *args)
+ stub.event_with(record, *args) if stub
end
def around_save(record)
@@ -70,23 +70,38 @@ class ObservingTest < ActiveModel::TestCase
ObservedModel.instantiate_observers
end
+ test "raises an appropriate error when a developer accidentally adds the wrong class (i.e. Widget instead of WidgetObserver)" do
+ assert_raise ArgumentError do
+ ObservedModel.observers = ['string']
+ ObservedModel.instantiate_observers
+ end
+ assert_raise ArgumentError do
+ ObservedModel.observers = [:string]
+ ObservedModel.instantiate_observers
+ end
+ assert_raise ArgumentError do
+ ObservedModel.observers = [String]
+ ObservedModel.instantiate_observers
+ end
+ end
+
test "passes observers to subclasses" do
FooObserver.instance
bar = Class.new(Foo)
- assert_equal Foo.count_observers, bar.count_observers
+ assert_equal Foo.observers_count, bar.observers_count
end
end
class ObserverTest < ActiveModel::TestCase
def setup
ObservedModel.observers = :foo_observer
- FooObserver.instance_eval do
+ FooObserver.singleton_class.instance_eval do
alias_method :original_observed_classes, :observed_classes
end
end
def teardown
- FooObserver.instance_eval do
+ FooObserver.singleton_class.instance_eval do
undef_method :observed_classes
alias_method :observed_classes, :original_observed_classes
end
@@ -98,44 +113,51 @@ class ObserverTest < ActiveModel::TestCase
test "tracks implicit observable models" do
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}"
- assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}"
+ assert_equal [Foo], instance.observed_classes
end
test "tracks explicit observed model class" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe ObservedModel
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "tracks explicit observed model as string" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe 'observed_model'
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "tracks explicit observed model as symbol" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe :observed_model
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "calls existing observer event" do
foo = Foo.new
FooObserver.instance.stub = stub
FooObserver.instance.stub.expects(:event_with).with(foo)
- Foo.send(:notify_observers, :on_spec, foo)
+ Foo.notify_observers(:on_spec, foo)
+ end
+
+ test "calls existing observer event from the instance" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo)
+ foo.notify_observers(:on_spec)
+ end
+
+ test "passes extra arguments" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo, :bar)
+ Foo.send(:notify_observers, :on_spec, foo, :bar)
end
test "skips nonexistent observer event" do
foo = Foo.new
- Foo.send(:notify_observers, :whatever, foo)
+ Foo.notify_observers(:whatever, foo)
end
test "update passes a block on to the observer" do
@@ -145,4 +167,15 @@ class ObserverTest < ActiveModel::TestCase
end
assert_equal :in_around_save, yielded_value
end
+
+ test "observe redefines observed_classes class method" do
+ class BarObserver < ActiveModel::Observer
+ observe :foo
+ end
+
+ assert_equal [Foo], BarObserver.observed_classes
+
+ BarObserver.observe(ObservedModel)
+ assert_equal [ObservedModel], BarObserver.observed_classes
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 4338a3fc53..5f18909301 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -7,28 +7,38 @@ class SecurePasswordTest < ActiveModel::TestCase
setup do
@user = User.new
+ @visitor = Visitor.new
end
test "blank password" do
- @user.password = ''
- assert !@user.valid?, 'user should be invalid'
+ @user.password = @visitor.password = ''
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert @visitor.valid?(:create), 'visitor should be valid'
end
test "nil password" do
- @user.password = nil
- assert !@user.valid?, 'user should be invalid'
+ @user.password = @visitor.password = nil
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert @visitor.valid?(:create), 'visitor should be valid'
+ end
+
+ test "blank password doesn't override previous password" do
+ @user.password = 'test'
+ @user.password = ''
+ assert_equal @user.password, 'test'
end
test "password must be present" do
- assert !@user.valid?
+ assert !@user.valid?(:create)
assert_equal 1, @user.errors.size
end
- test "password must match confirmation" do
- @user.password = "thiswillberight"
- @user.password_confirmation = "wrong"
+ test "match confirmation" do
+ @user.password = @visitor.password = "thiswillberight"
+ @user.password_confirmation = @visitor.password_confirmation = "wrong"
assert !@user.valid?
+ assert @visitor.valid?
@user.password_confirmation = "thiswillberight"
@@ -53,4 +63,14 @@ class SecurePasswordTest < ActiveModel::TestCase
assert !active_authorizer.include?(:password_digest)
assert active_authorizer.include?(:name)
end
+
+ test "User should not be created with blank digest" do
+ assert_raise RuntimeError do
+ @user.run_callbacks :create
+ end
+ @user.password = "supersecretpassword"
+ assert_nothing_raised do
+ @user.run_callbacks :create
+ end
+ end
end
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index 66b18d65e5..d2ba9fd95d 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -105,6 +105,24 @@ class SerializationTest < ActiveModel::TestCase
assert_equal expected, @user.serializable_hash(:include => :friends)
end
+ class FriendList
+ def initialize(friends)
+ @friends = friends
+ end
+
+ def to_ary
+ @friends
+ end
+ end
+
+ def test_include_option_with_ary
+ @user.friends = FriendList.new(@user.friends)
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => :friends)
+ end
+
def test_multiple_includes
expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
"address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 38aecf51ff..5fa227e0e0 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -188,6 +188,23 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<friend type="Contact">}, xml
end
+ class FriendList
+ def initialize(friends)
+ @friends = friends
+ end
+
+ def to_ary
+ @friends
+ end
+ end
+
+ test "include option with ary" do
+ @contact.friends = FriendList.new(@contact.friends)
+ xml = @contact.to_xml :include => :friends, :indent => 0
+ assert_match %r{<friends type="array">}, xml
+ assert_match %r{<friend type="Contact">}, xml
+ end
+
test "multiple includes" do
xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ]
assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true))
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 54e86d48db..4999583802 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -82,9 +82,15 @@ class ActiveModelI18nTests < ActiveModel::TestCase
end
def test_human_does_not_modify_options
- options = {:default => 'person model'}
+ options = { :default => 'person model' }
Person.model_name.human(options)
- assert_equal({:default => 'person model'}, options)
+ assert_equal({ :default => 'person model' }, options)
+ end
+
+ def test_human_attribute_name_does_not_modify_options
+ options = { :default => 'Cool gender' }
+ Person.human_attribute_name('gender', options)
+ assert_equal({ :default => 'Cool gender' }, options)
end
end
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index d0418170fa..f7556a249f 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -44,7 +44,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase
p.karma_confirmation = "None"
assert p.invalid?
- assert_equal ["doesn't match confirmation"], p.errors[:karma]
+ assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation]
p.karma = "None"
assert p.valid?
@@ -52,4 +52,23 @@ class ConfirmationValidationTest < ActiveModel::TestCase
Person.reset_callbacks(:validate)
end
+ def test_title_confirmation_with_i18n_attribute
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations('en', {
+ :errors => {:messages => {:confirmation => "doesn't match %{attribute}"}},
+ :activemodel => {:attributes => {:topic => {:title => 'Test Title'}}}
+ })
+
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
+ assert t.invalid?
+ assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
+
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
end
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 0679e67f84..df0fcd243a 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -37,7 +37,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
# validates_confirmation_of: generate_message(attr_name, :confirmation, :message => custom_message)
def test_generate_message_confirmation_with_default_message
- assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation)
+ assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation)
end
def test_generate_message_confirmation_with_custom_message
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index e9f0e430fe..6b6aad3bd1 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -81,7 +81,7 @@ class I18nValidationTest < ActiveModel::TestCase
test "validates_confirmation_of on generated message #{name}" do
Person.validates_confirmation_of :title, validation_options
@person.title_confirmation = 'foo'
- @person.errors.expects(:generate_message).with(:title, :confirmation, generate_message_options)
+ @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(:attribute => 'Title'))
@person.valid?
end
end
@@ -217,24 +217,29 @@ class I18nValidationTest < ActiveModel::TestCase
# To make things DRY this macro is defined to define 3 tests for every validation case.
def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation)
+ if error_type == :confirmation
+ attribute = :title_confirmation
+ else
+ attribute = :title
+ end
# test "validates_confirmation_of finds custom model key translation when blank"
test "#{validation} finds custom model key translation when #{error_type}" do
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
yield(@person, {})
@person.valid?
- assert_equal ['custom message'], @person.errors[:title]
+ assert_equal ['custom message'], @person.errors[attribute]
end
# test "validates_confirmation_of finds custom model key translation with interpolation when blank"
test "#{validation} finds custom model key translation with interpolation when #{error_type}" do
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message with %{extra}'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message with %{extra}'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
yield(@person, {:extra => "extra information"})
@person.valid?
- assert_equal ['custom message with extra information'], @person.errors[:title]
+ assert_equal ['custom message with extra information'], @person.errors[attribute]
end
# test "validates_confirmation_of finds global default key translation when blank"
@@ -243,7 +248,7 @@ class I18nValidationTest < ActiveModel::TestCase
yield(@person, {})
@person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ assert_equal ['global message'], @person.errors[attribute]
end
end
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 575154ffbd..90bc018ae1 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -154,6 +154,6 @@ class ValidatesTest < ActiveModel::TestCase
topic.title = "What's happening"
topic.title_confirmation = "Not this"
assert !topic.valid?
- assert_equal ['Y U NO CONFIRM'], topic.errors[:title]
+ assert_equal ['Y U NO CONFIRM'], topic.errors[:title_confirmation]
end
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index a716d0896e..1f5023bf76 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -344,4 +344,19 @@ class ValidationsTest < ActiveModel::TestCase
Topic.validates :title, options
assert_equal({ :presence => true }, options)
end
+
+ def test_dup_validity_is_independent
+ Topic.validates_presence_of :title
+ topic = Topic.new("title" => "Litterature")
+ topic.valid?
+
+ duped = topic.dup
+ duped.title = nil
+ assert duped.invalid?
+
+ topic.title = nil
+ duped.title = 'Mathematics'
+ assert topic.invalid?
+ assert duped.valid?
+ end
end
diff --git a/activemodel/test/models/administrator.rb b/activemodel/test/models/administrator.rb
index a48f8b064f..2d6d34b3e2 100644
--- a/activemodel/test/models/administrator.rb
+++ b/activemodel/test/models/administrator.rb
@@ -1,7 +1,10 @@
class Administrator
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
+
+ define_model_callbacks :create
attr_accessor :name, :password_digest
attr_accessible :name
diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb
index e221bb8091..4b11df12bf 100644
--- a/activemodel/test/models/user.rb
+++ b/activemodel/test/models/user.rb
@@ -1,6 +1,9 @@
class User
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
+
+ define_model_callbacks :create
has_secure_password
diff --git a/activemodel/test/models/visitor.rb b/activemodel/test/models/visitor.rb
index 36c0a16688..d15f448516 100644
--- a/activemodel/test/models/visitor.rb
+++ b/activemodel/test/models/visitor.rb
@@ -1,9 +1,12 @@
class Visitor
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
+
+ define_model_callbacks :create
- has_secure_password
+ has_secure_password(validations: false)
- attr_accessor :password_digest
+ attr_accessor :password_digest, :password_confirmation
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 26f6093bc2..8f1f315e42 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,119 @@
## Rails 4.0.0 (unreleased) ##
+* Deprecated most of the 'dynamic finder' methods. All dynamic methods
+ except for `find_by_...` and `find_by_...!` are deprecated. Here's
+ how you can rewrite the code:
+
+ * `find_all_by_...` can be rewritten using `where(...)`
+ * `find_last_by_...` can be rewritten using `where(...).last`
+ * `scoped_by_...` can be rewritten using `where(...)`
+ * `find_or_initialize_by_...` can be rewritten using
+ `where(...).first_or_initialize`
+ * `find_or_create_by_...` can be rewritten using
+ `where(...).first_or_create`
+ * `find_or_create_by_...!` can be rewritten using
+ `where(...).first_or_create!`
+
+ The implementation of the deprecated dynamic finders has been moved
+ to the `active_record_deprecated_finders` gem. See below for details.
+
+ *Jon Leighton*
+
+* Deprecated the old-style hash based finder API. This means that
+ methods which previously accepted "finder options" no longer do. For
+ example this:
+
+ Post.find(:all, :conditions => { :comments_count => 10 }, :limit => 5)
+
+ Should be rewritten in the new style which has existed since Rails 3:
+
+ Post.where(comments_count: 10).limit(5)
+
+ Note that as an interim step, it is possible to rewrite the above as:
+
+ Post.scoped(:where => { :comments_count => 10 }, :limit => 5)
+
+ This could save you a lot of work if there is a lot of old-style
+ finder usage in your application.
+
+ Calling `Post.scoped(options)` is a shortcut for
+ `Post.scoped.merge(options)`. `Relation#merge` now accepts a hash of
+ options, but they must be identical to the names of the equivalent
+ finder method. These are mostly identical to the old-style finder
+ option names, except in the following cases:
+
+ * `:conditions` becomes `:where`
+ * `:include` becomes `:includes`
+ * `:extend` becomes `:extending`
+
+ The code to implement the deprecated features has been moved out to
+ the `active_record_deprecated_finders` gem. This gem is a dependency
+ of Active Record in Rails 4.0. It will no longer be a dependency
+ from Rails 4.1, but if your app relies on the deprecated features
+ then you can add it to your own Gemfile. It will be maintained by
+ the Rails core team until Rails 5.0 is released.
+
+ *Jon Leighton*
+
+* It's not possible anymore to destroy a model marked as read only.
+
+ *Johannes Barre*
+
+* Added ability to ActiveRecord::Relation#from to accept other ActiveRecord::Relation objects
+
+ Record.from(subquery)
+ Record.from(subquery, :a)
+
+ *Radoslav Stankov*
+
+* Added custom coders support for ActiveRecord::Store. Now you can set
+ your custom coder like this:
+
+ store :settings, accessors: [ :color, :homepage ], coder: JSON
+
+ *Andrey Voronkov*
+
+* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by
+ default to avoid silent data loss. This can be disabled by specifying
+ `strict: false` in your `database.yml`.
+
+ *Michael Pearson*
+
+* Added default order to `first` to assure consistent results among
+ diferent database engines. Introduced `take` as a replacement to
+ the old behavior of `first`.
+
+ *Marcelo Silveira*
+
+* Added an :index option to automatically create indexes for references
+ and belongs_to statements in migrations.
+
+ The `references` and `belongs_to` methods now support an `index`
+ option that receives either a boolean value or an options hash
+ that is identical to options available to the add_index method:
+
+ create_table :messages do |t|
+ t.references :person, :index => true
+ end
+
+ Is the same as:
+
+ create_table :messages do |t|
+ t.references :person
+ end
+ add_index :messages, :person_id
+
+ Generators have also been updated to use the new syntax.
+
+ [Joshua Wood]
+
+* Added bang methods for mutating `ActiveRecord::Relation` objects.
+ For example, while `foo.where(:bar)` will return a new object
+ leaving `foo` unchanged, `foo.where!(:bar)` will mutate the foo
+ object
+
+ *Jon Leighton*
+
* Added `#find_by` and `#find_by!` to mirror the functionality
provided by dynamic finders in a way that allows dynamic input more
easily:
@@ -210,7 +324,7 @@
* PostgreSQL hstore types are automatically deserialized from the database.
-## Rails 3.2.3 (unreleased) ##
+## Rails 3.2.3 (March 30, 2012) ##
* Added find_or_create_by_{attribute}! dynamic method. *Andrew White*
@@ -551,19 +665,6 @@
a URI that specifies the connection configuration. For example:
ActiveRecord::Base.establish_connection 'postgres://localhost/foo'
-* Active Record's dynamic finder will now raise the error if you passing in less number of arguments than what you call in method signature.
-
- So if you were doing this and expecting the second argument to be nil:
-
- User.find_by_username_and_group("sikachu")
-
- You'll now get `ArgumentError: wrong number of arguments (1 for 2).` You'll then have to do this:
-
- User.find_by_username_and_group("sikachu", nil)
-
- *Prem Sichanugrist*
-
-
## Rails 3.1.0 (August 30, 2011) ##
* Add a proxy_association method to association proxies, which can be called by association
@@ -598,7 +699,7 @@
has_one :account
end
- user.build_account{ |a| a.credit_limit => 100.0 }
+ user.build_account{ |a| a.credit_limit = 100.0 }
The block is called after the instance has been initialized. *Andrew White*
@@ -3971,7 +4072,7 @@
* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 *Alisdair McDiarmid*
-* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
+* Fixed saving of in-memory association structures to happen as an after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
* Allow any Enumerable, not just Array, to work as bind variables #1344 *Jeremy Kemper*
@@ -5484,7 +5585,7 @@
* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 *Alisdair McDiarmid*
-* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
+* Fixed saving of in-memory association structures to happen as an after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
* Allow any Enumerable, not just Array, to work as bind variables #1344 *Jeremy Kemper*
@@ -6406,7 +6507,7 @@
post.categories.push_with_attributes(category, :added_on => Date.today)
post.categories.first.added_on # => Date.today
- NOTE: The categories table doesn't have a added_on column, it's the categories_post join table that does!
+ NOTE: The categories table doesn't have an added_on column, it's the categories_post join table that does!
* Fixed that :exclusively_dependent and :dependent can't be activated at the same time on has_many associations *Jeremy Kemper*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 4090293b56..7feb0b75a0 100755..100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 26eb74df0f..e8e5f4adfe 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -22,4 +22,6 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('arel', '~> 3.0.2')
+
+ s.add_dependency('active_record_deprecated_finders', '0.0.1')
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 0cb0644bcf..210820062b 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -22,9 +22,9 @@
#++
require 'active_support'
-require 'active_support/i18n'
require 'active_model'
require 'arel'
+require 'active_record_deprecated_finders'
require 'active_record/version'
@@ -62,8 +62,6 @@ module ActiveRecord
autoload :CounterCache
autoload :ConnectionHandling
autoload :DynamicMatchers
- autoload :DynamicFinderMatch
- autoload :DynamicScopeMatch
autoload :Explain
autoload :Inheritance
autoload :Integration
@@ -146,4 +144,6 @@ ActiveSupport.on_load(:active_record) do
Arel::Table.engine = self
end
-I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index c39284539c..c7a329d74d 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -71,7 +71,7 @@ module ActiveRecord
# Now it's possible to access attributes from the database through the value objects instead. If
# you choose to name the composition the same as the attribute's name, it will be the only way to
# access that attribute. That's the case with our +balance+ attribute. You interact with the value
- # objects just like you would any other attribute, though:
+ # objects just like you would with any other attribute:
#
# customer.balance = Money.new(20) # sets the Money value object and the attribute
# customer.balance # => Money value object
@@ -86,6 +86,12 @@ module ActiveRecord
# customer.address_street = "Hyancintvej"
# customer.address_city = "Copenhagen"
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ #
+ # customer.address_street = "Vesterbrogade"
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ # customer.clear_aggregation_cache
+ # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
+ #
# customer.address = Address.new("May Street", "Chicago")
# customer.address_street # => "May Street"
# customer.address_city # => "Chicago"
@@ -101,8 +107,8 @@ module ActiveRecord
# ActiveRecord::Base classes are entity objects.
#
# It's also important to treat the value objects as immutable. Don't allow the Money object to have
- # its amount changed after creation. Create a new Money object with the new value instead. This
- # is exemplified by the Money#exchange_to method that returns a new value object instead of changing
+ # its amount changed after creation. Create a new Money object with the new value instead. The
+ # Money#exchange_to method is an example of this. It returns a new value object instead of changing
# its own values. Active Record won't persist value objects that have been changed through means
# other than the writer method.
#
@@ -119,7 +125,7 @@ module ActiveRecord
# option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
# a custom constructor to be specified.
#
- # When a new value is assigned to the value object the default assumption is that the new value
+ # When a new value is assigned to the value object, the default assumption is that the new value
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
# converted to an instance of value class if necessary.
#
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index b901f06ca4..68f8bbeb1c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -543,7 +543,7 @@ module ActiveRecord
# end
#
# @group = Group.first
- # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
# @group.avatars # selects all avatars by going through the User join model.
#
# An important caveat with going through +has_one+ or +has_many+ associations on the
@@ -1129,7 +1129,7 @@ module ActiveRecord
# it would skip the first 4 rows.
# [:select]
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
- # you, for example, want to do a join but not include the joined columns. Do not forget
+ # you want to do a join but not include the joined columns, for example. Do not forget
# to include the primary and foreign keys, otherwise it will raise an error.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
@@ -1264,8 +1264,8 @@ module ActiveRecord
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
- # you want to do a join but not include the joined columns. Do not forget to include the
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you want to do a join but not include the joined columns, for example. 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>,
@@ -1355,7 +1355,7 @@ module ActiveRecord
# SQL fragment, such as <tt>authorized = 1</tt>.
# [:select]
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed
- # if, for example, you want to do a join but not include the joined columns. Do not
+ # if you want to do a join but not include the joined columns, for example. Do not
# forget to include the primary and foreign keys, otherwise it will raise an error.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
@@ -1382,7 +1382,7 @@ module ActiveRecord
# and +decrement_counter+. The counter cache is incremented when an object of this
# class is created and decremented when it's destroyed. This requires that a column
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
- # is used on the associate class (such as a Post class) - that is the migration for
+ # is used on the associate class (such as a Post class) - that is the migration for
# <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
# return the count cached, see note below). You can also specify a custom counter
# cache column by providing a column name instead of a +true+/+false+ value to this
@@ -1432,7 +1432,7 @@ module ActiveRecord
# Specifies a many-to-many relationship with another class. This associates two classes via an
# intermediate join table. Unless the join table is explicitly specified as an option, it is
# guessed using the lexical order of the class names. So a join between Developer and Project
- # will give the default join table name of "developers_projects" because "D" outranks "P".
+ # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
# means that if the strings are of different lengths, and the strings are equal when compared
# up to the shortest length, then the longer string is considered of higher
@@ -1576,8 +1576,8 @@ module ActiveRecord
# An integer determining the offset from where the rows should be fetched. So at 5,
# it would skip the first 4 rows.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
- # you want to do a join but not include the joined columns. Do not forget to include the primary
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you want to do a join but exclude the joined columns, for example. Do not forget to include the primary
# and foreign keys, otherwise it will raise an error.
# [:readonly]
# If true, all the associated objects are readonly through the association.
@@ -1596,7 +1596,7 @@ module ActiveRecord
# has_and_belongs_to_many :categories, :join_table => "prods_cats"
# has_and_belongs_to_many :categories, :readonly => true
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
- # "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}"
+ # proc { |record| "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}" }
def has_and_belongs_to_many(name, options = {}, &extension)
Builder::HasAndBelongsToMany.build(self, name, options, &extension)
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index fb0ca15c23..e75003f261 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -25,10 +25,7 @@ module ActiveRecord
def initialize(owner, reflection)
reflection.check_validity!
- @target = nil
@owner, @reflection = owner, reflection
- @updated = false
- @stale_state = nil
reset
reset_scope
@@ -39,13 +36,14 @@ module ActiveRecord
# post.comments.aliased_table_name # => "comments"
#
def aliased_table_name
- reflection.klass.table_name
+ klass.table_name
end
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
def reset
@loaded = false
@target = nil
+ @stale_state = nil
end
# Reloads the \target and returns +self+ on success.
@@ -134,7 +132,8 @@ module ActiveRecord
# ActiveRecord::RecordNotFound is rescued within the method, and it is
# not reraised. The proxy is \reset and +nil+ is the return value.
def load_target
- @target ||= find_target if find_target?
+ @target = find_target if (@stale_state && stale_target?) || find_target?
+
loaded! unless loaded?
target
rescue ActiveRecord::RecordNotFound
@@ -215,10 +214,6 @@ module ActiveRecord
def stale_state
end
- def association_class
- @reflection.klass
- end
-
def build_record(attributes, options)
reflection.build_association(attributes, options) do |record|
attributes = create_scope.except(*(record.changed - [reflection.foreign_key]))
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 2972b7e13e..5a44d3a156 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -15,19 +15,20 @@ module ActiveRecord
def scope
scope = klass.unscoped
- scope = scope.extending(*Array(options[:extend]))
+
+ scope.extending!(*Array(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, :references, :order, :limit, :joins, :group, :having, :offset, :select))
+ scope.merge!(options.slice(
+ :readonly, :references, :order, :limit, :joins, :group, :having, :offset, :select, :uniq))
- if options[:through] && !options[:include]
- scope = scope.includes(source_options[:include])
+ if options[:include]
+ scope.includes! options[:include]
+ elsif options[:through]
+ scope.includes! source_options[:include]
end
- scope = scope.uniq if options[:uniq]
-
add_constraints(scope)
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 97f531d064..ddfc6f6c05 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -14,6 +14,11 @@ module ActiveRecord
self.target = record
end
+ def reset
+ super
+ @updated = false
+ end
+
def updated?
@updated
end
@@ -72,7 +77,7 @@ module ActiveRecord
end
def stale_state
- owner[reflection.foreign_key].to_s
+ owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 2ee5dbbd70..88ce03a3cd 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -27,7 +27,8 @@ module ActiveRecord
end
def stale_state
- [super, owner[reflection.foreign_type].to_s]
+ foreign_key = super
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
end
end
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 0b634ab944..30fc44b4c2 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
@@ -18,7 +18,7 @@ module ActiveRecord::Associations::Builder
model.send(:include, Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy_associations
- association(#{name.to_sym.inspect}).delete_all_on_destroy
+ association(#{name.to_sym.inspect}).delete_all
super
end
RUBY
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 9ddfd433e4..d37d4e9d33 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -42,7 +42,7 @@ module ActiveRecord::Associations::Builder
def define_delete_all_dependency_method
name = self.name
mixin.redefine_method(dependency_method_name) do
- association(name).delete_all_on_destroy
+ association(name).delete_all
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index da4c311bce..8dbcf8b225 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -16,12 +16,6 @@ module ActiveRecord
# If you need to work on all current children, new and existing records,
# +load_target+ and the +loaded+ flag are your friends.
class CollectionAssociation < Association #:nodoc:
- attr_reader :proxy
-
- def initialize(owner, reflection)
- super
- @proxy = CollectionProxy.new(self)
- end
# Implements the reader method, e.g. foo.items for Foo.has_many :items
def reader(force_reload = false)
@@ -31,7 +25,7 @@ module ActiveRecord
reload
end
- proxy
+ CollectionProxy.new(self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -71,7 +65,7 @@ module ActiveRecord
end
def reset
- @loaded = false
+ super
@target = []
end
@@ -152,19 +146,12 @@ module ActiveRecord
#
# See delete for more info.
def delete_all
- delete(load_target).tap do
+ delete(:all).tap do
reset
loaded!
end
end
- # Called when the association is declared as :dependent => :delete_all. This is
- # an optimised version which avoids loading the records into memory. Not really
- # for public consumption.
- def delete_all_on_destroy
- scoped.delete_all
- end
-
# Destroy all the records from this association.
#
# See destroy for more info.
@@ -224,7 +211,17 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
- delete_or_destroy(records, options[:dependent])
+ dependent = options[:dependent]
+
+ if records.first == :all
+ if loaded? || dependent == :destroy
+ delete_or_destroy(load_target, dependent)
+ else
+ delete_records(:all, dependent)
+ end
+ else
+ delete_or_destroy(records, dependent)
+ end
end
# Destroy +records+ and remove them from this association calling
@@ -248,8 +245,12 @@ module ActiveRecord
# This method is abstract in the sense that it relies on
# +count_records+, which is a method descendants have to provide.
def size
- if !find_target? || (loaded? && !options[:uniq])
- target.size
+ if !find_target? || loaded?
+ if options[:uniq]
+ target.uniq.size
+ else
+ target.size
+ end
elsif !loaded? && options[:group]
load_target.size
elsif !loaded? && !options[:uniq] && target.is_a?(Array)
@@ -474,6 +475,8 @@ module ActiveRecord
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
end
+
+ target
end
def concat_records(records)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 5eda0387c4..cf4cc98f38 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -33,14 +33,7 @@ module ActiveRecord
#
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
- class CollectionProxy # :nodoc:
- alias :proxy_extend :extend
-
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
-
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
- :lock, :readonly, :having, :pluck, :to => :scoped
-
+ class CollectionProxy < Relation # :nodoc:
delegate :target, :load_target, :loaded?, :to => :@association
delegate :select, :find, :first, :last,
@@ -52,7 +45,8 @@ module ActiveRecord
def initialize(association)
@association = association
- Array(association.options[:extend]).each { |ext| proxy_extend(ext) }
+ super association.klass, association.klass.arel_table
+ merge! association.scoped
end
alias_method :new, :build
@@ -61,50 +55,28 @@ module ActiveRecord
@association
end
- def scoped
- association = @association
- association.scoped.extending do
- define_method(:proxy_association) { association }
- end
+ # We don't want this object to be put on the scoping stack, because
+ # that could create an infinite loop where we call an @association
+ # method, which gets the current scope, which is this object, which
+ # delegates to @association, and so on.
+ def scoping
+ @association.scoped.scoping { yield }
end
- def respond_to?(name, include_private = false)
- super ||
- (load_target && target.respond_to?(name, include_private)) ||
- proxy_association.klass.respond_to?(name, include_private)
+ def spawn
+ scoped
end
- def method_missing(method, *args, &block)
- match = DynamicFinderMatch.match(method)
- if match && match.instantiator?
- send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
- proxy_association.send :set_owner_attributes, r
- proxy_association.send :add_to_target, r
- yield(r) if block_given?
- end
-
- elsif target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
- if load_target
- if target.respond_to?(method)
- target.send(method, *args, &block)
- else
- begin
- super
- rescue NoMethodError => e
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
- end
- end
- end
+ def scoped(options = nil)
+ association = @association
- else
- scoped.readonly(nil).send(method, *args, &block)
+ super.extending! do
+ define_method(:proxy_association) { association }
end
end
- # Forwards <tt>===</tt> explicitly to the \target because the instance method
- # removal above doesn't catch it. Loads the \target if needed.
- def ===(other)
- other === load_target
+ def ==(other)
+ load_target == other
end
def to_ary
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 a4cea99372..58d041ec1d 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
@@ -32,10 +32,6 @@ module ActiveRecord
record
end
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
- # SQL-only implementation.
- alias delete_all_on_destroy delete_all
-
private
def count_records
@@ -44,13 +40,20 @@ module ActiveRecord
def delete_records(records, method)
if sql = options[:delete_sql]
+ records = load_target if records == :all
records.each { |record| owner.connection.delete(interpolate(sql, record)) }
else
- relation = join_table
- stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
- and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
- ).compile_delete
- owner.connection.delete stmt
+ relation = join_table
+ condition = relation[reflection.foreign_key].eq(owner.id)
+
+ unless records == :all
+ condition = condition.and(
+ relation[reflection.association_foreign_key]
+ .in(records.map { |x| x.id }.compact)
+ )
+ end
+
+ owner.connection.delete(relation.where(condition).compile_delete)
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 059e6c77bc..e631579087 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -89,8 +89,12 @@ module ActiveRecord
records.each { |r| r.destroy }
update_counter(-records.length) unless inverse_updates_counter_cache?
else
- keys = records.map { |r| r[reflection.association_primary_key] }
- scope = scoped.where(reflection.association_primary_key => keys)
+ if records == :all
+ scope = scoped
+ else
+ keys = records.map { |r| r[reflection.association_primary_key] }
+ scope = scoped.where(reflection.association_primary_key => keys)
+ end
if method == :delete_all
update_counter(-scope.delete_all)
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 53d49fef2e..2683aaf5da 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -54,10 +54,6 @@ module ActiveRecord
record
end
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
- # SQL-only implementation.
- alias delete_all_on_destroy delete_all
-
private
def through_association
@@ -126,7 +122,12 @@ module ActiveRecord
def delete_records(records, method)
ensure_not_nested
- scope = through_association.scoped.where(construct_join_attributes(*records))
+ # This is unoptimised; it will load all the target records
+ # even when we just want to delete everything.
+ records = load_target if records == :all
+
+ scope = through_association.scoped
+ scope.where! construct_join_attributes(*records)
case method
when :destroy
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index fd0e90aaf0..be890e5767 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -62,7 +62,7 @@ module ActiveRecord
# properly support stale-checking for nested associations.
def stale_state
if through_reflection.macro == :belongs_to
- owner[through_reflection.foreign_key].to_s
+ owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 3005bef092..d545e7799d 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -350,7 +350,7 @@ module ActiveRecord
end
records_to_destroy.each do |record|
- association.proxy.destroy(record)
+ association.destroy(record)
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index d25a821688..189985b671 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -304,21 +304,22 @@ module ActiveRecord #:nodoc:
# (or a bad spelling of an existing one).
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
# specified in the association definition.
- # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
- # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
+ # <tt>attributes=</tt> method.
+ # You can inspect the +attribute+ property of the exception object to determine which attribute
+ # triggered the error.
+ # * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
# before querying.
- # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
- # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
- # nothing was found, please check its documentation for further details.
- # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
# AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
- # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
- # <tt>attributes=</tt> method.
- # You can inspect the +attribute+ property of the exception object to determine which attribute
- # triggered the error.
+ # * RecordInvalid - raised by save! and create! when the record is invalid.
+ # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
+ # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
+ # nothing was found, please check its documentation for further details.
+ # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
+ # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
#
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
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 561e48d52e..46c7fc71ac 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -2,6 +2,7 @@ require 'thread'
require 'monitor'
require 'set'
require 'active_support/core_ext/module/deprecation'
+require 'timeout'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -11,9 +12,6 @@ module ActiveRecord
# Raised when a connection pool is full and another connection is requested
class PoolFullError < ConnectionNotEstablished
- def initialize size, timeout
- super("Connection pool of size #{size} and timeout #{timeout}s is full")
- end
end
module ConnectionAdapters
@@ -94,6 +92,21 @@ module ActiveRecord
attr_accessor :automatic_reconnect, :timeout
attr_reader :spec, :connections, :size, :reaper
+ class Latch # :nodoc:
+ def initialize
+ @mutex = Mutex.new
+ @cond = ConditionVariable.new
+ end
+
+ def release
+ @mutex.synchronize { @cond.broadcast }
+ end
+
+ def await
+ @mutex.synchronize { @cond.wait @mutex }
+ end
+ end
+
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
# object which describes database connection information (e.g. adapter,
# host name, username, password, etc), as well as the maximum size for
@@ -115,6 +128,7 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ @latch = Latch.new
@connections = []
@automatic_reconnect = true
end
@@ -139,8 +153,10 @@ module ActiveRecord
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
def release_connection(with_id = current_connection_id)
- conn = @reserved_connections.delete(with_id)
- checkin conn if conn
+ synchronize do
+ conn = @reserved_connections.delete(with_id)
+ checkin conn if conn
+ end
end
# If a connection already exists yield it to the block. If no connection
@@ -205,23 +221,23 @@ module ActiveRecord
# Raises:
# - PoolFullError: no connection can be obtained from the pool.
def checkout
- # Checkout an available connection
- synchronize do
- # Try to find a connection that hasn't been leased, and lease it
- conn = connections.find { |c| c.lease }
-
- # If all connections were leased, and we have room to expand,
- # create a new connection and lease it.
- if !conn && connections.size < size
- conn = checkout_new_connection
- conn.lease
- end
+ loop do
+ # Checkout an available connection
+ synchronize do
+ # Try to find a connection that hasn't been leased, and lease it
+ conn = connections.find { |c| c.lease }
+
+ # If all connections were leased, and we have room to expand,
+ # create a new connection and lease it.
+ if !conn && connections.size < size
+ conn = checkout_new_connection
+ conn.lease
+ end
- if conn
- checkout_and_verify conn
- else
- raise PoolFullError.new(size, timeout)
+ return checkout_and_verify(conn) if conn
end
+
+ Timeout.timeout(@timeout, PoolFullError) { @latch.await }
end
end
@@ -238,6 +254,7 @@ module ActiveRecord
release conn
end
+ @latch.release
end
# Remove a connection from the connection pool. The connection will
@@ -250,6 +267,7 @@ module ActiveRecord
# from the reserved hash will be a little easier.
release conn
end
+ @latch.release
end
# Removes dead connections from the pool. A dead connection can occur
@@ -262,17 +280,16 @@ module ActiveRecord
remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
end
end
+ @latch.release
end
private
def release(conn)
- thread_id = nil
-
- if @reserved_connections[current_connection_id] == conn
- thread_id = current_connection_id
+ thread_id = if @reserved_connections[current_connection_id] == conn
+ current_connection_id
else
- thread_id = @reserved_connections.keys.find { |k|
+ @reserved_connections.keys.find { |k|
@reserved_connections[k] == conn
}
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 174450eb00..7b2961a04a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -57,21 +57,21 @@ module ActiveRecord
end
# Executes insert +sql+ statement in the context of this connection using
- # +binds+ as the bind substitutes. +name+ is the logged along with
+ # +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
- def exec_insert(sql, name, binds)
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
exec_query(sql, name, binds)
end
# Executes delete +sql+ statement in the context of this connection using
- # +binds+ as the bind substitutes. +name+ is the logged along with
+ # +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_delete(sql, name, binds)
exec_query(sql, name, binds)
end
# Executes update +sql+ statement in the context of this connection using
- # +binds+ as the bind substitutes. +name+ is the logged along with
+ # +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_update(sql, name, binds)
exec_query(sql, name, binds)
@@ -87,7 +87,7 @@ module ActiveRecord
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
- value = exec_insert(sql, name, binds)
+ value = exec_insert(sql, name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end
@@ -312,13 +312,27 @@ module ActiveRecord
# on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
# an UPDATE statement, so in the mysql adapters we redefine this to do that.
def join_to_update(update, select) #:nodoc:
- subselect = select.clone
- subselect.projections = [update.key]
+ key = update.key
+ subselect = subquery_for(key, select)
- update.where update.key.in(subselect)
+ update.where key.in(subselect)
+ end
+
+ def join_to_delete(delete, select, key) #:nodoc:
+ subselect = subquery_for(key, select)
+
+ delete.where key.in(subselect)
end
protected
+
+ # Return a subquery for the given key using the join information.
+ def subquery_for(key, select)
+ subselect = select.clone
+ subselect.projections = [key]
+ subselect
+ end
+
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 44ac37c498..6f9f0399db 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -34,6 +34,7 @@ module ActiveRecord
when Numeric then value.to_s
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
+ when Class then "'#{value.to_s}'"
else
"'#{quote_string(YAML.dump(value))}'"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7ee8f40631..df78ba6c5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -23,7 +23,7 @@ module ActiveRecord
end
def sql_type
- base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
+ base.type_to_sql(type.to_sym, limit, precision, scale)
end
def to_sql
@@ -65,11 +65,12 @@ module ActiveRecord
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
- attr_accessor :columns
+ attr_accessor :columns, :indexes
def initialize(base)
@columns = []
@columns_hash = {}
+ @indexes = {}
@base = base
end
@@ -212,19 +213,22 @@ module ActiveRecord
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
- # options, these will be used when creating the <tt>_type</tt> column. So what can be written like this:
+ # options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
+ # will also create an index, similar to calling <tt>add_index</tt>. So what can be written like this:
#
# create_table :taggings do |t|
# t.integer :tag_id, :tagger_id, :taggable_id
# t.string :tagger_type
# t.string :taggable_type, :default => 'Photo'
# end
+ # add_index :taggings, :tag_id, :name => 'index_taggings_on_tag_id'
+ # add_index :taggings, [:tagger_id, :tagger_type]
#
# Can also be written as follows using references:
#
# create_table :taggings do |t|
- # t.references :tag
- # t.references :tagger, :polymorphic => true
+ # t.references :tag, :index => { :name => 'index_taggings_on_tag_id' }
+ # t.references :tagger, :polymorphic => true, :index => true
# t.references :taggable, :polymorphic => { :default => 'Photo' }
# end
def column(name, type, options = {})
@@ -255,6 +259,14 @@ module ActiveRecord
end # end
EOV
end
+
+ # Adds index options to the indexes hash, keyed by column name
+ # This is primarily used to track indexes that need to be created after the table
+ #
+ # index(:account_id, :name => 'index_projects_on_account_id')
+ def index(column_name, options = {})
+ indexes[column_name] = options
+ end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table.
@@ -267,9 +279,11 @@ module ActiveRecord
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
args.each do |col|
column("#{col}_id", :integer, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
alias :belongs_to :references
@@ -334,7 +348,7 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
- # ===== Example
+ #
# ====== Creating a simple column
# t.column(:name, :string)
def column(column_name, type, options = {})
@@ -349,7 +363,6 @@ module ActiveRecord
# Adds a new index to the table. +column_name+ can be a single Symbol, or
# an Array of Symbols. See SchemaStatements#add_index
#
- # ===== Examples
# ====== Creating a simple index
# t.index(:name)
# ====== Creating a unique index
@@ -366,7 +379,7 @@ module ActiveRecord
end
# Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
- # ===== Example
+ #
# t.timestamps
def timestamps
@base.add_timestamps(@table_name)
@@ -374,7 +387,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
- # ===== Examples
+ #
# t.change(:name, :string, :limit => 80)
# t.change(:description, :text)
def change(column_name, type, options = {})
@@ -382,7 +395,7 @@ module ActiveRecord
end
# Sets a new default value for a column. See SchemaStatements#change_column_default
- # ===== Examples
+ #
# t.change_default(:qualification, 'new')
# t.change_default(:authorized, 1)
def change_default(column_name, default)
@@ -390,16 +403,15 @@ module ActiveRecord
end
# Removes the column(s) from the table definition.
- # ===== Examples
+ #
# t.remove(:qualification)
# t.remove(:qualification, :experience)
def remove(*column_names)
- @base.remove_column(@table_name, column_names)
+ @base.remove_column(@table_name, *column_names)
end
# Removes the given index from the table.
#
- # ===== Examples
# ====== Remove the index_table_name_on_column in the table_name table
# t.remove_index :column
# ====== Remove the index named index_table_name_on_branch_id in the table_name table
@@ -413,14 +425,14 @@ module ActiveRecord
end
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
- # ===== Example
+ #
# t.remove_timestamps
def remove_timestamps
@base.remove_timestamps(@table_name)
end
# Renames a column.
- # ===== Example
+ #
# t.rename(:description, :name)
def rename(column_name, new_column_name)
@base.rename_column(@table_name, column_name, new_column_name)
@@ -428,23 +440,25 @@ module ActiveRecord
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
- # ===== Examples
+ #
# t.references(:goat)
# t.references(:goat, :polymorphic => true)
# t.belongs_to(:goat)
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
args.each do |col|
@base.add_column(@table_name, "#{col}_id", :integer, options)
@base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
alias :belongs_to :references
# Removes a reference. Optionally removes a +type+ column.
# <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
- # ===== Examples
+ #
# t.remove_references(:goat)
# t.remove_references(:goat, :polymorphic => true)
# t.remove_belongs_to(:goat)
@@ -459,7 +473,7 @@ module ActiveRecord
alias :remove_belongs_to :remove_references
# Adds a column or columns of a specified type
- # ===== Examples
+ #
# t.string(:goat)
# t.string(:goat, :sheep)
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 8b9e830040..62b0f51bb2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -16,12 +16,11 @@ module ActiveRecord
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
- table_name[0...table_alias_length].gsub(/\./, '_')
+ table_name[0...table_alias_length].tr('.', '_')
end
# Checks to see if the table +table_name+ exists on the database.
#
- # === Example
# table_exists?(:developers)
def table_exists?(table_name)
tables.include?(table_name.to_s)
@@ -32,7 +31,6 @@ module ActiveRecord
# Checks to see if an index exists on a table for a given index definition.
#
- # === Examples
# # Check an index exists
# index_exists?(:suppliers, :company_id)
#
@@ -126,7 +124,6 @@ module ActiveRecord
# Set to true to drop the table before creating it.
# Defaults to false.
#
- # ===== Examples
# ====== Add a backend specific option to the generated SQL (MySQL)
# create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
@@ -171,6 +168,7 @@ module ActiveRecord
create_sql << td.to_sql
create_sql << ") #{options[:options]}"
execute create_sql
+ td.indexes.each_pair { |c,o| add_index table_name, c, o }
end
# Creates a new join table with the name created using the lexical order of the first two
@@ -192,7 +190,6 @@ module ActiveRecord
# Set to true to drop the table before creating it.
# Defaults to false.
#
- # ===== Examples
# ====== Add a backend specific option to the generated SQL (MySQL)
# create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
@@ -214,7 +211,6 @@ module ActiveRecord
# A block for changing columns in +table+.
#
- # === Example
# # change_table() yields a Table instance
# change_table(:suppliers) do |t|
# t.column :name, :string, :limit => 60
@@ -228,7 +224,6 @@ module ActiveRecord
#
# Defaults to false.
#
- # ===== Examples
# ====== Add a column
# change_table(:suppliers) do |t|
# t.column :name, :string, :limit => 60
@@ -287,7 +282,7 @@ module ActiveRecord
end
# Renames a table.
- # ===== Example
+ #
# rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
raise NotImplementedError, "rename_table is not implemented"
@@ -307,7 +302,7 @@ module ActiveRecord
end
# Removes the column(s) from the table definition.
- # ===== Examples
+ #
# remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience)
def remove_column(table_name, *column_names)
@@ -317,7 +312,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
- # ===== Examples
+ #
# change_column(:suppliers, :name, :string, :limit => 80)
# change_column(:accounts, :description, :text)
def change_column(table_name, column_name, type, options = {})
@@ -325,7 +320,7 @@ module ActiveRecord
end
# 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)
@@ -334,7 +329,7 @@ module ActiveRecord
end
# Renames a column.
- # ===== Example
+ #
# rename_column(:suppliers, :description, :name)
def rename_column(table_name, column_name, new_column_name)
raise NotImplementedError, "rename_column is not implemented"
@@ -346,8 +341,6 @@ module ActiveRecord
# The index will be named after the table and the column name(s), unless
# you pass <tt>:name</tt> as an option.
#
- # ===== Examples
- #
# ====== Creating a simple index
# add_index(:suppliers, :name)
# generates
@@ -375,7 +368,7 @@ module ActiveRecord
# Note: SQLite doesn't support index length
#
# ====== Creating an index with a sort order (desc or asc, asc is the default)
- # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :part_id => :asc})
+ # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :party_id => :asc})
# generates
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
@@ -536,7 +529,7 @@ module ActiveRecord
end
# Adds timestamps (created_at and updated_at) columns to the named table.
- # ===== Examples
+ #
# add_timestamps(:suppliers)
def add_timestamps(table_name)
add_column table_name, :created_at, :datetime
@@ -544,7 +537,7 @@ module ActiveRecord
end
# Removes the timestamp columns (created_at and updated_at) from the table definition.
- # ===== Examples
+ #
# remove_timestamps(:suppliers)
def remove_timestamps(table_name)
remove_column table_name, :updated_at
@@ -617,8 +610,6 @@ module ActiveRecord
end
def columns_for_remove(table_name, *column_names)
- column_names = column_names.flatten
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
column_names.map {|column_name| quote_column_name(column_name) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 1d713e472b..c6faae77cc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -163,11 +163,6 @@ module ActiveRecord
# QUOTING ==================================================
- # Override to return the quoted table name. Defaults to column quoting.
- def quote_table_name(name)
- quote_column_name(name)
- end
-
# Returns a bind substitution value given a +column+ and list of current
# +binds+
def substitute_at(column, index)
@@ -299,7 +294,7 @@ module ActiveRecord
raise exception
end
- def translate_exception(e, message)
+ def translate_exception(exception, message)
# override in derived class
ActiveRecord::StatementInvalid.new(message)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 64f922b7ad..9794c5663e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -251,7 +251,7 @@ module ActiveRecord
end
# MysqlAdapter has to free a result after using it, so we use this method to write
- # stuff in a abstract way without concerning ourselves about whether it needs to be
+ # stuff in an abstract way without concerning ourselves about whether it needs to be
# explicitly freed or not.
def execute_and_free(sql, name = nil) #:nodoc:
yield execute(sql, name)
@@ -294,19 +294,10 @@ module ActiveRecord
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
- # these, we must use a subquery. However, MySQL is too stupid to create a
- # temporary table for this automatically, so we have to give it some prompting
- # in the form of a subsubquery. Ugh!
+ # these, we must use a subquery.
def join_to_update(update, select) #:nodoc:
if select.limit || select.offset || select.orders.any?
- subsubselect = select.clone
- subsubselect.projections = [update.key]
-
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(update.key.name)
- subselect.from subsubselect.as('__active_record_temp')
-
- update.where update.key.in(subselect)
+ super
else
update.table select.source
update.wheres = select.constraints
@@ -525,7 +516,7 @@ module ActiveRecord
def pk_and_sequence_for(table)
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
create_table = each_hash(result).first[:"Create Table"]
- if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
+ if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
keys = $1.split(",").map { |key| key.delete('`"') }
keys.length == 1 ? [keys.first, nil] : nil
else
@@ -558,6 +549,17 @@ module ActiveRecord
protected
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
+ # to give it some prompting in the form of a subsubquery. Ugh!
+ def subquery_for(key, select)
+ subsubselect = select.clone
+ subsubselect.projections = [key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(key.name)
+ subselect.from subsubselect.as('__active_record_temp')
+ end
+
def add_index_length(option_strings, column_names, options = {})
if options.is_a?(Hash) && length = options[:length]
case length
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index b7e1513422..01bd3ae26c 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -95,7 +95,7 @@ module ActiveRecord
case type
when :string, :text then value
- when :integer then value.to_i rescue value ? 1 : 0
+ when :integer then value.to_i
when :float then value.to_f
when :decimal then klass.value_to_decimal(value)
when :datetime, :timestamp then klass.string_to_time(value)
@@ -124,6 +124,7 @@ module ActiveRecord
when :binary then "#{klass}.binary_to_string(#{var_name})"
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
when :hstore then "#{klass}.string_to_hstore(#{var_name})"
+ when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
else var_name
end
end
@@ -158,7 +159,7 @@ module ActiveRecord
def value_to_date(value)
if value.is_a?(String)
- return nil if value.empty?
+ return nil if value.blank?
fast_string_to_date(value) || fallback_string_to_date(value)
elsif value.respond_to?(:to_date)
value.to_date
@@ -169,14 +170,14 @@ module ActiveRecord
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
fast_string_to_time(string) || fallback_string_to_time(string)
end
def string_to_dummy_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
string_to_time "2000-01-01 #{string}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 3f45f23de8..350ccce03d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -226,7 +226,7 @@ module ActiveRecord
end
alias :create :insert_sql
- def exec_insert(sql, name, binds)
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
execute to_sql(sql, binds), name
end
@@ -253,6 +253,14 @@ module ActiveRecord
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
variable_assignments = ['SQL_AUTO_IS_NULL=0']
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'"
+ end
+
encoding = @config[:encoding]
# make sure we set the encoding
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 724dbff1f0..0b6734b010 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -53,6 +53,7 @@ module ActiveRecord
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html)
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -404,6 +405,13 @@ module ActiveRecord
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging)
+ end
end
def select(sql, name = nil, binds = [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index c82afc232c..df3d5e4657 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -137,6 +137,14 @@ module ActiveRecord
end
end
+ class Cidr < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
+ end
+ end
+
class TypeMap
def initialize
@mapping = {}
@@ -212,11 +220,9 @@ module ActiveRecord
# FIXME: why are we keeping these types as strings?
alias_type 'tsvector', 'text'
alias_type 'interval', 'text'
- alias_type 'cidr', 'text'
- alias_type 'inet', 'text'
- alias_type 'macaddr', 'text'
alias_type 'bit', 'text'
alias_type 'varbit', 'text'
+ alias_type 'macaddr', 'text'
# FIXME: I don't think this is correct. We should probably be returning a parsed date,
# but the tests pass with a string returned.
@@ -237,6 +243,9 @@ module ActiveRecord
register_type 'polygon', OID::Identity.new
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
+
+ register_type 'cidr', OID::Cidr.new
+ alias_type 'inet', 'cidr'
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 10a178e369..14bc95abfe 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -8,6 +8,8 @@ require 'arel/visitors/bind_visitor'
gem 'pg', '~> 0.11'
require 'pg'
+require 'ipaddr'
+
module ActiveRecord
module ConnectionHandling
# Establishes a connection to the database that's used by all Active Record objects
@@ -17,7 +19,7 @@ module ActiveRecord
# Forward any unused config params to PGconn.connect.
[:statement_limit, :encoding, :min_messages, :schema_search_path,
:schema_order, :adapter, :pool, :wait_timeout, :template,
- :reaping_frequency].each do |key|
+ :reaping_frequency, :insert_returning].each do |key|
conn_params.delete key
end
conn_params.delete_if { |k,v| v.nil? }
@@ -79,6 +81,25 @@ module ActiveRecord
end
end
+ def string_to_cidr(string)
+ if string.nil?
+ nil
+ elsif String === string
+ IPAddr.new(string)
+ else
+ string
+ end
+
+ end
+
+ def cidr_to_string(object)
+ if IPAddr === object
+ "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
+ else
+ object
+ end
+ end
+
private
HstorePair = begin
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
@@ -88,9 +109,8 @@ module ActiveRecord
def escape_hstore(value)
value.nil? ? 'NULL'
- : value =~ /[=\s,>]/ ? '"%s"' % value.gsub(/(["\\])/, '\\\\\1')
: value == "" ? '""'
- : value.to_s.gsub(/(["\\])/, '\\\\\1')
+ : '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
end
end
# :startdoc:
@@ -198,6 +218,13 @@ module ActiveRecord
:decimal
when 'hstore'
:hstore
+ # Network address types
+ when 'inet'
+ :inet
+ when 'cidr'
+ :cidr
+ when 'macaddr'
+ :macaddr
# Character types
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
:string
@@ -212,9 +239,6 @@ module ActiveRecord
# Geometric types
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
:string
- # Network address types
- when /^(?:cidr|inet|macaddr)$/
- :string
# Bit strings
when /^bit(?: varying)?(?:\(\d+\))?$/
:string
@@ -259,6 +283,8 @@ module ActiveRecord
# <encoding></tt> call on the connection.
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT<tt> statements
+ # defaults to true.
#
# Any further options are used as connection parameters to libpq. See
# http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
@@ -281,6 +307,18 @@ module ActiveRecord
def hstore(name, options = {})
column(name, 'hstore', options)
end
+
+ def inet(name, options = {})
+ column(name, 'inet', options)
+ end
+
+ def cidr(name, options = {})
+ column(name, 'cidr', options)
+ end
+
+ def macaddr(name, options = {})
+ column(name, 'macaddr', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -300,7 +338,10 @@ module ActiveRecord
:boolean => { :name => "boolean" },
:xml => { :name => "xml" },
:tsvector => { :name => "tsvector" },
- :hstore => { :name => "hstore" }
+ :hstore => { :name => "hstore" },
+ :inet => { :name => "inet" },
+ :cidr => { :name => "cidr" },
+ :macaddr => { :name => "macaddr" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -406,6 +447,7 @@ module ActiveRecord
initialize_type_map
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
+ @use_insert_returning = @config.key?(:insert_returning) ? @config[:insert_returning] : true
end
# Clears the prepared statements cache.
@@ -508,9 +550,19 @@ module ActiveRecord
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
else super
end
+ when IPAddr
+ case column.sql_type
+ when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
+ else super
+ end
when Float
- return super unless value.infinite? && column.type == :datetime
- "'#{value.to_s.downcase}'"
+ if value.infinite? && column.type == :datetime
+ "'#{value.to_s.downcase}'"
+ elsif value.infinite? || value.nan?
+ "'#{value.to_s}'"
+ else
+ super
+ end
when Numeric
return super unless column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
@@ -542,6 +594,9 @@ module ActiveRecord
when Hash
return super unless 'hstore' == column.sql_type
PostgreSQLColumn.hstore_to_string(value)
+ when IPAddr
+ return super unless ['inet','cidr'].includes? column.sql_type
+ PostgreSQLColumn.cidr_to_string(value)
else
super
end
@@ -667,8 +722,11 @@ module ActiveRecord
pk = primary_key(table_ref) if table_ref
end
- if pk
+ if pk && use_insert_returning?
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
+ elsif pk
+ super
+ last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
else
super
end
@@ -783,11 +841,27 @@ module ActiveRecord
pk = primary_key(table_ref) if table_ref
end
- sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
+ if pk && use_insert_returning?
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}"
+ end
[sql, binds]
end
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
+ val = exec_query(sql, name, binds)
+ if !use_insert_returning? && pk
+ unless sequence_name
+ table_ref = extract_table_ref_from_insert_sql(sql)
+ sequence_name = default_sequence_name(table_ref, pk)
+ return val unless sequence_name
+ end
+ last_insert_id_result(sequence_name)
+ else
+ val
+ end
+ end
+
# Executes an UPDATE query and returns the number of affected tuples.
def update_sql(sql, name = nil)
super.cmd_tuples
@@ -1028,7 +1102,9 @@ module ActiveRecord
# Returns the sequence name for a table's primary key or some other specified key.
def default_sequence_name(table_name, pk = nil) #:nodoc:
- serial_sequence(table_name, pk || 'id').split('.').last
+ result = serial_sequence(table_name, pk || 'id')
+ return nil unless result
+ result.split('.').last
rescue ActiveRecord::StatementInvalid
"#{table_name}_#{pk || 'id'}_seq"
end
@@ -1188,14 +1264,25 @@ module ActiveRecord
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
- return 'integer' unless limit
-
- case limit
- when 1, 2; 'smallint'
- when 3, 4; 'integer'
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ case type.to_s
+ when 'binary'
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
+ end
+ when 'integer'
+ return 'integer' unless limit
+
+ case limit
+ when 1, 2; 'smallint'
+ when 3, 4; 'integer'
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ end
+ else
+ super
end
end
@@ -1210,7 +1297,10 @@ module ActiveRecord
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
+ order_columns = orders.collect do |s|
+ s = s.to_sql unless s.is_a?(String)
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
+ end
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}" }
@@ -1236,6 +1326,10 @@ module ActiveRecord
end
end
+ def use_insert_returning?
+ @use_insert_returning
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
def postgresql_version
@@ -1365,8 +1459,15 @@ module ActiveRecord
# Returns the current ID of a table's sequence.
def last_insert_id(sequence_name) #:nodoc:
- r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
- Integer(r.rows.first.first)
+ Integer(last_insert_id_value(sequence_name))
+ end
+
+ def last_insert_id_value(sequence_name)
+ last_insert_id_result(sequence_name).rows.first.first
+ end
+
+ def last_insert_id_result(sequence_name) #:nodoc:
+ exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
end
# Executes a SELECT query and returns the results, performing any data type
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index ee5d10859c..d4ffa82b17 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,6 +1,8 @@
-require 'active_record/connection_adapters/sqlite_adapter'
+require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/statement_pool'
+require 'arel/visitors/bind_visitor'
-gem 'sqlite3', '~> 1.3.5'
+gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
module ActiveRecord
@@ -19,10 +21,6 @@ module ActiveRecord
config[:database] = File.expand_path(config[:database], Rails.root)
end
- unless 'sqlite3' == config[:adapter]
- raise ArgumentError, 'adapter name should be "sqlite3"'
- end
-
db = SQLite3::Database.new(
config[:database],
:results_as_hash => true
@@ -35,7 +33,184 @@ module ActiveRecord
end
module ConnectionAdapters #:nodoc:
- class SQLite3Adapter < SQLiteAdapter # :nodoc:
+ class SQLite3Column < Column #:nodoc:
+ class << self
+ def binary_to_string(value)
+ if value.encoding != Encoding::ASCII_8BIT
+ value = value.force_encoding(Encoding::ASCII_8BIT)
+ end
+ value
+ end
+ end
+ end
+
+ # The SQLite3 adapter works SQLite 3.6.16 or newer
+ # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
+ #
+ # Options:
+ #
+ # * <tt>:database</tt> - Path to the database file.
+ class SQLite3Adapter < AbstractAdapter
+ class Version
+ include Comparable
+
+ def initialize(version_string)
+ @version = version_string.split('.').map { |v| v.to_i }
+ end
+
+ def <=>(version_string)
+ @version <=> version_string.split('.').map { |v| v.to_i }
+ end
+ end
+
+ class StatementPool < ConnectionAdapters::StatementPool
+ def initialize(connection, max)
+ super
+ @cache = Hash.new { |h,pid| h[pid] = {} }
+ end
+
+ def each(&block); cache.each(&block); end
+ def key?(key); cache.key?(key); end
+ def [](key); cache[key]; end
+ def length; cache.length; end
+
+ def []=(sql, key)
+ while @max <= cache.size
+ dealloc(cache.shift.last[:stmt])
+ end
+ cache[sql] = key
+ end
+
+ def clear
+ cache.values.each do |hash|
+ dealloc hash[:stmt]
+ end
+ cache.clear
+ end
+
+ private
+ def cache
+ @cache[$$]
+ end
+
+ def dealloc(stmt)
+ stmt.close unless stmt.closed?
+ end
+ end
+
+ class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
+ def initialize(connection, logger, config)
+ super(connection, logger)
+ @statements = StatementPool.new(@connection,
+ config.fetch(:statement_limit) { 1000 })
+ @config = config
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::SQLite.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
+ end
+
+ def adapter_name #:nodoc:
+ 'SQLite'
+ end
+
+ # Returns true
+ def supports_ddl_transactions?
+ true
+ end
+
+ # Returns true if SQLite version is '3.6.8' or greater, false otherwise.
+ def supports_savepoints?
+ sqlite_version >= '3.6.8'
+ end
+
+ # Returns true, since this connection adapter supports prepared statement
+ # caching.
+ def supports_statement_cache?
+ true
+ end
+
+ # Returns true, since this connection adapter supports migrations.
+ def supports_migrations? #:nodoc:
+ true
+ end
+
+ # Returns true.
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
+ def requires_reloading?
+ true
+ end
+
+ # Returns true
+ def supports_add_column?
+ true
+ 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
+
+ # Returns true
+ def supports_count_distinct? #:nodoc:
+ true
+ end
+
+ # Returns true
+ def supports_autoincrement? #:nodoc:
+ true
+ end
+
+ def supports_index_sort_order?
+ true
+ end
+
+ def native_database_types #:nodoc:
+ {
+ :primary_key => default_primary_key_type,
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "integer" },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "datetime" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "boolean" }
+ }
+ end
+
+ # Returns the current database encoding format as a string, eg: 'UTF-8'
+ def encoding
+ @connection.encoding.to_s
+ end
+
+ # Returns true.
+ def supports_explain?
+ true
+ end
+
+
+ # QUOTING ==================================================
+
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]
@@ -45,11 +220,392 @@ module ActiveRecord
end
end
- # Returns the current database encoding format as a string, eg: 'UTF-8'
- def encoding
- @connection.encoding.to_s
+
+ def quote_string(s) #:nodoc:
+ @connection.class.quote(s)
+ end
+
+ def quote_column_name(name) #:nodoc:
+ %Q("#{name.to_s.gsub('"', '""')}")
+ end
+
+ # Quote date/time values for use in SQL input. Includes microseconds
+ # if the value is a Time responding to usec.
+ def quoted_date(value) #:nodoc:
+ if value.respond_to?(:usec)
+ "#{super}.#{sprintf("%06d", value.usec)}"
+ else
+ super
+ end
+ end
+
+ def type_cast(value, column) # :nodoc:
+ return value.to_f if BigDecimal === value
+ return super unless String === value
+ return super unless column && value
+
+ value = super
+ if column.type == :string && value.encoding == Encoding::ASCII_8BIT
+ logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
+ value.encode! 'utf-8'
+ end
+ value
end
+ # DATABASE STATEMENTS ======================================
+
+ def explain(arel, binds = [])
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
+ end
+
+ class ExplainPrettyPrinter
+ # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
+ # the output of the SQLite shell:
+ #
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
+ #
+ def pp(result) # :nodoc:
+ result.rows.map do |row|
+ row.join('|')
+ end.join("\n") + "\n"
+ end
+ end
+
+ def exec_query(sql, name = nil, binds = [])
+ log(sql, name, binds) do
+
+ # Don't cache statements without bind values
+ if binds.empty?
+ stmt = @connection.prepare(sql)
+ cols = stmt.columns
+ records = stmt.to_a
+ stmt.close
+ stmt = records
+ else
+ cache = @statements[sql] ||= {
+ :stmt => @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ cols = cache[:cols] ||= stmt.columns
+ stmt.reset!
+ stmt.bind_params binds.map { |col, val|
+ type_cast(val, col)
+ }
+ end
+
+ ActiveRecord::Result.new(cols, stmt.to_a)
+ end
+ end
+
+ def exec_delete(sql, name = 'SQL', binds = [])
+ exec_query(sql, name, binds)
+ @connection.changes
+ end
+ alias :exec_update :exec_delete
+
+ def last_inserted_id(result)
+ @connection.last_insert_row_id
+ end
+
+ def execute(sql, name = nil) #:nodoc:
+ log(sql, name) { @connection.execute(sql) }
+ end
+
+ def update_sql(sql, name = nil) #:nodoc:
+ super
+ @connection.changes
+ end
+
+ def delete_sql(sql, name = nil) #:nodoc:
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
+ super sql, name
+ end
+
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
+ super
+ id_value || @connection.last_insert_row_id
+ end
+ alias :create :insert_sql
+
+ def select_rows(sql, name = nil)
+ exec_query(sql, name).rows
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def begin_db_transaction #:nodoc:
+ log('begin transaction',nil) { @connection.transaction }
+ end
+
+ def commit_db_transaction #:nodoc:
+ log('commit transaction',nil) { @connection.commit }
+ end
+
+ def rollback_db_transaction #:nodoc:
+ log('rollback transaction',nil) { @connection.rollback }
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
+ sql = <<-SQL
+ SELECT name
+ FROM sqlite_master
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
+ SQL
+ sql << " AND name = #{quote_table_name(table_name)}" if table_name
+
+ exec_query(sql, name).map do |row|
+ row['name']
+ end
+ end
+
+ def table_exists?(name)
+ name && tables('SCHEMA', name).any?
+ end
+
+ # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
+ def columns(table_name) #:nodoc:
+ table_structure(table_name).map do |field|
+ case field["dflt_value"]
+ when /^null$/i
+ field["dflt_value"] = nil
+ when /^'(.*)'$/
+ field["dflt_value"] = $1.gsub("''", "'")
+ when /^"(.*)"$/
+ field["dflt_value"] = $1.gsub('""', '"')
+ end
+
+ SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
+ end
+ end
+
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil) #:nodoc:
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
+ IndexDefinition.new(
+ table_name,
+ row['name'],
+ row['unique'] != 0,
+ exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
+ col['name']
+ })
+ end
+ end
+
+ def primary_key(table_name) #:nodoc:
+ column = table_structure(table_name).find { |field|
+ field['pk'] == 1
+ }
+ column && column['name']
+ end
+
+ def remove_index!(table_name, index_name) #:nodoc:
+ exec_query "DROP INDEX #{quote_column_name(index_name)}"
+ end
+
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
+ def rename_table(name, new_name)
+ exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
+ end
+
+ # See: http://www.sqlite.org/lang_altertable.html
+ # SQLite has an additional restriction on the ALTER TABLE statement
+ def valid_alter_table_options( type, options)
+ type.to_sym != :primary_key
+ end
+
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ if supports_add_column? && valid_alter_table_options( type, options )
+ super(table_name, column_name, type, options)
+ else
+ alter_table(table_name) do |definition|
+ definition.column(column_name, type, options)
+ end
+ end
+ end
+
+ def remove_column(table_name, *column_names) #:nodoc:
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
+ column_names.each do |column_name|
+ alter_table(table_name) do |definition|
+ definition.columns.delete(definition[column_name])
+ end
+ end
+ end
+ alias :remove_columns :remove_column
+
+ def change_column_default(table_name, column_name, default) #:nodoc:
+ alter_table(table_name) do |definition|
+ definition[column_name].default = default
+ end
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ unless null || default.nil?
+ exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+ alter_table(table_name) do |definition|
+ definition[column_name].null = null
+ end
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ alter_table(table_name) do |definition|
+ include_default = options_include_default?(options)
+ definition[column_name].instance_eval do
+ self.type = type
+ self.limit = options[:limit] if options.include?(:limit)
+ self.default = options[:default] if include_default
+ self.null = options[:null] if options.include?(:null)
+ self.precision = options[:precision] if options.include?(:precision)
+ self.scale = options[:scale] if options.include?(:scale)
+ end
+ end
+ end
+
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ unless columns(table_name).detect{|c| c.name == column_name.to_s }
+ raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
+ end
+ alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
+ end
+
+ def empty_insert_statement_value
+ "VALUES(NULL)"
+ end
+
+ protected
+ def select(sql, name = nil, binds = []) #:nodoc:
+ exec_query(sql, name, binds)
+ end
+
+ def table_structure(table_name)
+ 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
+
+ def alter_table(table_name, options = {}) #:nodoc:
+ altered_table_name = "altered_#{table_name}"
+ caller = lambda {|definition| yield definition if block_given?}
+
+ transaction do
+ move_table(table_name, altered_table_name,
+ options.merge(:temporary => true))
+ move_table(altered_table_name, table_name, &caller)
+ end
+ end
+
+ def move_table(from, to, options = {}, &block) #:nodoc:
+ copy_table(from, to, options, &block)
+ drop_table(from)
+ end
+
+ def copy_table(from, to, options = {}) #:nodoc:
+ from_primary_key = primary_key(from)
+ options[:primary_key] = from_primary_key if from_primary_key != 'id'
+ unless options[:primary_key]
+ options[:id] = columns(from).detect{|c| c.name == 'id'}.present? && from_primary_key == 'id'
+ end
+ create_table(to, options) do |definition|
+ @definition = definition
+ columns(from).each do |column|
+ column_name = options[:rename] ?
+ (options[:rename][column.name] ||
+ options[:rename][column.name.to_sym] ||
+ column.name) : column.name
+
+ @definition.column(column_name, column.type,
+ :limit => column.limit, :default => column.default,
+ :precision => column.precision, :scale => column.scale,
+ :null => column.null)
+ end
+ @definition.primary_key(from_primary_key) if from_primary_key
+ yield @definition if block_given?
+ end
+
+ copy_table_indexes(from, to, options[:rename] || {})
+ copy_table_contents(from, to,
+ @definition.columns.map {|column| column.name},
+ options[:rename] || {})
+ end
+
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
+ indexes(from).each do |index|
+ name = index.name
+ if to == "altered_#{from}"
+ name = "temp_#{name}"
+ elsif from == "altered_#{to}"
+ name = name[5..-1]
+ end
+
+ to_column_names = columns(to).map { |c| c.name }
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
+ to_column_names.include?(column)
+ end
+
+ unless columns.empty?
+ # index name can't be the same
+ opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
+ opts[:unique] = true if index.unique
+ add_index(to, columns, opts)
+ end
+ end
+ end
+
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
+ column_mappings = Hash[columns.map {|name| [name, name]}]
+ rename.each { |a| column_mappings[a.last] = a.first }
+ from_columns = columns(from).collect {|col| col.name}
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
+
+ quoted_to = quote_table_name(to)
+ exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
+ sql << ')'
+ exec_query sql
+ end
+ end
+
+ def sqlite_version
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
+ end
+
+ def default_primary_key_type
+ if supports_autoincrement?
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
+ else
+ 'INTEGER PRIMARY KEY NOT NULL'
+ end
+ end
+
+ def translate_exception(exception, message)
+ case exception.message
+ when /column(s)? .* (is|are) not unique/
+ RecordNotUnique.new(message, exception)
+ else
+ super
+ end
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
deleted file mode 100644
index 3d8dfab05c..0000000000
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ /dev/null
@@ -1,563 +0,0 @@
-require 'active_record/connection_adapters/abstract_adapter'
-require 'active_record/connection_adapters/statement_pool'
-require 'arel/visitors/bind_visitor'
-
-module ActiveRecord
- module ConnectionAdapters #:nodoc:
- class SQLiteColumn < Column #:nodoc:
- class << self
- def binary_to_string(value)
- if value.encoding != Encoding::ASCII_8BIT
- value = value.force_encoding(Encoding::ASCII_8BIT)
- end
- value
- end
- end
- end
-
- # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby
- # drivers (available both as gems and from http://rubyforge.org/projects/sqlite-ruby/).
- #
- # Options:
- #
- # * <tt>:database</tt> - Path to the database file.
- class SQLiteAdapter < AbstractAdapter
- class Version
- include Comparable
-
- def initialize(version_string)
- @version = version_string.split('.').map { |v| v.to_i }
- end
-
- def <=>(version_string)
- @version <=> version_string.split('.').map { |v| v.to_i }
- end
- end
-
- class StatementPool < ConnectionAdapters::StatementPool
- def initialize(connection, max)
- super
- @cache = Hash.new { |h,pid| h[pid] = {} }
- end
-
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
-
- def []=(sql, key)
- while @max <= cache.size
- dealloc(cache.shift.last[:stmt])
- end
- cache[sql] = key
- end
-
- def clear
- cache.values.each do |hash|
- dealloc hash[:stmt]
- end
- cache.clear
- end
-
- private
- def cache
- @cache[$$]
- end
-
- def dealloc(stmt)
- stmt.close unless stmt.closed?
- end
- end
-
- class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
- def initialize(connection, logger, config)
- super(connection, logger)
- @statements = StatementPool.new(@connection,
- config.fetch(:statement_limit) { 1000 })
- @config = config
-
- if config.fetch(:prepared_statements) { true }
- @visitor = Arel::Visitors::SQLite.new self
- else
- @visitor = BindSubstitution.new self
- end
- end
-
- def adapter_name #:nodoc:
- 'SQLite'
- end
-
- # Returns true if SQLite version is '2.0.0' or greater, false otherwise.
- def supports_ddl_transactions?
- sqlite_version >= '2.0.0'
- end
-
- # Returns true if SQLite version is '3.6.8' or greater, false otherwise.
- def supports_savepoints?
- sqlite_version >= '3.6.8'
- end
-
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations? #:nodoc:
- true
- end
-
- # Returns true.
- def supports_primary_key? #:nodoc:
- true
- end
-
- # Returns true.
- def supports_explain?
- true
- end
-
- def requires_reloading?
- true
- end
-
- # Returns true if SQLite version is '3.1.6' or greater, false otherwise.
- def supports_add_column?
- 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
-
- # Returns true if SQLite version is '3.2.6' or greater, false otherwise.
- def supports_count_distinct? #:nodoc:
- sqlite_version >= '3.2.6'
- end
-
- # Returns true if SQLite version is '3.1.0' or greater, false otherwise.
- def supports_autoincrement? #:nodoc:
- sqlite_version >= '3.1.0'
- end
-
- def supports_index_sort_order?
- sqlite_version >= '3.3.0'
- end
-
- def native_database_types #:nodoc:
- {
- :primary_key => default_primary_key_type,
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "integer" },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "boolean" }
- }
- end
-
-
- # QUOTING ==================================================
-
- def quote_string(s) #:nodoc:
- @connection.class.quote(s)
- end
-
- def quote_column_name(name) #:nodoc:
- %Q("#{name.to_s.gsub('"', '""')}")
- end
-
- # Quote date/time values for use in SQL input. Includes microseconds
- # if the value is a Time responding to usec.
- def quoted_date(value) #:nodoc:
- if value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
- end
- end
-
- def type_cast(value, column) # :nodoc:
- return value.to_f if BigDecimal === value
- return super unless String === value
- return super unless column && value
-
- value = super
- if column.type == :string && value.encoding == Encoding::ASCII_8BIT
- logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
- value.encode! 'utf-8'
- end
- value
- end
-
- # DATABASE STATEMENTS ======================================
-
- def explain(arel, binds = [])
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
- end
-
- class ExplainPrettyPrinter
- # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
- # the output of the SQLite shell:
- #
- # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
- # 0|1|1|SCAN TABLE posts (~100000 rows)
- #
- def pp(result) # :nodoc:
- result.rows.map do |row|
- row.join('|')
- end.join("\n") + "\n"
- end
- end
-
- def exec_query(sql, name = nil, binds = [])
- log(sql, name, binds) do
-
- # Don't cache statements without bind values
- if binds.empty?
- stmt = @connection.prepare(sql)
- cols = stmt.columns
- records = stmt.to_a
- stmt.close
- stmt = records
- else
- cache = @statements[sql] ||= {
- :stmt => @connection.prepare(sql)
- }
- stmt = cache[:stmt]
- cols = cache[:cols] ||= stmt.columns
- stmt.reset!
- stmt.bind_params binds.map { |col, val|
- type_cast(val, col)
- }
- end
-
- ActiveRecord::Result.new(cols, stmt.to_a)
- end
- end
-
- def exec_delete(sql, name = 'SQL', binds = [])
- exec_query(sql, name, binds)
- @connection.changes
- end
- alias :exec_update :exec_delete
-
- def last_inserted_id(result)
- @connection.last_insert_row_id
- end
-
- def execute(sql, name = nil) #:nodoc:
- log(sql, name) { @connection.execute(sql) }
- end
-
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.changes
- end
-
- def delete_sql(sql, name = nil) #:nodoc:
- sql += " WHERE 1=1" unless sql =~ /WHERE/i
- super sql, name
- end
-
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- super
- id_value || @connection.last_insert_row_id
- end
- alias :create :insert_sql
-
- def select_rows(sql, name = nil)
- exec_query(sql, name).rows
- end
-
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
- def begin_db_transaction #:nodoc:
- log('begin transaction',nil) { @connection.transaction }
- end
-
- def commit_db_transaction #:nodoc:
- log('commit transaction',nil) { @connection.commit }
- end
-
- def rollback_db_transaction #:nodoc:
- log('rollback transaction',nil) { @connection.rollback }
- end
-
- # SCHEMA STATEMENTS ========================================
-
- def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
- sql = <<-SQL
- SELECT name
- FROM sqlite_master
- WHERE type = 'table' AND NOT name = 'sqlite_sequence'
- SQL
- sql << " AND name = #{quote_table_name(table_name)}" if table_name
-
- exec_query(sql, name).map do |row|
- row['name']
- end
- end
-
- def table_exists?(name)
- name && tables('SCHEMA', name).any?
- end
-
- # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
- def columns(table_name) #:nodoc:
- table_structure(table_name).map do |field|
- case field["dflt_value"]
- when /^null$/i
- field["dflt_value"] = nil
- when /^'(.*)'$/
- field["dflt_value"] = $1.gsub(/''/, "'")
- when /^"(.*)"$/
- field["dflt_value"] = $1.gsub(/""/, '"')
- end
-
- SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
- end
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) #:nodoc:
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
- IndexDefinition.new(
- table_name,
- row['name'],
- row['unique'] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
- col['name']
- })
- end
- end
-
- def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find { |field|
- field['pk'] == 1
- }
- column && column['name']
- end
-
- def remove_index!(table_name, index_name) #:nodoc:
- exec_query "DROP INDEX #{quote_column_name(index_name)}"
- end
-
- # Renames a table.
- #
- # Example:
- # rename_table('octopuses', 'octopi')
- def rename_table(name, new_name)
- exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
- end
-
- # See: http://www.sqlite.org/lang_altertable.html
- # SQLite has an additional restriction on the ALTER TABLE statement
- def valid_alter_table_options( type, options)
- type.to_sym != :primary_key
- end
-
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
- if supports_add_column? && valid_alter_table_options( type, options )
- super(table_name, column_name, type, options)
- else
- alter_table(table_name) do |definition|
- definition.column(column_name, type, options)
- end
- end
- end
-
- def remove_column(table_name, *column_names) #:nodoc:
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
- column_names.flatten.each do |column_name|
- alter_table(table_name) do |definition|
- definition.columns.delete(definition[column_name])
- end
- end
- end
- alias :remove_columns :remove_column
-
- def change_column_default(table_name, column_name, default) #:nodoc:
- alter_table(table_name) do |definition|
- definition[column_name].default = default
- end
- end
-
- def change_column_null(table_name, column_name, null, default = nil)
- unless null || default.nil?
- exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
- end
- alter_table(table_name) do |definition|
- definition[column_name].null = null
- end
- end
-
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
- alter_table(table_name) do |definition|
- include_default = options_include_default?(options)
- definition[column_name].instance_eval do
- self.type = type
- self.limit = options[:limit] if options.include?(:limit)
- self.default = options[:default] if include_default
- self.null = options[:null] if options.include?(:null)
- self.precision = options[:precision] if options.include?(:precision)
- self.scale = options[:scale] if options.include?(:scale)
- end
- end
- end
-
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
- unless columns(table_name).detect{|c| c.name == column_name.to_s }
- raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
- end
- alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
- end
-
- def empty_insert_statement_value
- "VALUES(NULL)"
- end
-
- protected
- def select(sql, name = nil, binds = []) #:nodoc:
- exec_query(sql, name, binds)
- end
-
- def table_structure(table_name)
- 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
-
- def alter_table(table_name, options = {}) #:nodoc:
- altered_table_name = "altered_#{table_name}"
- caller = lambda {|definition| yield definition if block_given?}
-
- transaction do
- move_table(table_name, altered_table_name,
- options.merge(:temporary => true))
- move_table(altered_table_name, table_name, &caller)
- end
- end
-
- def move_table(from, to, options = {}, &block) #:nodoc:
- copy_table(from, to, options, &block)
- drop_table(from)
- end
-
- def copy_table(from, to, options = {}) #:nodoc:
- options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
- create_table(to, options) do |definition|
- @definition = definition
- columns(from).each do |column|
- column_name = options[:rename] ?
- (options[:rename][column.name] ||
- options[:rename][column.name.to_sym] ||
- column.name) : column.name
-
- @definition.column(column_name, column.type,
- :limit => column.limit, :default => column.default,
- :precision => column.precision, :scale => column.scale,
- :null => column.null)
- end
- @definition.primary_key(primary_key(from)) if primary_key(from)
- yield @definition if block_given?
- end
-
- copy_table_indexes(from, to, options[:rename] || {})
- copy_table_contents(from, to,
- @definition.columns.map {|column| column.name},
- options[:rename] || {})
- end
-
- def copy_table_indexes(from, to, rename = {}) #:nodoc:
- indexes(from).each do |index|
- name = index.name
- if to == "altered_#{from}"
- name = "temp_#{name}"
- elsif from == "altered_#{to}"
- name = name[5..-1]
- end
-
- to_column_names = columns(to).map { |c| c.name }
- columns = index.columns.map {|c| rename[c] || c }.select do |column|
- to_column_names.include?(column)
- end
-
- unless columns.empty?
- # index name can't be the same
- opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
- opts[:unique] = true if index.unique
- add_index(to, columns, opts)
- end
- end
- end
-
- def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
- column_mappings = Hash[columns.map {|name| [name, name]}]
- rename.each { |a| column_mappings[a.last] = a.first }
- from_columns = columns(from).collect {|col| col.name}
- columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
- quoted_columns = columns.map { |col| quote_column_name(col) } * ','
-
- quoted_to = quote_table_name(to)
- exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
- sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
- sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
- sql << ')'
- exec_query sql
- end
- end
-
- def sqlite_version
- @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
- end
-
- def default_primary_key_type
- if supports_autoincrement?
- 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
- else
- 'INTEGER PRIMARY KEY NOT NULL'
- end
- end
-
- def translate_exception(exception, message)
- case exception.message
- when /column(s)? .* (is|are) not unique/
- RecordNotUnique.new(message, exception)
- else
- super
- end
- end
-
- end
- end
-end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 76c424e8b4..b2ed606e5f 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,5 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/object/deep_dup'
require 'thread'
module ActiveRecord
@@ -76,7 +77,7 @@ module ActiveRecord
##
# :singleton-method:
- # Specifies wether or not has_many or has_one association option
+ # Specifies whether or not has_many or has_one association option
# :dependent => :restrict raises an exception. If set to true, the
# ActiveRecord::DeleteRestrictionError exception will be raised
# along with a DEPRECATION WARNING. If set to false, an error would
@@ -126,10 +127,16 @@ module ActiveRecord
object.is_a?(self)
end
+ # Returns an instance of <tt>Arel::Table</tt> loaded with the curent table name.
+ #
+ # class Post < ActiveRecord::Base
+ # scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
+ # end
def arel_table
@arel_table ||= Arel::Table.new(table_name, arel_engine)
end
+ # Returns the Arel engine.
def arel_engine
@arel_engine ||= connection_handler.retrieve_connection_pool(self) ? self : active_record_super.arel_engine
end
@@ -137,12 +144,12 @@ module ActiveRecord
private
def relation #:nodoc:
- @relation ||= Relation.new(self, arel_table)
+ relation = Relation.new(self, arel_table)
if finder_needs_type_condition?
- @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
+ relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
else
- @relation
+ relation
end
end
end
@@ -165,7 +172,7 @@ module ActiveRecord
# # Instantiates a single new object bypassing mass-assignment security
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
def initialize(attributes = nil, options = {})
- @attributes = self.class.initialize_attributes(self.class.column_defaults.dup)
+ @attributes = self.class.initialize_attributes(self.class.column_defaults.deep_dup)
@columns_hash = self.class.column_types.dup
init_internals
@@ -204,13 +211,34 @@ module ActiveRecord
self
end
+ ##
+ # :method: clone
+ # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
+ # That means that modifying attributes of the clone will modify the original, since they will both point to the
+ # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
+ #
+ # user = User.first
+ # new_user = user.clone
+ # user.name # => "Bob"
+ # new_user.name = "Joe"
+ # user.name # => "Joe"
+ #
+ # user.object_id == new_user.object_id # => false
+ # user.name.object_id == new_user.name.object_id # => true
+ #
+ # user.name.object_id == user.dup.name.object_id # => false
+
+ ##
+ # :method: dup
# Duped objects have no id assigned and are treated as new records. Note
# that this is a "shallow" copy as it copies the object's attributes
# only, not its associations. The extent of a "deep" copy is application
# specific and is therefore left to the application to implement according
# to its need.
# The dup method does not preserve the timestamps (created|updated)_(at|on).
- def initialize_dup(other)
+
+ ##
+ def initialize_dup(other) # :nodoc:
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
self.class.initialize_attributes(cloned_attributes)
@@ -351,7 +379,6 @@ module ActiveRecord
@attributes[pk] = nil unless @attributes.key?(pk)
- @relation = nil
@aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index f52979ebd9..b163ef3c12 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -69,7 +69,7 @@ module ActiveRecord
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
end
- update_all(updates.join(', '), primary_key => id)
+ where(primary_key => id).update_all updates.join(', ')
end
# Increment a number field by one, usually representing a count.
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
deleted file mode 100644
index 0473d6aafc..0000000000
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-module ActiveRecord
-
- # = Active Record Dynamic Finder Match
- #
- # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info
- #
- class DynamicFinderMatch
- def self.match(method)
- method = method.to_s
- klass = klasses.find do |_klass|
- _klass.matches?(method)
- end
- klass.new(method) if klass
- end
-
- def self.matches?(method)
- method =~ self::METHOD_PATTERN
- end
-
- def self.klasses
- [FindBy, FindByBang, FindOrInitializeCreateBy, FindOrCreateByBang]
- end
-
- def initialize(method)
- @finder = :first
- @instantiator = nil
- match_data = method.match(self.class::METHOD_PATTERN)
- @attribute_names = match_data[-1].split("_and_")
- initialize_from_match_data(match_data)
- end
-
- attr_reader :finder, :attribute_names, :instantiator
-
- def finder?
- @finder && !@instantiator
- end
-
- def creator?
- @finder == :first && @instantiator == :create
- end
-
- def instantiator?
- @instantiator
- end
-
- def bang?
- false
- end
-
- def valid_arguments?(arguments)
- arguments.size >= @attribute_names.size
- end
-
- def save_record?
- @instantiator == :create
- end
-
- def save_method
- bang? ? :save! : :save
- end
-
- private
-
- def initialize_from_match_data(match_data)
- end
- end
-
- class FindBy < DynamicFinderMatch
- METHOD_PATTERN = /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
-
- def initialize_from_match_data(match_data)
- @finder = :last if match_data[1] == 'last_'
- @finder = :all if match_data[1] == 'all_'
- end
- end
-
- class FindByBang < DynamicFinderMatch
- METHOD_PATTERN = /^find_by_([_a-zA-Z]\w*)\!$/
-
- def bang?
- true
- end
- end
-
- class FindOrInitializeCreateBy < DynamicFinderMatch
- METHOD_PATTERN = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
-
- def initialize_from_match_data(match_data)
- @instantiator = match_data[1] == 'initialize' ? :new : :create
- end
-
- def valid_arguments?(arguments)
- arguments.size == 1 && arguments.first.is_a?(Hash) || super
- end
- end
-
- class FindOrCreateByBang < DynamicFinderMatch
- METHOD_PATTERN = /^find_or_create_by_([_a-zA-Z]\w*)\!$/
-
- def initialize_from_match_data(match_data)
- @instantiator = :create
- end
-
- def bang?
- true
- end
- end
-end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e35b1c91a0..e278e62ce7 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,83 +1,130 @@
module ActiveRecord
- module DynamicMatchers
- def respond_to?(method_id, include_private = false)
- match = find_dynamic_match(method_id)
- valid_match = match && all_attributes_exists?(match.attribute_names)
+ module DynamicMatchers #:nodoc:
+ # This code in this file seems to have a lot of indirection, but the indirection
+ # is there to provide extension points for the active_record_deprecated_finders
+ # gem. When we stop supporting active_record_deprecated_finders (from Rails 5),
+ # then we can remove the indirection.
- valid_match || super
+ def respond_to?(name, include_private = false)
+ match = Method.match(self, name)
+ match && match.valid? || super
end
private
- # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
- # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
- # section at the top of this file for more detailed information.
- #
- # It's even possible to use all the additional parameters to +find+. For example, the
- # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
- #
- # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
- # is first invoked, so that future attempts to use it do not run through method_missing.
- def method_missing(method_id, *arguments, &block)
- if match = find_dynamic_match(method_id)
- attribute_names = match.attribute_names
- super unless all_attributes_exists?(attribute_names)
-
- unless match.valid_arguments?(arguments)
- method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
- backtrace = [method_trace] + caller
- raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
- end
+ def method_missing(name, *arguments, &block)
+ match = Method.match(self, name)
- if match.respond_to?(:scope?) && match.scope?
- define_scope_method(method_id, attribute_names)
- send(method_id, *arguments)
- elsif match.finder?
- options = arguments.extract_options!
- relation = options.any? ? scoped(options) : scoped
- relation.send :find_by_attributes, match, attribute_names, *arguments, &block
- elsif match.instantiator?
- scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
- end
+ if match && match.valid?
+ match.define
+ send(name, *arguments, &block)
else
super
end
end
- def define_scope_method(method_id, attribute_names) #:nodoc
- self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
- conditions = Hash[[:#{attribute_names.join(',:')}].zip(args)] # conditions = Hash[[:user_name, :password].zip(args)]
- where(conditions) # where(conditions)
- end # end
- METHOD
- end
+ class Method
+ @matchers = []
- def find_dynamic_match(method_id) #:nodoc:
- DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id)
- end
+ class << self
+ attr_reader :matchers
- # Similar in purpose to +expand_hash_conditions_for_aggregates+.
- def expand_attribute_names_for_aggregates(attribute_names)
- attribute_names.map do |attribute_name|
- if aggregation = reflect_on_aggregation(attribute_name.to_sym)
- aggregate_mapping(aggregation).map do |field_attr, _|
- field_attr.to_sym
- end
- else
- attribute_name.to_sym
+ def match(model, name)
+ klass = matchers.find { |k| name =~ k.pattern }
+ klass.new(model, name) if klass
+ end
+
+ def pattern
+ /^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/
+ end
+
+ def prefix
+ raise NotImplementedError
+ end
+
+ def suffix
+ ''
end
- end.flatten
+ end
+
+ attr_reader :model, :name, :attribute_names
+
+ def initialize(model, name)
+ @model = model
+ @name = name.to_s
+ @attribute_names = @name.match(self.class.pattern)[1].split('_and_')
+ end
+
+ def valid?
+ attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
+ end
+
+ def define
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def self.#{name}(#{signature})
+ #{body}
+ end
+ CODE
+ end
+
+ def body
+ raise NotImplementedError
+ end
end
- def all_attributes_exists?(attribute_names)
- (expand_attribute_names_for_aggregates(attribute_names) -
- column_methods_hash.keys).empty?
+ module Finder
+ # Extended in active_record_deprecated_finders
+ def body
+ result
+ end
+
+ # Extended in active_record_deprecated_finders
+ def result
+ "#{finder}(#{attributes_hash})"
+ end
+
+ # Extended in active_record_deprecated_finders
+ def signature
+ attribute_names.join(', ')
+ end
+
+ def attributes_hash
+ "{" + attribute_names.map { |name| ":#{name} => #{name}" }.join(',') + "}"
+ end
+
+ def finder
+ raise NotImplementedError
+ end
end
- def aggregate_mapping(reflection)
- mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
- mapping.first.is_a?(Array) ? mapping : [mapping]
+ class FindBy < Method
+ Method.matchers << self
+ include Finder
+
+ def self.prefix
+ "find_by"
+ end
+
+ def finder
+ "find_by"
+ end
+ end
+
+ class FindByBang < Method
+ Method.matchers << self
+ include Finder
+
+ def self.prefix
+ "find_by"
+ end
+
+ def self.suffix
+ "!"
+ end
+
+ def finder
+ "find_by!"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
deleted file mode 100644
index 6c043d29c4..0000000000
--- a/activerecord/lib/active_record/dynamic_scope_match.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module ActiveRecord
-
- # = Active Record Dynamic Scope Match
- #
- # Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
- # if, for example, the <tt>Product</tt> has an attribute with that name. You can
- # chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
- # scope except that it's dynamic.
- class DynamicScopeMatch
- METHOD_PATTERN = /^scoped_by_([_a-zA-Z]\w*)$/
-
- def self.match(method)
- if method.to_s =~ METHOD_PATTERN
- new(true, $1 && $1.split('_and_'))
- end
- end
-
- def initialize(scope, attribute_names)
- @scope = scope
- @attribute_names = attribute_names
- end
-
- attr_reader :scope, :attribute_names
- alias :scope? :scope
-
- def valid_arguments?(arguments)
- arguments.size >= @attribute_names.size
- end
- end
-end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 01cacf6153..313fdb3487 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -70,7 +70,7 @@ module ActiveRecord
# the threshold is set to 0.
#
# As the name of the method suggests this only applies to automatic
- # EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run.
+ # EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run.
def silence_auto_explain
current = Thread.current
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 9796b0a321..7e6512501c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -83,7 +83,7 @@ module ActiveRecord
# end
#
# test "find_alt_method_2" do
- # assert_equal "Ruby on Rails", @rubyonrails.news
+ # assert_equal "Ruby on Rails", @rubyonrails.name
# end
#
# In order to use these methods to access fixtured data within your testcases, you must specify one of the
@@ -372,19 +372,23 @@ module ActiveRecord
#
# Any fixture labeled "DEFAULTS" is safely ignored.
class Fixtures
+ #--
+ # NOTE: an instance of Fixtures can be called fixture_set, it is normally stored in a single YAML file and possibly in a folder with the same name.
+ #++
MAX_ID = 2 ** 30 - 1
@@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
- def self.default_fixture_model_name(fixture_name) # :nodoc:
+ def self.default_fixture_model_name(fixture_set_name) # :nodoc:
ActiveRecord::Base.pluralize_table_names ?
- fixture_name.singularize.camelize :
- fixture_name.camelize
+ fixture_set_name.singularize.camelize :
+ fixture_set_name.camelize
end
- def self.default_fixture_table_name(fixture_name) # :nodoc:
- "#{ActiveRecord::Base.table_name_prefix}"\
- "#{fixture_name.tr('/', '_')}#{ActiveRecord::Base.table_name_suffix}".to_sym
+ def self.default_fixture_table_name(fixture_set_name) # :nodoc:
+ "#{ ActiveRecord::Base.table_name_prefix }"\
+ "#{ fixture_set_name.tr('/', '_') }"\
+ "#{ ActiveRecord::Base.table_name_suffix }".to_sym
end
def self.reset_cache
@@ -411,11 +415,11 @@ module ActiveRecord
cache_for_connection(connection).update(fixtures_map)
end
- def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
+ def self.instantiate_fixtures(object, fixture_set, load_instances = true)
if load_instances
- fixtures.each do |name, fixture|
+ fixture_set.each do |fixture_name, fixture|
begin
- object.instance_variable_set "@#{name}", fixture.find
+ object.instance_variable_set "@#{fixture_name}", fixture.find
rescue FixtureClassNotFound
nil
end
@@ -424,61 +428,59 @@ module ActiveRecord
end
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each do |table_name, fixtures|
- ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ all_loaded_fixtures.each_value do |fixture_set|
+ instantiate_fixtures(object, fixture_set, load_instances)
end
end
cattr_accessor :all_loaded_fixtures
self.all_loaded_fixtures = {}
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = Array(table_names).map(&:to_s)
+ def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {})
+ fixture_set_names = Array(fixture_set_names).map(&:to_s)
class_names = class_names.stringify_keys
# FIXME: Apparently JK uses this.
connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name|
- fixture_is_cached?(connection, table_name)
+ files_to_read = fixture_set_names.reject { |fs_name|
+ fixture_is_cached?(connection, fs_name)
}
unless files_to_read.empty?
connection.disable_referential_integrity do
fixtures_map = {}
- fixture_files = files_to_read.map do |path|
- fixture_name = path
-
- fixtures_map[fixture_name] = new( # ActiveRecord::Fixtures.new
+ fixture_sets = files_to_read.map do |fs_name|
+ fixtures_map[fs_name] = new( # ActiveRecord::Fixtures.new
connection,
- fixture_name,
- class_names[fixture_name.to_s] || default_fixture_model_name(fixture_name),
- ::File.join(fixtures_directory, path))
+ fs_name,
+ class_names[fs_name] || default_fixture_model_name(fs_name),
+ ::File.join(fixtures_directory, fs_name))
end
all_loaded_fixtures.update(fixtures_map)
connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
+ fixture_sets.each do |fs|
+ conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
+ table_rows = fs.table_rows
table_rows.keys.each do |table|
conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
end
- table_rows.each do |table_name,rows|
+ table_rows.each do |fixture_set_name, rows|
rows.each do |row|
- conn.insert_fixture(row, table_name)
+ conn.insert_fixture(row, fixture_set_name)
end
end
end
# Cap primary key sequences to max(pk).
if connection.respond_to?(:reset_pk_sequence!)
- fixture_files.each do |ff|
- connection.reset_pk_sequence!(ff.table_name)
+ fixture_sets.each do |fs|
+ connection.reset_pk_sequence!(fs.table_name)
end
end
end
@@ -486,7 +488,7 @@ module ActiveRecord
cache_fixtures(connection, fixtures_map)
end
end
- cached_fixtures(connection, table_names)
+ cached_fixtures(connection, fixture_set_names)
end
# Returns a consistent, platform-independent identifier for +label+.
@@ -497,26 +499,23 @@ module ActiveRecord
attr_reader :table_name, :name, :fixtures, :model_class
- def initialize(connection, fixture_name, class_name, fixture_path)
- @connection = connection
- @fixture_path = fixture_path
- @name = fixture_name
- @class_name = class_name
-
- @fixtures = {}
+ def initialize(connection, name, class_name, path)
+ @fixtures = {} # Ordered hash
+ @name = name
+ @path = path
- # Should be an AR::Base type class
- if class_name.is_a?(Class)
+ if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@model_class = class_name
else
@model_class = class_name.constantize rescue nil
end
- @connection = model_class.connection if model_class && model_class.respond_to?(:connection)
+ @connection = ( model_class.respond_to?(:connection) ?
+ model_class.connection : connection )
@table_name = ( model_class.respond_to?(:table_name) ?
model_class.table_name :
- self.class.default_fixture_table_name(fixture_name) )
+ self.class.default_fixture_table_name(name) )
read_fixture_files
end
@@ -555,8 +554,8 @@ module ActiveRecord
if model_class && model_class < ActiveRecord::Model
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
if model_class.record_timestamps
- timestamp_column_names.each do |name|
- row[name] = now unless row.key?(name)
+ timestamp_column_names.each do |c_name|
+ row[c_name] = now unless row.key?(c_name)
end
end
@@ -634,26 +633,23 @@ module ActiveRecord
end
def read_fixture_files
- yaml_files = Dir["#{@fixture_path}/**/*.yml"].select { |f|
+ yaml_files = Dir["#{@path}/**/*.yml"].select { |f|
::File.file?(f)
} + [yaml_file_path]
yaml_files.each do |file|
Fixtures::File.open(file) do |fh|
- fh.each do |name, row|
- fixtures[name] = ActiveRecord::Fixture.new(row, model_class)
+ fh.each do |fixture_name, row|
+ fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
end
end
end
end
def yaml_file_path
- "#{@fixture_path}.yml"
+ "#{@path}.yml"
end
- def yaml_fixtures_key(path)
- ::File.basename(@fixture_path).split(".").first
- end
end
class Fixture #:nodoc:
@@ -708,7 +704,7 @@ module ActiveRecord
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_fixtures
- class_attribute :use_instantiated_fixtures # true, false, or :no_instances
+ class_attribute :use_instantiated_fixtures # true, false, or :no_instances
class_attribute :pre_loaded_fixtures
self.fixture_table_names = []
@@ -716,9 +712,8 @@ module ActiveRecord
self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
- self.fixture_class_names = Hash.new do |h, fixture_name|
- fixture_name = fixture_name.to_s
- h[fixture_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_name)
+ self.fixture_class_names = Hash.new do |h, fixture_set_name|
+ h[fixture_set_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_set_name)
end
end
@@ -742,18 +737,18 @@ module ActiveRecord
self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
end
- def fixtures(*fixture_names)
- if fixture_names.first == :all
- fixture_names = Dir["#{fixture_path}/**/*.yml"].map { |f|
+ def fixtures(*fixture_set_names)
+ if fixture_set_names.first == :all
+ fixture_set_names = Dir["#{fixture_path}/**/*.yml"].map { |f|
File.basename f, '.yml'
}
else
- fixture_names = fixture_names.flatten.map { |n| n.to_s }
+ fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
- self.fixture_table_names |= fixture_names
- require_fixture_classes(fixture_names)
- setup_fixture_accessors(fixture_names)
+ self.fixture_table_names |= fixture_set_names
+ require_fixture_classes(fixture_set_names)
+ setup_fixture_accessors(fixture_set_names)
end
def try_to_load_dependency(file_name)
@@ -768,33 +763,39 @@ module ActiveRecord
end
end
- def require_fixture_classes(fixture_names = nil)
- (fixture_names || fixture_table_names).each do |fixture_name|
- file_name = fixture_name.to_s
+ def require_fixture_classes(fixture_set_names = nil)
+ if fixture_set_names
+ fixture_set_names = fixture_set_names.map { |n| n.to_s }
+ else
+ fixture_set_names = fixture_table_names
+ end
+
+ fixture_set_names.each do |file_name|
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
try_to_load_dependency(file_name)
end
end
- def setup_fixture_accessors(fixture_names = nil)
- fixture_names = Array(fixture_names || fixture_table_names)
+ def setup_fixture_accessors(fixture_set_names = nil)
+ fixture_set_names = Array(fixture_set_names || fixture_table_names)
methods = Module.new do
- fixture_names.each do |fixture_name|
- fixture_name = fixture_name.to_s
- accessor_name = fixture_name.tr('/', '_').to_sym
+ fixture_set_names.each do |fs_name|
+ fs_name = fs_name.to_s
+ accessor_name = fs_name.tr('/', '_').to_sym
- define_method(accessor_name) do |*fixtures|
- force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
+ define_method(accessor_name) do |*fixture_names|
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
- @fixture_cache[fixture_name] ||= {}
+ @fixture_cache[fs_name] ||= {}
- instances = fixtures.map do |fixture|
- @fixture_cache[fixture_name].delete(fixture) if force_reload
+ instances = fixture_names.map do |f_name|
+ f_name = f_name.to_s
+ @fixture_cache[fs_name].delete(f_name) if force_reload
- if @loaded_fixtures[fixture_name][fixture.to_s]
- @fixture_cache[fixture_name][fixture] ||= @loaded_fixtures[fixture_name][fixture.to_s].find
+ if @loaded_fixtures[fs_name][f_name]
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
else
- raise StandardError, "No entry named '#{fixture}' found for fixture collection '#{fixture_name}'"
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
end
end
@@ -823,7 +824,7 @@ module ActiveRecord
end
def setup_fixtures
- return unless !ActiveRecord::Base.configurations.blank?
+ return if ActiveRecord::Base.configurations.blank?
if pre_loaded_fixtures && !use_transactional_fixtures
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
@@ -901,8 +902,8 @@ module ActiveRecord
ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
- @loaded_fixtures.each do |fixture_name, fixtures|
- ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
+ @loaded_fixtures.each_value do |fixture_set|
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_set, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 2c42f4cca5..23c272ef12 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -39,7 +39,7 @@ module ActiveRecord
when new_record?
"#{self.class.model_name.cache_key}/new"
when timestamp = self[:updated_at]
- timestamp = timestamp.utc.to_s(:number)
+ timestamp = timestamp.utc.to_s(:nsec)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
"#{self.class.model_name.cache_key}/#{id}"
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index c85d590ce1..7f38dda11e 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -172,8 +172,8 @@ module ActiveRecord
end
def reset_sequence_name #:nodoc:
- @sequence_name = connection.default_sequence_name(table_name, primary_key)
@explicit_sequence_name = false
+ @sequence_name = connection.default_sequence_name(table_name, primary_key)
end
# Sets the name of the sequence to use when generating ids to the given
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 32a1dae6bc..95a2ddcc11 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -19,10 +19,10 @@ module ActiveRecord
# = Active Record Nested Attributes
#
# Nested attributes allow you to save attributes on associated records
- # through the parent. By default nested attribute updating is turned off,
- # you can enable it using the accepts_nested_attributes_for class method.
- # When you enable nested attributes an attribute writer is defined on
- # the model.
+ # through the parent. By default nested attribute updating is turned off
+ # and you can enable it using the accepts_nested_attributes_for class
+ # method. When you enable nested attributes an attribute writer is
+ # defined on the model.
#
# The attribute writer is named after the association, which means that
# in the following example, two new methods are added to your model:
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 60c37ac2b7..c2d3eeb8ce 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -6,5 +6,58 @@ module ActiveRecord
def exec_queries
@records = []
end
+
+ def pluck(column_name)
+ []
+ end
+
+ def delete_all(conditions = nil)
+ 0
+ end
+
+ def update_all(updates, conditions = nil, options = {})
+ 0
+ end
+
+ def delete(id_or_array)
+ 0
+ end
+
+ def size
+ 0
+ end
+
+ def empty?
+ true
+ end
+
+ def any?
+ false
+ end
+
+ def many?
+ false
+ end
+
+ def to_sql
+ @to_sql ||= ""
+ end
+
+ def where_values_hash
+ {}
+ end
+
+ def count
+ 0
+ end
+
+ def calculate(operation, column_name, options = {})
+ nil
+ end
+
+ def exists?(id = false)
+ false
+ end
+
end
end \ No newline at end of file
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index bb504ae90f..a1bc39a32d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -123,6 +123,7 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
def destroy
+ raise ReadOnlyRecord if readonly?
destroy_associations
destroy_row if persisted?
@destroyed = true
@@ -178,7 +179,7 @@ module ActiveRecord
verify_readonly_attribute(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
+ self.class.where(self.class.primary_key => id).update_all(name => value) == 1
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -268,7 +269,13 @@ module ActiveRecord
clear_aggregation_cache
clear_association_cache
- fresh_object = self.class.unscoped { self.class.find(id, options) }
+ fresh_object =
+ if options && options[:lock]
+ self.class.unscoped { self.class.lock.find(id) }
+ else
+ self.class.unscoped { self.class.find(id) }
+ end
+
@attributes.update(fresh_object.instance_variable_get('@attributes'))
@columns_hash = fresh_object.instance_variable_get('@columns_hash')
@@ -313,7 +320,7 @@ module ActiveRecord
@changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
- self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1
+ self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 95565b503a..4d8283bcff 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -3,7 +3,7 @@ require 'active_support/deprecation'
module ActiveRecord
module Querying
- delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
delegate :find_by, :find_by!, :to => :scoped
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
@@ -11,7 +11,7 @@ module ActiveRecord
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :references, :none, :to => :scoped
- delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped
+ delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index ee3a6bf8c0..eb2769f1ef 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -68,7 +68,9 @@ module ActiveRecord
# and then establishes the connection.
initializer "active_record.initialize_database" do |app|
ActiveSupport.on_load(:active_record) do
- self.configurations = app.config.database_configuration
+ unless ENV['DATABASE_URL']
+ self.configurations = app.config.database_configuration
+ end
establish_connection
end
end
@@ -115,7 +117,7 @@ module ActiveRecord
if app.config.use_schema_cache_dump
filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
if File.file?(filename)
- cache = Marshal.load(open(filename, 'rb') { |f| f.read })
+ cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
ActiveRecord::Base.connection.schema_cache = cache
else
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index d4f4d593c6..c380b5c029 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -153,6 +153,10 @@ module ActiveRecord
# Holds all the meta-data about an aggregation as it was specified in the
# Active Record class.
class AggregateReflection < MacroReflection #:nodoc:
+ def mapping
+ mapping = options[:mapping] || [name, name]
+ mapping.first.is_a?(Array) ? mapping : [mapping]
+ end
end
# Holds all the meta-data about an association as it was specified in the
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index b125449127..779e052e3c 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -6,28 +6,30 @@ module ActiveRecord
# = Active Record Relation
class Relation
JoinOperation = Struct.new(:relation, :join_class, :on)
- ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
- MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind, :references]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
+
+ MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
+ :order, :joins, :where, :having, :bind, :references,
+ :extending]
+
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
+ :reverse_order, :uniq, :create_with]
+
+ VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded
- attr_accessor :extensions, :default_scoped
+ attr_accessor :default_scoped
alias :loaded? :loaded
alias :default_scoped? :default_scoped
- def initialize(klass, table)
- @klass, @table = klass, table
-
+ def initialize(klass, table, values = {})
+ @klass = klass
+ @table = table
+ @values = values
@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", [])}
- @extensions = []
- @create_with_value = {}
end
def insert(values)
@@ -78,7 +80,8 @@ module ActiveRecord
end
def initialize_copy(other)
- @bind_values = @bind_values.dup
+ @values = @values.dup
+ @values[:bind] = @values[:bind].dup if @values[:bind]
reset
end
@@ -168,17 +171,17 @@ module ActiveRecord
default_scoped = with_default_scope
if default_scoped.equal?(self)
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
- preload = @preload_values
- preload += @includes_values unless eager_loading?
+ preload = preload_values
+ preload += includes_values unless eager_loading?
preload.each do |associations|
ActiveRecord::Associations::Preloader.new(@records, associations).run
end
# @readonly_value is true only if set explicitly. @implicit_readonly is true if there
# are JOINS and no explicit SELECT.
- readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
+ readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
@records.each { |record| record.readonly! } if readonly
else
@records = default_scoped.to_a
@@ -218,7 +221,7 @@ module ActiveRecord
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
- @limit_value ? to_a.many? : size > 1
+ limit_value ? to_a.many? : size > 1
end
end
@@ -233,7 +236,10 @@ 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.with_scope(self, :overwrite) { yield }
+ previous, klass.current_scope = klass.current_scope, self
+ yield
+ ensure
+ klass.current_scope = previous
end
# Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -254,39 +260,26 @@ module ActiveRecord
# Customer.update_all :wants_email => true
#
# # Update all books with 'Rails' in their title
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
- #
- # # Update all avatars migrated more recently than a week ago
- # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
- #
- # # Update all books that match conditions, but limit it to 5 ordered by date
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
- #
- # # Conditions from the current relation also works
# Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')
#
- # # The same idea applies to limit and order
+ # # Update all books that match conditions, but limit it to 5 ordered by date
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
- def update_all(updates, conditions = nil, options = {})
- if conditions || options.present?
- where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
- else
- stmt = Arel::UpdateManager.new(arel.engine)
+ def update_all(updates)
+ stmt = Arel::UpdateManager.new(arel.engine)
- stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
- stmt.table(table)
- stmt.key = table[primary_key]
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
+ stmt.table(table)
+ stmt.key = table[primary_key]
- if joins_values.any?
- @klass.connection.join_to_update(stmt, arel)
- else
- stmt.take(arel.limit)
- stmt.order(*arel.orders)
- stmt.wheres = arel.constraints
- end
-
- @klass.connection.update stmt, 'SQL', bind_values
+ if joins_values.any?
+ @klass.connection.join_to_update(stmt, arel)
+ else
+ stmt.take(arel.limit)
+ stmt.order(*arel.orders)
+ stmt.wheres = arel.constraints
end
+
+ @klass.connection.update stmt, 'SQL', bind_values
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -397,11 +390,21 @@ 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)
+ raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
+
if conditions
where(conditions).delete_all
else
- statement = arel.compile_delete
- affected = @klass.connection.delete(statement, 'SQL', bind_values)
+ stmt = Arel::DeleteManager.new(arel.engine)
+ stmt.from(table)
+
+ if joins_values.any?
+ @klass.connection.join_to_delete(stmt, arel, table[primary_key])
+ else
+ stmt.wheres = arel.constraints
+ end
+
+ affected = @klass.connection.delete(stmt, 'SQL', bind_values)
reset
affected
@@ -446,7 +449,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
+ @to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
end
def where_values_hash
@@ -468,8 +471,8 @@ module ActiveRecord
def eager_loading?
@should_eager_load ||=
- @eager_load_values.any? ||
- @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
+ eager_load_values.any? ||
+ includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end
# Joins that are also marked for preloading. In which case we should just eager load them.
@@ -477,7 +480,7 @@ module ActiveRecord
# represent the same association, but that aren't matched by this. Also, we could have
# nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] }
def joined_includes_values
- @includes_values & @joins_values
+ includes_values & joins_values
end
def ==(other)
@@ -511,6 +514,10 @@ module ActiveRecord
to_a.blank?
end
+ def values
+ @values.dup
+ end
+
private
def references_eager_loaded_tables?
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 2fd89882ff..15f838a5ab 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -46,19 +46,14 @@ module ActiveRecord
# group.each { |person| person.party_all_night! }
# end
def find_in_batches(options = {})
+ options.assert_valid_keys(:start, :batch_size)
+
relation = self
unless arel.orders.blank? && arel.taken.blank?
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
- if (finder_options = options.except(:start, :batch_size)).present?
- raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
-
- relation = apply_finder_options(finder_options)
- end
-
start = options.delete(:start).to_i
batch_size = options.delete(:batch_size) || 1000
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index f613014f23..31d99f0192 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -3,56 +3,19 @@ require 'active_support/core_ext/object/try'
module ActiveRecord
module Calculations
- # Count operates using three different approaches.
+ # Count the records.
#
- # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
- # * Count using column: By passing a column name to count, it will return a count of all the
- # rows for the model with supplied column present.
- # * Count using options will find the row count matched by the options used.
+ # Person.count
+ # # => the total count of all people
#
- # The third approach, count using options, accepts an option hash as the only parameter. The options are:
+ # Person.count(:age)
+ # # => returns the total count of all people whose age is present in database
#
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
- # See conditions in the intro to ActiveRecord::Base.
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
- # (rarely needed) or named associations in the same form used for the <tt>:include</tt> option, which will
- # perform an INNER JOIN on the associated table(s). If the value is a string, then the records
- # will be returned read-only since they will have attributes that do not correspond to the table's columns.
- # Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
- # The symbols named refer to already defined associations. When using named associations, count
- # returns the number of DISTINCT items for the model you're counting.
- # See eager loading under Associations.
- # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
- # want to do a join but not include the joined columns.
- # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
- # SELECT COUNT(DISTINCT posts.id) ...
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
- # alternate table name (or even the name of a database view).
+ # Person.count(:all)
+ # # => performs a COUNT(*) (:all is an alias for '*')
#
- # Examples for counting all:
- # Person.count # returns the total count of all people
- #
- # Examples for counting by column:
- # Person.count(:age) # returns the total count of all people whose age is present in database
- #
- # Examples for count with options:
- # Person.count(:conditions => "age > 26")
- #
- # # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
- #
- # # finds the number of rows matching the conditions and joins.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000",
- # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
- #
- # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
- # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
- #
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
- # Use Person.count instead.
+ # Person.count(:age, distinct: true)
+ # # => counts the number of different age values
def count(column_name = nil, options = {})
column_name, options = nil, column_name if column_name.is_a?(Hash)
calculate(:count, column_name, options)
@@ -98,21 +61,22 @@ module ActiveRecord
end
# This calculates aggregate values in the given column. Methods for count, sum, average,
- # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
- # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
+ # minimum, and maximum have been added as shortcuts.
#
# There are two basic forms of output:
+ #
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
# for AVG, and the given column's type for everything else.
- # * Grouped values: This returns an ordered hash of the values and groups them by the
- # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
#
- # values = Person.maximum(:age, :group => 'last_name')
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
+ # takes either a column name, or the name of a belongs_to association.
+ #
+ # values = Person.group('last_name').maximum(:age)
# puts values["Drake"]
# => 43
#
# drake = Family.find_by_last_name('Drake')
- # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
# => 43
#
@@ -120,83 +84,94 @@ module ActiveRecord
# ...
# end
#
- # Options:
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
- # See conditions in the intro to ActiveRecord::Base.
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
- # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
- # (Rarely needed).
- # The records will be returned read-only since they will have attributes that do not correspond to the
- # table's columns.
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
- # want to do a join, but not include the joined columns.
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
- # SELECT COUNT(DISTINCT posts.id) ...
- #
# Examples:
# Person.calculate(:count, :all) # The same as Person.count
# Person.average(:age) # SELECT AVG(age) FROM people...
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
- # # everyone with a last name other than 'Drake'
#
# # Selects the minimum age for any family without any minors
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
- if options.except(:distinct).present?
- apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
- else
- relation = with_default_scope
-
- if relation.equal?(self)
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
- else
- perform_calculation(operation, column_name, options)
- end
+ relation = with_default_scope
+
+ if relation.equal?(self)
+ if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
- relation.calculate(operation, column_name, options)
+ perform_calculation(operation, column_name, options)
end
+ else
+ relation.calculate(operation, column_name, options)
end
rescue ThrowResult
0
end
- # This method is designed to perform select by a single column as direct SQL query
- # Returns <tt>Array</tt> with values of the specified column name
- # The values has same data type as column.
+ # Use <tt>pluck</tt> as a shortcut to select a single attribute without
+ # loading a bunch of records just to grab one attribute you want.
+ #
+ # Person.pluck(:name)
+ #
+ # instead of
+ #
+ # Person.all.map(&:name)
+ #
+ # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
+ # the plucked column name, if it can be deduced. Plucking a SQL fragment
+ # returns String values by default.
#
# Examples:
#
- # Person.pluck(:id) # SELECT people.id FROM people
- # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
- # Person.where(:age => 21).limit(5).pluck(:id) # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
+ # Person.pluck(:id)
+ # # SELECT people.id FROM people
+ # # => [1, 2, 3]
+ #
+ # Person.uniq.pluck(:role)
+ # # SELECT DISTINCT role FROM people
+ # # => ['admin', 'member', 'guest']
+ #
+ # Person.where(:age => 21).limit(5).pluck(:id)
+ # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
+ # # => [2, 3]
+ #
+ # Person.pluck('DATEDIFF(updated_at, created_at)')
+ # # SELECT DATEDIFF(updated_at, created_at) FROM people
+ # # => ['0', '27761', '173']
#
def pluck(column_name)
- key = column_name.to_s.split('.', 2).last
-
if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
column_name = "#{table_name}.#{column_name}"
end
result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
- types = result.column_types.merge klass.column_types
- column = types[key]
+
+ key = result.columns.first
+ column = klass.column_types.fetch(key) {
+ result.column_types.fetch(key) {
+ Class.new { def type_cast(v); v; end }.new
+ }
+ }
result.map do |attributes|
- value = klass.initialize_attributes(attributes)[key]
- if column
- column.type_cast value
- else
- value
- end
+ raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
+
+ value = klass.initialize_attributes(attributes).values.first
+
+ column.type_cast(value)
end
end
+ # Pluck all the ID's for the relation using the table's primary key
+ #
+ # Examples:
+ #
+ # Person.ids # SELECT people.id FROM people
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
+ def ids
+ pluck primary_key
+ end
+
private
def perform_calculation(operation, column_name, options = {})
@@ -216,7 +191,7 @@ module ActiveRecord
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
end
- if @group_values.any?
+ if group_values.any?
execute_grouped_calculation(operation, column_name, distinct)
else
execute_simple_calculation(operation, column_name, distinct)
@@ -259,7 +234,7 @@ module ActiveRecord
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
- group_attr = @group_values
+ group_attr = group_values
association = @klass.reflect_on_association(group_attr.first.to_sym)
associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attr)
@@ -282,7 +257,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
- select_values += @select_values unless @having_values.empty?
+ select_values += select_values unless having_values.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
"#{field} AS #{aliaz}"
@@ -347,8 +322,8 @@ module ActiveRecord
end
def select_for_count
- if @select_values.present?
- select = @select_values.join(", ")
+ if select_values.present?
+ select = select_values.join(", ")
select if select !~ /[,*]/
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 74f8e30404..4fedd33d64 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -3,83 +3,24 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActiveRecord
module FinderMethods
- # Find operates with four different retrieval approaches:
- #
- # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
- # * Find first - This will return the first record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
- # * Find last - This will return the last record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
- # * Find all - This will return all the records matched by the options used.
- # If no records are found, an empty array is returned. Use
- # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
- #
- # All approaches accept an options hash as their last parameter.
- #
- # ==== Options
- #
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>["user_name = ?", username]</tt>,
- # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
- # <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
- # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
- # it would skip rows 0 through 4.
- # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an
- # <tt>INNER JOIN</tt> on the associated table(s),
- # or an array containing a mixture of both strings and named associations.
- # If the value is a string, then the records will be returned read-only since they will
- # have attributes that do not correspond to the table's columns.
- # Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
- # to already defined associations. See eager loading under Associations.
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
- # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
- # to an alternate table name (or even the name of a database view).
- # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
- # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
- # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
+ # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
+ # is an integer, find by id coerces its arguments using +to_i+.
#
# ==== Examples
#
- # # find by id
# Person.find(1) # returns the object for ID = 1
+ # Person.find("1") # returns the object for ID = 1
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
# Person.find([1]) # returns an array for the object with ID = 1
# Person.where("administrator = 1").order("created_on DESC").find(1)
#
# Note that returned records may not be in the same order as the ids you
- # provide since database rows are unordered. Give an explicit <tt>:order</tt>
+ # provide since database rows are unordered. Give an explicit <tt>order</tt>
# to ensure the results are sorted.
#
- # ==== Examples
- #
- # # find first
- # Person.first # returns the first object fetched by SELECT * FROM people
- # Person.where(["user_name = ?", user_name]).first
- # Person.where(["user_name = :u", { :u => user_name }]).first
- # Person.order("created_on DESC").offset(5).first
- #
- # # find last
- # Person.last # returns the last object fetched by SELECT * FROM people
- # Person.where(["user_name = ?", user_name]).last
- # Person.order("created_on DESC").offset(5).last
- #
- # # find all
- # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
- # Person.where(["category IN (?)", categories]).limit(50).all
- # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
- # Person.offset(10).limit(10).all
- # Person.includes([:account, :friends]).all
- # Person.group("category").all
+ # ==== Find with lock
#
# Example for find with a lock: Imagine two concurrent transactions:
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
@@ -93,19 +34,10 @@ module ActiveRecord
# person.save!
# end
def find(*args)
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
-
- options = args.extract_options!
-
- if options.present?
- apply_finder_options(options).find(*args)
+ if block_given?
+ to_a.find { |*block_args| yield(*block_args) }
else
- case args.first
- when :first, :last, :all
- send(args.first)
- else
- find_with_ids(*args)
- end
+ find_with_ids(*args)
end
end
@@ -119,23 +51,49 @@ module ActiveRecord
# Post.find_by "published_at < ?", 2.weeks.ago
#
def find_by(*args)
- where(*args).first
+ where(*args).take
end
# Like <tt>find_by</tt>, except that if no record is found, raises
# an <tt>ActiveRecord::RecordNotFound</tt> error.
def find_by!(*args)
- where(*args).first!
+ where(*args).take!
+ end
+
+ # Gives a record (or N records if a parameter is supplied) without any implied
+ # order. The order will depend on the database implementation.
+ # If an order is supplied it will be respected.
+ #
+ # Examples:
+ #
+ # Person.take # returns an object fetched by SELECT * FROM people
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
+ # Person.where(["name LIKE '%?'", name]).take
+ def take(limit = nil)
+ limit ? limit(limit).to_a : find_take
+ end
+
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>take!</tt> accepts no arguments.
+ def take!
+ take or raise RecordNotFound
end
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:first)</tt>.
- def first(*args)
- if args.any?
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- limit(*args).to_a
+ # Find the first record (or first N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
+ # Examples:
+ #
+ # Person.first # returns the first object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).first
+ # Person.where(["user_name = :u", { :u => user_name }]).first
+ # Person.order("created_on DESC").offset(5).first
+ def first(limit = nil)
+ if limit
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc).limit(limit).to_a
else
- apply_finder_options(args.first).first
+ limit(limit).to_a
end
else
find_first
@@ -148,18 +106,20 @@ module ActiveRecord
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)
- if args.any?
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- if order_values.empty?
- order("#{primary_key} DESC").limit(*args).reverse
- else
- to_a.last(*args)
- end
+ # Find the last record (or last N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
+ # Examples:
+ #
+ # Person.last # returns the last object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).last
+ # Person.order("created_on DESC").offset(5).last
+ def last(limit = nil)
+ if limit
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].desc).limit(limit).reverse
else
- apply_finder_options(args.first).last
+ to_a.last(limit)
end
else
find_last
@@ -172,10 +132,16 @@ module ActiveRecord
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)
- args.any? ? apply_finder_options(args.first).to_a : to_a
+ # Examples:
+ #
+ # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
+ # Person.where(["category IN (?)", categories]).limit(50).all
+ # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
+ # Person.offset(10).limit(10).all
+ # Person.includes([:account, :friends]).all
+ # Person.group("category").all
+ def all
+ to_a
end
# Returns true if a record exists in the table that matches the +id+ or
@@ -204,9 +170,8 @@ module ActiveRecord
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
def exists?(id = false)
- return false if id.nil?
-
id = id.id if ActiveRecord::Model === id
+ return false if id.nil?
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
@@ -234,12 +199,12 @@ module ActiveRecord
end
def construct_join_dependency_for_association_find
- including = (@eager_load_values + @includes_values).uniq
+ 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
+ including = (eager_load_values + includes_values).uniq
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
relation = except(:includes, :eager_load, :preload)
apply_join_dependency(relation, join_dependency)
@@ -277,44 +242,6 @@ module ActiveRecord
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
- def find_by_attributes(match, attributes, *args)
- conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
- result = where(conditions).send(match.finder)
-
- if match.bang? && result.blank?
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
- else
- yield(result) if block_given?
- result
- end
- end
-
- def find_or_instantiator_by_attributes(match, attributes, *args)
- options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
- args.each_with_index do |arg, i|
- if arg.is_a?(Hash)
- protected_attributes_for_create = args[i].with_indifferent_access
- else
- unprotected_attributes_for_create[attributes[i]] = args[i]
- end
- end
-
- conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
-
- record = where(conditions).first
-
- unless record
- record = @klass.new(protected_attributes_for_create, options) do |r|
- r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
- end
- yield(record) if block_given?
- record.send(match.save_method) if match.save_record?
- end
-
- record
- end
-
def find_with_ids(*ids)
return to_a.find { |*block_args| yield(*block_args) } if block_given?
@@ -338,10 +265,10 @@ module ActiveRecord
id = id.id if ActiveRecord::Base === id
column = columns_hash[primary_key]
- substitute = connection.substitute_at(column, @bind_values.length)
+ substitute = connection.substitute_at(column, bind_values.length)
relation = where(table[primary_key].eq(substitute))
relation.bind_values += [[column, id]]
- record = relation.first
+ record = relation.take
unless record
conditions = arel.where_sql
@@ -356,15 +283,15 @@ module ActiveRecord
result = where(table[primary_key].in(ids)).all
expected_size =
- if @limit_value && ids.size > @limit_value
- @limit_value
+ if limit_value && ids.size > limit_value
+ limit_value
else
ids.size
end
# 11 ids with limit 3, offset 9 should give 2 results.
- if @offset_value && (ids.size - @offset_value < expected_size)
- expected_size = ids.size - @offset_value
+ if offset_value && (ids.size - offset_value < expected_size)
+ expected_size = ids.size - offset_value
end
if result.size == expected_size
@@ -379,11 +306,24 @@ module ActiveRecord
end
end
+ def find_take
+ if loaded?
+ @records.first
+ else
+ @take ||= limit(1).to_a.first
+ end
+ end
+
def find_first
if loaded?
@records.first
else
- @first ||= limit(1).to_a[0]
+ @first ||=
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc).limit(1).to_a.first
+ else
+ limit(1).to_a.first
+ end
end
end
@@ -395,7 +335,7 @@ module ActiveRecord
if offset_value || limit_value
to_a.last
else
- reverse_order.limit(1).to_a[0]
+ reverse_order.limit(1).to_a.first
end
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
new file mode 100644
index 0000000000..36f98c6480
--- /dev/null
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -0,0 +1,122 @@
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+
+module ActiveRecord
+ class Relation
+ class HashMerger
+ attr_reader :relation, :hash
+
+ def initialize(relation, hash)
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
+
+ @relation = relation
+ @hash = hash
+ end
+
+ def merge
+ Merger.new(relation, other).merge
+ end
+
+ # Applying values to a relation has some side effects. E.g.
+ # interpolation might take place for where values. So we should
+ # build a relation to merge in rather than directly merging
+ # the values.
+ def other
+ other = Relation.new(relation.klass, relation.table)
+ hash.each { |k, v| other.send("#{k}!", v) }
+ other
+ end
+ end
+
+ class Merger
+ attr_reader :relation, :values
+
+ def initialize(relation, other)
+ if other.default_scoped? && other.klass != relation.klass
+ other = other.with_default_scope
+ end
+
+ @relation = relation
+ @values = other.values
+ end
+
+ def normal_values
+ Relation::SINGLE_VALUE_METHODS +
+ Relation::MULTI_VALUE_METHODS -
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from]
+ end
+
+ def merge
+ normal_values.each do |name|
+ value = values[name]
+ relation.send("#{name}!", value) unless value.blank?
+ end
+
+ merge_multi_values
+ merge_single_values
+
+ relation
+ end
+
+ private
+
+ def merge_multi_values
+ relation.where_values = merged_wheres
+ relation.bind_values = merged_binds
+
+ if values[:reordering]
+ # override any order specified in the original relation
+ relation.reorder! values[:order]
+ elsif values[:order]
+ # merge in order_values from r
+ relation.order! values[:order]
+ end
+
+ relation.extend(*values[:extending]) unless values[:extending].blank?
+ end
+
+ def merge_single_values
+ relation.from_value = values[:from] unless relation.from_value
+ relation.lock_value = values[:lock] unless relation.lock_value
+ relation.reverse_order_value = values[:reverse_order]
+
+ unless values[:create_with].blank?
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
+ end
+ end
+
+ def merged_binds
+ if values[:bind]
+ (relation.bind_values + values[:bind]).uniq(&:first)
+ else
+ relation.bind_values
+ end
+ end
+
+ def merged_wheres
+ if values[:where]
+ merged_wheres = relation.where_values + values[:where]
+
+ unless relation.where_values.empty?
+ # Remove duplicates, last one wins.
+ seen = Hash.new { |h,table| h[table] = {} }
+ merged_wheres = merged_wheres.reverse.reject { |w|
+ nuke = false
+ if w.respond_to?(:operator) && w.operator == :==
+ name = w.left.name
+ table = w.left.relation.name
+ nuke = seen[table][name]
+ seen[table][name] = true
+ end
+ nuke
+ }.reverse
+ end
+
+ merged_wheres
+ else
+ relation.where_values
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index b40bf2b3cf..6a0cdd5917 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -34,9 +34,6 @@ module ActiveRecord
private
def self.build(attribute, value)
case value
- when ActiveRecord::Relation
- value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
- attribute.in(value.arel.ast)
when Array, ActiveRecord::Associations::CollectionProxy
values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x}
ranges, values = values.partition {|v| v.is_a?(Range)}
@@ -59,6 +56,9 @@ module ActiveRecord
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
+ when ActiveRecord::Relation
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
+ attribute.in(value.arel.ast)
when Range
attribute.in(value)
when ActiveRecord::Model
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index d737b34115..19fe8155d9 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,37 +5,67 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
- 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, :reordering_value, :reverse_order_value,
- :uniq_value, :references_values
+ Relation::MULTI_VALUE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
+ CODE
+ end
+
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_value # def readonly_value
+ @values[:#{name}] # @values[:readonly]
+ end # end
+ #
+ def #{name}_value=(value) # def readonly_value=(value)
+ @values[:#{name}] = value # @values[:readonly] = value
+ end # end
+ CODE
+ end
+
+ def create_with_value
+ @values[:create_with] || {}
+ end
+
+ def create_with_value=(value)
+ @values[:create_with] = value
+ end
+
+ alias extensions extending_values
def includes(*args)
- args.reject! {|a| a.blank? }
+ args.empty? ? self : spawn.includes!(*args)
+ end
- return self if args.empty?
+ def includes!(*args)
+ args.reject! {|a| a.blank? }
- relation = clone
- relation.includes_values = (relation.includes_values + args).flatten.uniq
- relation
+ self.includes_values = (includes_values + args).flatten.uniq
+ self
end
def eager_load(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.eager_load!(*args)
+ end
- relation = clone
- relation.eager_load_values += args
- relation
+ def eager_load!(*args)
+ self.eager_load_values += args
+ self
end
def preload(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.preload!(*args)
+ end
- relation = clone
- relation.preload_values += args
- relation
+ def preload!(*args)
+ self.preload_values += args
+ self
end
# Used to indicate that an association is referenced by an SQL string, and should
@@ -49,11 +79,12 @@ module ActiveRecord
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
# # => Query now knows the string references posts, so adds a JOIN
def references(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.references!(*args)
+ end
- relation = clone
- relation.references_values = (references_values + args.flatten.map(&:to_s)).uniq
- relation
+ def references!(*args)
+ self.references_values = (references_values + args.flatten.map(&:to_s)).uniq
+ self
end
# Works in two unique ways.
@@ -87,34 +118,40 @@ module ActiveRecord
# => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(value = Proc.new)
if block_given?
- to_a.select {|*block_args| value.call(*block_args) }
+ to_a.select { |*block_args| value.call(*block_args) }
else
- relation = clone
- relation.select_values += Array.wrap(value)
- relation
+ spawn.select!(value)
end
end
+ def select!(value)
+ self.select_values += Array.wrap(value)
+ self
+ end
+
def group(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.group!(*args)
+ end
- relation = clone
- relation.group_values += args.flatten
- relation
+ def group!(*args)
+ self.group_values += args.flatten
+ self
end
def order(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.order!(*args)
+ end
+ def order!(*args)
args = args.flatten
+
references = args.reject { |arg| Arel::Node === arg }
.map { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }
.compact
+ references!(references) if references.any?
- relation = clone
- relation = relation.references(references) if references.any?
- relation.order_values += args
- relation
+ self.order_values += args
+ self
end
# Replaces any existing order defined on the relation with the specified order.
@@ -128,72 +165,88 @@ module ActiveRecord
# generates a query with 'ORDER BY id ASC, name ASC'.
#
def reorder(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.reorder!(*args)
+ end
- relation = clone
- relation.reordering_value = true
- relation.order_values = args.flatten
- relation
+ def reorder!(*args)
+ self.reordering_value = true
+ self.order_values = args.flatten
+ self
end
def joins(*args)
- return self if args.compact.blank?
-
- relation = clone
+ args.compact.blank? ? self : spawn.joins!(*args)
+ end
+ def joins!(*args)
args.flatten!
- relation.joins_values += args
- relation
+ self.joins_values += args
+ self
end
def bind(value)
- relation = clone
- relation.bind_values += [value]
- relation
+ spawn.bind!(value)
+ end
+
+ def bind!(value)
+ self.bind_values += [value]
+ self
end
def where(opts, *rest)
- return self if opts.blank?
+ opts.blank? ? self : spawn.where!(opts, *rest)
+ end
- relation = clone
- relation = relation.references(PredicateBuilder.references(opts)) if Hash === opts
- relation.where_values += build_where(opts, rest)
- relation
+ def where!(opts, *rest)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
+
+ self.where_values += build_where(opts, rest)
+ self
end
def having(opts, *rest)
- return self if opts.blank?
+ opts.blank? ? self : spawn.having!(opts, *rest)
+ end
- relation = clone
- relation = relation.references(PredicateBuilder.references(opts)) if Hash === opts
- relation.having_values += build_where(opts, rest)
- relation
+ def having!(opts, *rest)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
+
+ self.having_values += build_where(opts, rest)
+ self
end
def limit(value)
- relation = clone
- relation.limit_value = value
- relation
+ spawn.limit!(value)
+ end
+
+ def limit!(value)
+ self.limit_value = value
+ self
end
def offset(value)
- relation = clone
- relation.offset_value = value
- relation
+ spawn.offset!(value)
+ end
+
+ def offset!(value)
+ self.offset_value = value
+ self
end
def lock(locks = true)
- relation = clone
+ spawn.lock!(locks)
+ end
+ def lock!(locks = true)
case locks
when String, TrueClass, NilClass
- relation.lock_value = locks || true
+ self.lock_value = locks || true
else
- relation.lock_value = false
+ self.lock_value = false
end
- relation
+ self
end
# Returns a chainable relation with zero records, specifically an
@@ -230,21 +283,43 @@ module ActiveRecord
end
def readonly(value = true)
- relation = clone
- relation.readonly_value = value
- relation
+ spawn.readonly!(value)
+ end
+
+ def readonly!(value = true)
+ self.readonly_value = value
+ self
end
def create_with(value)
- relation = clone
- relation.create_with_value = value ? create_with_value.merge(value) : {}
- relation
+ spawn.create_with!(value)
end
- def from(value)
- relation = clone
- relation.from_value = value
- relation
+ def create_with!(value)
+ self.create_with_value = value ? create_with_value.merge(value) : {}
+ self
+ end
+
+ # Specifies table from which the records will be fetched. For example:
+ #
+ # Topic.select('title').from('posts')
+ # #=> SELECT title FROM posts
+ #
+ # Can accept other relation objects. For example:
+ #
+ # Topic.select('title').from(Topics.approved)
+ # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
+ #
+ # Topics.select('a.title').from(Topics.approved, :a)
+ # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
+ #
+ def from(value, subquery_name = nil)
+ spawn.from!(value, subquery_name)
+ end
+
+ def from!(value, subquery_name = nil)
+ self.from_value = [value, subquery_name]
+ self
end
# Specifies whether the records should be unique or not. For example:
@@ -258,9 +333,12 @@ module ActiveRecord
# User.select(:name).uniq.uniq(false)
# # => You can also remove the uniqueness
def uniq(value = true)
- relation = clone
- relation.uniq_value = value
- relation
+ spawn.uniq!(value)
+ end
+
+ def uniq!(value = true)
+ self.uniq_value = value
+ self
end
# Used to extend a scope with additional methods, either through
@@ -299,20 +377,30 @@ module ActiveRecord
# # pagination code goes here
# end
# end
- def extending(*modules)
- modules << Module.new(&Proc.new) if block_given?
+ def extending(*modules, &block)
+ if modules.any? || block
+ spawn.extending!(*modules, &block)
+ else
+ self
+ end
+ end
- return self if modules.empty?
+ def extending!(*modules, &block)
+ modules << Module.new(&block) if block_given?
- relation = clone
- relation.send(:apply_modules, modules.flatten)
- relation
+ self.extending_values = modules.flatten
+ extend(*extending_values) if extending_values.any?
+
+ self
end
def reverse_order
- relation = clone
- relation.reverse_order_value = !relation.reverse_order_value
- relation
+ spawn.reverse_order!
+ end
+
+ def reverse_order!
+ self.reverse_order_value = !reverse_order_value
+ self
end
def arel
@@ -322,26 +410,26 @@ module ActiveRecord
def build_arel
arel = table.from table
- build_joins(arel, @joins_values) unless @joins_values.empty?
+ build_joins(arel, joins_values) unless joins_values.empty?
- collapse_wheres(arel, (@where_values - ['']).uniq)
+ collapse_wheres(arel, (where_values - ['']).uniq)
- arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
+ arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
- arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
- arel.skip(@offset_value.to_i) if @offset_value
+ arel.take(connection.sanitize_limit(limit_value)) if limit_value
+ arel.skip(offset_value.to_i) if offset_value
- arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
+ arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
- order = @order_values
- order = reverse_sql_order(order) if @reverse_order_value
+ order = order_values
+ order = reverse_sql_order(order) if reverse_order_value
arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
- build_select(arel, @select_values.uniq)
+ build_select(arel, select_values.uniq)
- arel.distinct(@uniq_value)
- arel.from(@from_value) if @from_value
- arel.lock(@lock_value) if @lock_value
+ arel.distinct(uniq_value)
+ arel.from(build_from) if from_value
+ arel.lock(lock_value) if lock_value
arel
end
@@ -389,6 +477,17 @@ module ActiveRecord
end
end
+ def build_from
+ opts, name = from_value
+ case opts
+ when Relation
+ name ||= 'subquery'
+ opts.arel.as(name.to_s)
+ else
+ opts
+ end
+ end
+
def build_joins(manager, joins)
buckets = joins.group_by do |join|
case join
@@ -443,13 +542,6 @@ module ActiveRecord
end
end
- def apply_modules(modules)
- unless modules.empty?
- @extensions += modules
- modules.each {|extension| extend(extension) }
- end
- end
-
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 03ba8c8628..80d087a9ea 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,81 +1,42 @@
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+require 'active_record/relation/merger'
module ActiveRecord
module SpawnMethods
- def merge(r)
- return self unless r
- return to_a & r if r.is_a?(Array)
- 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")
-
- unless value.empty?
- if method == :includes
- merged_relation = merged_relation.includes(value)
- else
- merged_relation.send(:"#{method}_values=", value)
- end
- end
- end
-
- (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order, :binds]).each do |method|
- value = r.send(:"#{method}_values")
- next if value.empty?
-
- value += merged_relation.send(:"#{method}_values")
- merged_relation.send :"#{method}_values=", value
- end
-
- merged_relation.joins_values += r.joins_values
-
- merged_wheres = @where_values + r.where_values
-
- merged_binds = (@bind_values + r.bind_values).uniq(&:first)
-
- unless @where_values.empty?
- # Remove duplicates, last one wins.
- seen = Hash.new { |h,table| h[table] = {} }
- merged_wheres = merged_wheres.reverse.reject { |w|
- nuke = false
- if w.respond_to?(:operator) && w.operator == :==
- name = w.left.name
- table = w.left.relation.name
- nuke = seen[table][name]
- seen[table][name] = true
- end
- nuke
- }.reverse
- end
-
- merged_relation.where_values = merged_wheres
- merged_relation.bind_values = merged_binds
-
- (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
- value = r.send(:"#{method}_value")
- merged_relation.send(:"#{method}_value=", value) unless value.nil?
- end
-
- merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
-
- merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
+ # This is overridden by Associations::CollectionProxy
+ def spawn #:nodoc:
+ clone
+ end
- if (r.reordering_value)
- # override any order specified in the original relation
- merged_relation.reordering_value = true
- merged_relation.order_values = r.order_values
+ # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
+ # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
+ #
+ # ==== Examples
+ #
+ # Post.where(:published => true).joins(:comments).merge( Comment.where(:spam => false) )
+ # # Performs a single join query with both where conditions.
+ #
+ # recent_posts = Post.order('created_at DESC').first(5)
+ # Post.where(:published => true).merge(recent_posts)
+ # # Returns the intersection of all published posts with the 5 most recently created posts.
+ # # (This is just an example. You'd probably want to do this with a single query!)
+ #
+ def merge(other)
+ if other.is_a?(Array)
+ to_a & other
+ elsif other
+ spawn.merge!(other)
else
- # merge in order_values from r
- merged_relation.order_values += r.order_values
+ self
end
+ end
- # Apply scope extension modules
- merged_relation.send :apply_modules, r.extensions
-
- merged_relation
+ def merge!(other)
+ klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
+ klass.new(self, other).merge
end
# Removes from the query the condition(s) specified in +skips+.
@@ -86,20 +47,9 @@ module ActiveRecord
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
#
def except(*skips)
- result = self.class.new(@klass, table)
+ result = Relation.new(klass, table, values.except(*skips))
result.default_scoped = default_scoped
-
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
- result.send(:"#{method}_values=", send(:"#{method}_values"))
- end
-
- (Relation::SINGLE_VALUE_METHODS - skips).each do |method|
- result.send(:"#{method}_value=", send(:"#{method}_value"))
- end
-
- # Apply scope extension modules
- result.send(:apply_modules, extensions)
-
+ result.extend(*extending_values) if extending_values.any?
result
end
@@ -111,44 +61,11 @@ module ActiveRecord
# Post.order('id asc').only(:where, :order) # uses the specified order
#
def only(*onlies)
- result = self.class.new(@klass, table)
+ result = Relation.new(klass, table, values.slice(*onlies))
result.default_scoped = default_scoped
-
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
- result.send(:"#{method}_values=", send(:"#{method}_values"))
- end
-
- (Relation::SINGLE_VALUE_METHODS & onlies).each do |method|
- result.send(:"#{method}_value=", send(:"#{method}_value"))
- end
-
- # Apply scope extension modules
- result.send(:apply_modules, extensions)
-
+ result.extend(*extending_values) if extending_values.any?
result
end
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend, :references,
- :order, :select, :readonly, :group, :having, :from, :lock ]
-
- def apply_finder_options(options)
- relation = clone
- return relation unless options
-
- options.assert_valid_keys(VALID_FIND_OPTIONS)
- finders = options.dup
- finders.delete_if { |key, value| value.nil? && key != :limit }
-
- ((VALID_FIND_OPTIONS - [:conditions, :include, :extend]) & finders.keys).each do |finder|
- relation = relation.send(finder, finders[finder])
- end
-
- relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
- relation = relation.includes(finders[:include]) if options.has_key?(:include)
- relation = relation.extending(finders[:extend]) if options.has_key?(:extend)
-
- relation
- end
-
end
end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index fb4b89b87b..fd276ccf5d 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -28,6 +28,7 @@ module ActiveRecord
alias :map! :map
alias :collect! :map
+ # Returns true if there are no records.
def empty?
rows.empty?
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 81b13fe529..5530be3219 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -58,7 +58,7 @@ module ActiveRecord
expanded_attrs = {}
attrs.each do |attr, value|
if aggregation = reflect_on_aggregation(attr.to_sym)
- mapping = aggregate_mapping(aggregation)
+ mapping = aggregation.mapping
mapping.each do |field_attr, aggregate_attr|
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
expanded_attrs[field_attr] = value
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index d815ab05ac..599e68379a 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -34,6 +34,15 @@ module ActiveRecord
ActiveRecord::Migrator.migrations_paths
end
+ def define(info, &block)
+ instance_eval(&block)
+
+ unless info[:version].blank?
+ initialize_schema_migrations_table
+ assume_migrated_upto_version(info[:version], migrations_paths)
+ end
+ end
+
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
@@ -46,13 +55,7 @@ module ActiveRecord
# ...
# end
def self.define(info={}, &block)
- schema = new
- schema.instance_eval(&block)
-
- unless info[:version].blank?
- initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], schema.migrations_paths)
- end
+ new.define(info, &block)
end
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 95fd33c1d1..7cbe2db408 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -55,7 +55,7 @@ module ActiveRecord
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
-# It's strongly recommended to check this file into your version control system.
+# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(#{define_params}) do
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index a8f5e96190..66a486ae0a 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -10,118 +10,6 @@ module ActiveRecord
end
module ClassMethods
- # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
- # <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
- # <tt>:create</tt> parameters are an attributes hash.
- #
- # class Article < ActiveRecord::Base
- # def self.create_with_scope
- # with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
- # find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
- # a = create(1)
- # a.blog_id # => 1
- # end
- # end
- # end
- #
- # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
- # <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
- #
- # <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
- # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
- # array of strings format for your joins.
- #
- # class Article < ActiveRecord::Base
- # def self.find_with_scope
- # with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do
- # with_scope(:find => limit(10)) do
- # all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
- # end
- # with_scope(:find => where(:author_id => 3)) do
- # all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
- # end
- # end
- # end
- # end
- #
- # You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
- #
- # class Article < ActiveRecord::Base
- # def self.find_with_exclusive_scope
- # with_scope(:find => where(:blog_id => 1).limit(1)) do
- # with_exclusive_scope(:find => limit(10)) do
- # all # => SELECT * from articles LIMIT 10
- # end
- # end
- # end
- # end
- #
- # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
- 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 scope.is_a?(Hash)
- # Dup first and second level of hash (method and params).
- scope = scope.dup
- scope.each do |method, params|
- scope[method] = params.dup unless params == true
- end
-
- scope.assert_valid_keys([ :find, :create ])
- relation = construct_finder_arel(scope[:find] || {})
- relation.default_scoped = true unless action == :overwrite
-
- if previous_scope && previous_scope.create_with_value && scope[:create]
- scope_for_create = if action == :merge
- previous_scope.create_with_value.merge(scope[:create])
- else
- scope[:create]
- end
-
- relation = relation.create_with(scope_for_create)
- else
- 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
-
- scope = relation
- end
-
- scope = previous_scope.merge(scope) if previous_scope && action == :merge
-
- self.current_scope = scope
- begin
- yield
- ensure
- self.current_scope = previous_scope
- end
- end
-
- protected
-
- # Works like with_scope, but discards any nested properties.
- def with_exclusive_scope(method_scoping = {}, &block)
- if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
- raise ArgumentError, <<-MSG
- New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
-
- User.unscoped.where(:active => true)
-
- Or call unscoped with a block:
-
- User.unscoped do
- User.where(:active => true).all
- end
-
- MSG
- end
- with_scope(method_scoping, :overwrite, &block)
- end
-
def current_scope #:nodoc:
Thread.current["#{self}_current_scope"]
end
@@ -129,15 +17,6 @@ module ActiveRecord
def current_scope=(scope) #:nodoc:
Thread.current["#{self}_current_scope"] = scope
end
-
- private
-
- def construct_finder_arel(options = {}, scope = nil)
- relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options
- relation = scope.merge(relation) if scope
- relation
- end
-
end
def populate_with_current_scope_attributes
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index b0609a8c08..db833fc7f1 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -31,7 +31,7 @@ module ActiveRecord
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
#
- # It is recommended to use the block form of unscoped because chaining
+ # It is recommended that you use the block form of unscoped because chaining
# unscoped with <tt>scope</tt> does not work. Assuming that
# <tt>published</tt> is a <tt>scope</tt>, the following two statements
# are equal: the default_scope is applied on both.
@@ -87,7 +87,7 @@ module ActiveRecord
# # Should return a scope, you can call 'super' here etc.
# end
# end
- def default_scope(scope = {})
+ def default_scope(scope = nil)
scope = Proc.new if block_given?
if scope.is_a?(Relation) || !scope.respond_to?(:call)
@@ -103,14 +103,13 @@ module ActiveRecord
end
def build_default_scope #:nodoc:
- if method(:default_scope).owner != ActiveRecord::Scoping::Default::ClassMethods
+ if !Base.is_a?(method(:default_scope).owner)
+ # The user has defined their own default scope method, so call that
evaluate_default_scope { default_scope }
elsif default_scopes.any?
evaluate_default_scope do
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)
+ if !scope.is_a?(Relation) && scope.respond_to?(:call)
default_scope.merge(unscoped { scope.call })
else
default_scope.merge(scope)
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 077e2d067e..2af476c1ba 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -29,17 +29,16 @@ module ActiveRecord
# You can define a \scope that applies to all finders using
# ActiveRecord::Base.default_scope.
def scoped(options = nil)
- if options
- scoped.apply_finder_options(options)
+ if current_scope
+ scope = current_scope.clone
else
- if current_scope
- current_scope.clone
- else
- scope = relation.clone
- scope.default_scoped = true
- scope
- end
+ scope = relation
+ scope.default_scoped = true
+ scope
end
+
+ scope.merge!(options) if options
+ scope
end
##
@@ -49,7 +48,7 @@ module ActiveRecord
if current_scope
current_scope.scope_for_create
else
- scope = relation.clone
+ scope = relation
scope.default_scoped = true
scope.scope_for_create
end
@@ -172,7 +171,7 @@ module ActiveRecord
# Article.published.featured.latest_article
# Article.featured.titles
- def scope(name, body = {}, &block)
+ def scope(name, body, &block)
extension = Module.new(&block) if block
# Check body.is_a?(Relation) to prevent the relation actually being
@@ -189,9 +188,7 @@ module ActiveRecord
end
singleton_class.send(:define_method, name) do |*args|
- options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body
- options = scoped.apply_finder_options(options) if options.is_a?(Hash)
-
+ options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body
relation = scoped.merge(options)
extension ? relation.extending(extension) : relation
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index ce43ae8066..5a256b040b 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -119,7 +119,7 @@ module ActiveRecord
class << self; remove_possible_method :find_by_session_id; end
def self.find_by_session_id(session_id)
- find :first, :conditions => {:session_id=>session_id}
+ where(session_id: session_id).first
end
end
end
@@ -201,10 +201,10 @@ module ActiveRecord
class << self
alias :data_column_name :data_column
-
+
# Use the ActiveRecord::Base.connection by default.
attr_writer :connection
-
+
# Use the ActiveRecord::Base.connection_pool by default.
attr_writer :connection_pool
@@ -218,12 +218,12 @@ module ActiveRecord
# Look up a session by id and unmarshal its data if found.
def find_by_session_id(session_id)
- if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
+ if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id.to_s)}")
new(:session_id => session_id, :marshaled_data => record['data'])
end
end
end
-
+
delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
attr_reader :session_id, :new_record
@@ -241,6 +241,11 @@ module ActiveRecord
@new_record = @marshaled_data.nil?
end
+ # Returns true if the record is persisted, i.e. it's not a new record
+ def persisted?
+ !@new_record
+ end
+
# Lazy-unmarshal session state.
def data
unless @data
@@ -287,7 +292,7 @@ module ActiveRecord
connect = connection
connect.delete <<-end_sql, 'Destroy session'
DELETE FROM #{table_name}
- WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id.to_s)}
end_sql
end
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 1c7b839e5e..ce2ea85ef9 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -10,15 +10,21 @@ module ActiveRecord
# Make sure that you declare the database column used for the serialized store as a text, so there's
# plenty of room.
#
+ # You can set custom coder to encode/decode your serialized attributes to/from different formats.
+ # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
+ #
+ # String keys should be used for direct access to virtual attributes because of most of the coders do not
+ # distinguish symbols and strings as keys.
+ #
# Examples:
#
# class User < ActiveRecord::Base
- # store :settings, accessors: [ :color, :homepage ]
+ # store :settings, accessors: [ :color, :homepage ], coder: JSON
# end
#
# u = User.new(color: 'black', homepage: '37signals.com')
- # u.color # Accessor stored attribute
- # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+ # u.color # Accessor stored attribute
+ # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor
#
# # Add additional accessors to an existing store through store_accessor
# class SuperUser < User
@@ -29,7 +35,7 @@ module ActiveRecord
module ClassMethods
def store(store_attribute, options = {})
- serialize store_attribute, Hash
+ serialize store_attribute, options.fetch(:coder, Hash)
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
end
@@ -37,13 +43,13 @@ module ActiveRecord
keys.flatten.each do |key|
define_method("#{key}=") do |value|
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key] = value
+ send(store_attribute)[key.to_s] = value
send("#{store_attribute}_will_change!")
end
define_method(key) do
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key]
+ send(store_attribute)[key.to_s]
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 743dfc5a38..30e1035300 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -290,7 +290,15 @@ module ActiveRecord
status = nil
self.class.transaction do
add_to_transaction
- status = yield
+ begin
+ status = yield
+ rescue ActiveRecord::Rollback
+ if defined?(@_start_transaction_state)
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ end
+ status = nil
+ end
+
raise ActiveRecord::Rollback unless status
end
status
@@ -302,12 +310,8 @@ module ActiveRecord
def remember_transaction_record_state #:nodoc:
@_start_transaction_state ||= {}
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
- unless @_start_transaction_state.include?(:new_record)
- @_start_transaction_state[:new_record] = @new_record
- end
- unless @_start_transaction_state.include?(:destroyed)
- @_start_transaction_state[:destroyed] = @destroyed
- end
+ @_start_transaction_state[:new_record] = @new_record
+ @_start_transaction_state[:destroyed] = @destroyed
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index db618f617f..9e4b588ac2 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -196,7 +196,6 @@ module ActiveRecord
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
- # * ActiveRecord::ConnectionAdapters::SQLiteAdapter
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
#
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index b9b5ec7956..b1a0f83b5f 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -12,10 +12,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
def up
<% attributes.each do |attribute| -%>
<%- if migration_action -%>
- <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %>
- <%- if attribute.has_index? && migration_action == 'add' -%>
- add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
- <%- end -%>
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %>
<%- end -%>
<%- end -%>
end
@@ -23,8 +20,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration
def down
<% attributes.reverse.each do |attribute| -%>
<%- if migration_action -%>
- <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %>
- <%- if attribute.has_index? && migration_action == 'remove' -%>
+ add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
+ <%- if attribute.has_index? -%>
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
<%- end -%>
<%- end -%>
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index f3bb70fb41..8e6ef20285 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -14,6 +14,7 @@ module ActiveRecord
def create_migration_file
return unless options[:migration] && options[:parent].nil?
+ attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
end
@@ -27,7 +28,7 @@ module ActiveRecord
end
def attributes_with_index
- attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) }
+ attributes.select { |a| !a.reference? && a.has_index? }
end
def accessible_attributes
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index fa2ba8d592..5e1c52c9ba 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -120,6 +120,19 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 5aa4743e7b..475a292f85 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -56,6 +56,36 @@ module ActiveRecord
end
end
+ def test_pk_and_sequence_for
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ end
+
+ def test_pk_and_sequence_for_with_non_standard_primary_key
+ @conn.exec_query('drop table if exists ex_with_non_standard_pk')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex_with_non_standard_pk` (
+ `code` INT(11) DEFAULT NULL auto_increment,
+ PRIMARY KEY (`code`))
+ eosql
+ pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk')
+ assert_equal 'code', pk
+ assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq
+ end
+
+ def test_pk_and_sequence_for_with_custom_index_type_pk
+ @conn.exec_query('drop table if exists ex_with_custom_index_type_pk')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex_with_custom_index_type_pk` (
+ `id` INT(11) DEFAULT NULL auto_increment,
+ PRIMARY KEY USING BTREE (`id`))
+ eosql
+ pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 292c7efebb..6faceaf7c0 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
end
def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ assert_nothing_raised { Select.scoped(:includes => [:groups]).all }
end
#the following functions were added to DRY test cases
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index 29f885c6e7..d94bb629a7 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
def test_schema
- assert @omgpost.find(:first)
+ assert @omgpost.first
end
def test_primary_key
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
index cd9c1041dc..5e8065d80d 100644
--- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -9,7 +9,7 @@ module ActiveRecord
def test_update_question_marks
str = "foo?bar"
- x = Topic.find :first
+ x = Topic.first
x.title = str
x.content = str
x.save!
@@ -28,7 +28,7 @@ module ActiveRecord
def test_update_null_bytes
str = "foo\0bar"
- x = Topic.find :first
+ x = Topic.first
x.title = str
x.content = str
x.save!
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 8e2b9ca9a5..684c7f5929 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -29,6 +29,22 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert @connection.active?
end
+ # TODO: Below is a straight up copy/paste from mysql/connection_test.rb
+ # I'm not sure what the correct way is to share these tests between
+ # adapters in minitest.
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 3a9744e78f..32d4282623 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
end
def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ assert_nothing_raised { Select.scoped(:includes => [:groups]).all }
end
#the following functions were added to DRY test cases
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index de4d9c2b33..2c0ed73c92 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
def test_schema
- assert @omgpost.find(:first)
+ assert @omgpost.first
end
def test_primary_key
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index ce08e4c6a7..34660577da 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -86,9 +86,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
def test_data_type_of_network_address_types
- assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type
- assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type
- assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type
+ assert_equal :cidr, @first_network_address.column_for_attribute(:cidr_address).type
+ assert_equal :inet, @first_network_address.column_for_attribute(:inet_address).type
+ assert_equal :macaddr, @first_network_address.column_for_attribute(:mac_address).type
end
def test_data_type_of_bit_string_types
@@ -134,9 +134,12 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '-1 years -2 days', @first_time.time_interval
end
- def test_network_address_values
- assert_equal '192.168.0.0/24', @first_network_address.cidr_address
- assert_equal '172.16.1.254', @first_network_address.inet_address
+ def test_network_address_values_ipaddr
+ cidr_address = IPAddr.new '192.168.0.0/24'
+ inet_address = IPAddr.new '172.16.1.254'
+
+ assert_equal cidr_address, @first_network_address.cidr_address
+ assert_equal inet_address, @first_network_address.inet_address
assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
end
@@ -200,8 +203,8 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
def test_update_network_address
- new_cidr_address = '10.1.2.3/32'
- new_inet_address = '10.0.0.0/8'
+ new_inet_address = '10.1.2.3/32'
+ new_cidr_address = '10.0.0.0/8'
new_mac_address = 'bc:de:f0:12:34:56'
assert @first_network_address.cidr_address = new_cidr_address
assert @first_network_address.inet_address = new_inet_address
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 1644a58d92..23bafde17b 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
require "cases/helper"
require 'active_record/base'
require 'active_record/connection_adapters/postgresql_adapter'
@@ -88,7 +90,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def test_rewrite
@connection.execute "insert into hstores (tags) VALUES ('1=>2')"
- x = Hstore.find :first
+ x = Hstore.first
x.tags = { '"a\'' => 'b' }
assert x.save!
end
@@ -96,13 +98,13 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def test_select
@connection.execute "insert into hstores (tags) VALUES ('1=>2')"
- x = Hstore.find :first
+ x = Hstore.first
assert_equal({'1' => '2'}, x.tags)
end
def test_select_multikey
@connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
- x = Hstore.find :first
+ x = Hstore.first
assert_equal({'1' => '2', '2' => '3'}, x.tags)
end
@@ -134,13 +136,19 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_cycle('a=>b' => 'bar', '1"foo' => '2')
end
+ def test_quoting_special_characters
+ assert_cycle('ca' => 'cà', 'ac' => 'àc')
+ end
+
private
def assert_cycle hash
+ # test creation
x = Hstore.create!(:tags => hash)
x.reload
assert_equal(hash, x.tags)
- # make sure updates work
+ # test updating
+ x = Hstore.create!(:tags => {})
x.tags = hash
x.save!
x.reload
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index a71d0bb848..92e31a3e44 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -49,6 +49,33 @@ module ActiveRecord
assert_equal expect, id
end
+ def test_insert_sql_with_returning_disabled
+ connection = connection_without_insert_returning
+ id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)")
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect, id
+ end
+
+ def test_exec_insert_with_returning_disabled
+ connection = connection_without_insert_returning
+ result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq')
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect, result.rows.first.first
+ end
+
+ def test_exec_insert_with_returning_disabled_and_no_sequence_name_given
+ connection = connection_without_insert_returning
+ result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id')
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect, result.rows.first.first
+ end
+
+ def test_sql_for_insert_with_returning_disabled
+ connection = connection_without_insert_returning
+ result = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
+ assert_equal ['sql', 'binds'], result
+ end
+
def test_serial_sequence
assert_equal 'public.accounts_id_seq',
@connection.serial_sequence('accounts', 'id')
@@ -204,6 +231,10 @@ module ActiveRecord
ctx.exec_insert(sql, 'SQL', binds)
end
+
+ def connection_without_insert_returning
+ ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 172055f15c..f8a605b67c 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -19,6 +19,18 @@ module ActiveRecord
assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 'f', @conn.type_cast(false, c)
end
+
+ def test_quote_float_nan
+ nan = 0.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'NaN'", @conn.quote(nan, c)
+ end
+
+ def test_quote_float_infinity
+ infinity = 1.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'Infinity'", @conn.quote(infinity, c)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 575b4806c1..7eef4ace81 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -57,6 +57,14 @@ class CopyTableTest < ActiveRecord::TestCase
end
end
+ def test_copy_table_with_unconventional_primary_key
+ test_copy_table('owners', 'owners_unconventional') do |from, to, options|
+ original_pk = @connection.primary_key('owners')
+ copied_pk = @connection.primary_key('owners_unconventional')
+ assert_equal original_pk, copied_pk
+ end
+ end
+
protected
def copy_table(from, to, options = {})
@connection.copy_table(from, to, {:temporary => true}.merge(options))
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 1dbeb66af6..2ba9143cd5 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -5,7 +5,7 @@ require 'securerandom'
module ActiveRecord
module ConnectionAdapters
- class SQLiteAdapter
+ class SQLite3Adapter
class QuotingTest < ActiveRecord::TestCase
def setup
@conn = Base.sqlite3_connection :database => ':memory:',
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 17bde6cb62..8a7f44d0a3 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -35,6 +35,11 @@ module ActiveRecord
assert(!result.rows.first.include?("blob"), "should not store blobs")
end
+ def test_time_column
+ owner = Owner.create!(:eats_at => Time.utc(1995,1,1,6,0))
+ assert_match(/1995-01-01/, owner.reload.eats_at.to_s)
+ end
+
def test_exec_insert
column = @conn.columns('items').find { |col| col.name == 'number' }
vals = [[column, 10]]
@@ -58,18 +63,6 @@ module ActiveRecord
end
end
- def test_connection_no_adapter
- assert_raises(ArgumentError) do
- Base.sqlite3_connection :database => ':memory:'
- end
- end
-
- def test_connection_wrong_adapter
- assert_raises(ArgumentError) do
- Base.sqlite3_connection :database => ':memory:',:adapter => 'vuvuzela'
- end
- end
-
def test_bad_timeout
assert_raises(TypeError) do
Base.sqlite3_connection :database => ':memory:',
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index ae272e2c4b..2f04c60a9a 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
module ActiveRecord::ConnectionAdapters
- class SQLiteAdapter
+ class SQLite3Adapter
class StatementPoolTest < ActiveRecord::TestCase
def test_cache_is_per_pid
return skip('must support fork') unless Process.respond_to?(:fork)
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 588adc38e3..b2eac0349b 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -37,6 +37,13 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
end
+
+ def test_schema_subclass
+ Class.new(ActiveRecord::Schema).define(:version => 9) do
+ create_table :fruits
+ end
+ assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
+ end
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 1160d236c9..2c7a240915 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -73,14 +73,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_eager_loading_with_primary_key
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
- citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key)
+ citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first
assert citibank_result.association_cache.key?(:firm_with_primary_key)
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
- citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key_symbols)
+ citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first
assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols)
end
@@ -168,6 +168,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable = Member.new :name => "Bert"
assert_equal Member, sponsor.association(:sponsorable).send(:klass)
+ assert_equal "members", sponsor.association(:sponsorable).aliased_table_name
end
def test_with_polymorphic_and_condition
@@ -181,7 +182,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_with_select
assert_equal Company.find(2).firm_with_select.attributes.size, 1
- assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1
+ assert_equal Company.scoped(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size, 1
end
def test_belongs_to_counter
@@ -333,7 +334,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_new_record_with_foreign_key_but_no_object
c = Client.new("firm_id" => 1)
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
+ assert_equal Firm.scoped(:order => "id").first, c.firm_with_basic_id
end
def test_setting_foreign_key_after_nil_target_loaded
@@ -393,9 +394,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_association_assignment_sticks
- post = Post.find(:first)
+ post = Post.first
- author1, author2 = Author.find(:all, :limit => 2)
+ author1, author2 = Author.scoped(:limit => 2).all
assert_not_nil author1
assert_not_nil author2
@@ -497,14 +498,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
Account.find(@account.id).save!
- Account.find(@account.id, :include => :firm).save!
+ Account.scoped(:includes => :firm).find(@account.id).save!
end
@account.firm.delete
assert_nothing_raised do
Account.find(@account.id).save!
- Account.find(@account.id, :include => :firm).save!
+ Account.scoped(:includes => :firm).find(@account.id).save!
end
end
@@ -517,7 +518,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
authors(:david).destroy
end
- assert_equal [], AuthorAddress.find_all_by_id([author_address.id, author_address_extra.id])
+ assert_equal [], AuthorAddress.where(id: [author_address.id, author_address_extra.id])
assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids
end
@@ -704,4 +705,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal toy, sponsor.reload.sponsorable
end
+
+ test "stale tracking doesn't care about the type" do
+ apple = Firm.create("name" => "Apple")
+ citibank = Account.create("credit_limit" => 10)
+
+ citibank.firm_id = apple.id
+ citibank.firm # load it
+
+ citibank.firm_id = apple.id.to_s
+
+ assert !citibank.association(:firm).stale_target?
+ end
+
+ def test_reflect_the_most_recent_change
+ author1, author2 = Author.limit(2)
+ post = Post.new(:title => "foo", :body=> "bar")
+
+ post.author = author1
+ post.author_id = author2.id
+
+ assert post.save
+ assert_equal post.author_id, author2.id
+ 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 6733f3e889..01f7f18397 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -16,7 +16,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
:categorizations, :people, :categories, :edges, :vertices
def test_eager_association_loading_with_cascaded_two_levels
- authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
+ authors = Author.scoped(:includes=>{:posts=>:comments}, :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
@@ -24,7 +24,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_cascaded_two_levels_and_one_level
- authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
+ authors = Author.scoped(:includes=>[{:posts=>:comments}, :categorizations], :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
@@ -84,7 +84,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
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")
+ authors = Author.scoped(:includes=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
@@ -92,7 +92,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
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")
+ authors = Author.scoped(:includes=>{:posts=>[:comments, :author]}, :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal authors(:david).name, authors[0].name
@@ -100,13 +100,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_cascaded_two_levels_with_condition
- authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id")
+ authors = Author.scoped(:includes=>{:posts=>:comments}, :where=>"authors.id=1", :order=>"authors.id").all
assert_equal 1, authors.size
assert_equal 5, authors[0].posts.size
end
def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
- firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id")
+ firms = Firm.scoped(:includes=>{:account=>{:firm=>:account}}, :order=>"companies.id").all
assert_equal 2, firms.size
assert_equal firms.first.account, firms.first.account.firm.account
assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
@@ -114,7 +114,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_has_many_sti
- topics = Topic.find(:all, :include => :replies, :order => 'topics.id')
+ topics = Topic.scoped(:includes => :replies, :order => 'topics.id').all
first, second, = topics(:first).replies.size, topics(:second).replies.size
assert_no_queries do
assert_equal first, topics[0].replies.size
@@ -127,7 +127,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
silly.parent_id = 1
assert silly.save
- topics = Topic.find(:all, :include => :replies, :order => ['topics.id', 'replies_topics.id'])
+ topics = Topic.scoped(:includes => :replies, :order => ['topics.id', 'replies_topics.id']).all
assert_no_queries do
assert_equal 2, topics[0].replies.size
assert_equal 0, topics[1].replies.size
@@ -135,14 +135,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_belongs_to_sti
- replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
+ replies = Reply.scoped(:includes => :topic, :order => 'topics.id').all
assert replies.include?(topics(:second))
assert !replies.include?(topics(:first))
assert_equal topics(:first), assert_no_queries { replies.first.topic }
end
def test_eager_association_loading_with_multiple_stis_and_order
- author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4')
+ author = Author.scoped(:includes => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :where => 'posts.id = 4').first
assert_equal authors(:david), author
assert_no_queries do
author.posts.first.special_comments
@@ -151,7 +151,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_of_stis_with_multiple_references
- authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
+ authors = Author.scoped(:includes => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :where => 'posts.id = 4').all
assert_equal [authors(:david)], authors
assert_no_queries do
authors.first.posts.first.special_comments.first.post.special_comments
@@ -160,7 +160,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_where_first_level_returns_nil
- authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC')
+ authors = Author.scoped(:includes => {:post_about_thinking => :comments}, :order => 'authors.id DESC').all
assert_equal [authors(:bob), authors(:mary), authors(:david)], authors
assert_no_queries do
authors[2].post_about_thinking.comments.first
@@ -168,12 +168,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
- source = Vertex.find(:first, :include=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id')
+ source = Vertex.scoped(:includes=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id').first
assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
- sink = Vertex.find(:first, :include=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC')
+ sink = Vertex.scoped(:includes=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC').first
assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
end
end
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
index 3044a8c312..75a6295350 100644
--- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -24,11 +24,11 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = false
- post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
+ post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff')
assert_nil post.tagging
ActiveRecord::Base.store_full_sti_class = true
- post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
+ post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff')
assert_instance_of Tagging, post.tagging
ensure
ActiveRecord::Base.store_full_sti_class = old
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 1e1958410c..bb0d6bc70b 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -92,8 +92,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
end
def test_include_query
- res = 0
- res = ShapeExpression.find :all, :include => [ :shape, { :paint => :non_poly } ]
+ res = ShapeExpression.scoped(:includes => [ :shape, { :paint => :non_poly } ]).all
assert_equal NUM_SHAPE_EXPRESSIONS, res.size
assert_queries(0) do
res.each do |se|
@@ -123,7 +122,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase
assert_nothing_raised do
# @davey_mcdave doesn't have any author_favorites
includes = {:posts => :comments, :categorizations => :category, :author_favorites => :favorite_author }
- Author.all :include => includes, :conditions => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name'
+ Author.scoped(:includes => includes, :where => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name').to_a
end
end
end
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index 07d0b24613..5805e71249 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -103,43 +103,43 @@ class EagerSingularizationTest < ActiveRecord::TestCase
def test_eager_no_extra_singularization_belongs_to
return unless @have_tables
assert_nothing_raised do
- Virus.find(:all, :include => :octopus)
+ Virus.scoped(:includes => :octopus).all
end
end
def test_eager_no_extra_singularization_has_one
return unless @have_tables
assert_nothing_raised do
- Octopus.find(:all, :include => :virus)
+ Octopus.scoped(:includes => :virus).all
end
end
def test_eager_no_extra_singularization_has_many
return unless @have_tables
assert_nothing_raised do
- Bus.find(:all, :include => :passes)
+ Bus.scoped(:includes => :passes).all
end
end
def test_eager_no_extra_singularization_has_and_belongs_to_many
return unless @have_tables
assert_nothing_raised do
- Crisis.find(:all, :include => :messes)
- Mess.find(:all, :include => :crises)
+ Crisis.scoped(:includes => :messes).all
+ Mess.scoped(:includes => :crises).all
end
end
def test_eager_no_extra_singularization_has_many_through_belongs_to
return unless @have_tables
assert_nothing_raised do
- Crisis.find(:all, :include => :successes)
+ Crisis.scoped(:includes => :successes).all
end
end
def test_eager_no_extra_singularization_has_many_through_has_many
return unless @have_tables
assert_nothing_raised do
- Crisis.find(:all, :include => :compresses)
+ Crisis.scoped(:includes => :compresses).all
end
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index efdb7cbb36..08467900f9 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -35,42 +35,42 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_one_through_join_model_with_conditions_on_the_through
- member = Member.find(members(:some_other_guy).id, :include => :favourite_club)
+ member = Member.scoped(:includes => :favourite_club).find(members(:some_other_guy).id)
assert_nil member.favourite_club
end
def test_loading_with_one_association
- posts = Post.find(:all, :include => :comments)
+ posts = Post.scoped(:includes => :comments).all
post = posts.find { |p| p.id == 1 }
assert_equal 2, post.comments.size
assert post.comments.include?(comments(:greetings))
- post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'")
+ post = Post.scoped(:includes => :comments, :where => "posts.title = 'Welcome to the weblog'").first
assert_equal 2, post.comments.size
assert post.comments.include?(comments(:greetings))
- posts = Post.find(:all, :include => :last_comment)
+ posts = Post.scoped(:includes => :last_comment).all
post = posts.find { |p| p.id == 1 }
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_with_one_association_with_non_preload
- posts = Post.find(:all, :include => :last_comment, :order => 'comments.id DESC')
+ posts = Post.scoped(:includes => :last_comment, :order => 'comments.id DESC').all
post = posts.find { |p| p.id == 1 }
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_conditions_with_or
- posts = authors(:david).posts.find(
- :all, :include => :comments, :references => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'"
- )
+ posts = authors(:david).posts.references(:comments).scoped(
+ :includes => :comments,
+ :where => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'"
+ ).all
assert_nil posts.detect { |p| p.author_id != authors(:david).id },
"expected to find only david's posts"
end
def test_with_ordering
- list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
+ list = Post.scoped(:includes => :comments, :order => "posts.id DESC").all
[: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|
@@ -84,14 +84,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_with_multiple_associations
- posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id")
+ posts = Post.scoped(:includes => [ :comments, :author, :categories ], :order => "posts.id").all
assert_equal 2, posts.first.comments.size
assert_equal 2, posts.first.categories.size
assert posts.first.comments.include?(comments(:greetings))
end
def test_duplicate_middle_objects
- comments = Comment.find :all, :conditions => 'post_id = 1', :include => [:post => :author]
+ comments = Comment.scoped(:where => 'post_id = 1', :includes => [:post => :author]).all
assert_no_queries do
comments.each {|comment| comment.post.author.name}
end
@@ -99,25 +99,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)
+ posts = Post.scoped(:includes=>:comments).all
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)
+ posts = Post.scoped(:includes=>:comments).all
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)
+ posts = Post.scoped(:includes=>:categories).all
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)
+ posts = Post.scoped(:includes=>:categories).all
assert_equal 11, posts.size
end
@@ -154,8 +154,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
popular_post.readers.create!(:person => people(:michael))
popular_post.readers.create!(:person => people(:david))
- readers = Reader.find(:all, :conditions => ["post_id = ?", popular_post.id],
- :include => {:post => :comments})
+ readers = Reader.scoped(:where => ["post_id = ?", popular_post.id],
+ :includes => {:post => :comments}).all
readers.each do |reader|
assert_equal [comment], reader.post.comments
end
@@ -167,8 +167,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
car_post.categories << categories(:technology)
comment = car_post.comments.create!(:body => "hmm")
- categories = Category.find(:all, :conditions => { 'posts.id' => car_post.id },
- :include => {:posts => :comments})
+ categories = Category.scoped(:where => { 'posts.id' => car_post.id },
+ :includes => {:posts => :comments}).all
categories.each do |category|
assert_equal [comment], category.posts[0].comments
end
@@ -186,10 +186,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once
author_id = authors(:david).id
- author = assert_queries(3) { Author.find(author_id, :include => {:posts_with_comments => :comments}) } # find the author, then find the posts, then find the comments
+ author = assert_queries(3) { Author.scoped(:includes => {:posts_with_comments => :comments}).find(author_id) } # find the author, then find the posts, then find the comments
author.posts_with_comments.each do |post_with_comments|
assert_equal post_with_comments.comments.length, post_with_comments.comments.count
- assert_nil post_with_comments.comments.uniq!
+ assert_nil post_with_comments.comments.to_a.uniq!
end
end
@@ -197,7 +197,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
author = authors(:david)
post = author.post_about_thinking_with_last_comment
last_comment = post.last_comment
- author = assert_queries(3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments
+ author = assert_queries(3) { Author.scoped(:includes => {:post_about_thinking_with_last_comment => :last_comment}).find(author.id)} # find the author, then find the posts, then find the comments
assert_no_queries do
assert_equal post, author.post_about_thinking_with_last_comment
assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment
@@ -208,7 +208,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
post = posts(:welcome)
author = post.author
author_address = author.author_address
- post = assert_queries(3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address
+ post = assert_queries(3) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author, then find the address
assert_no_queries do
assert_equal author, post.author_with_address
assert_equal author_address, post.author_with_address.author_address
@@ -218,7 +218,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
post = posts(:welcome)
post.update_attributes!(:author => nil)
- post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address
+ post = assert_queries(1) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address
assert_no_queries do
assert_equal nil, post.author_with_address
end
@@ -227,85 +227,85 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_polymorphic_association
sponsor = sponsors(:moustache_club_sponsor_for_groucho)
sponsor.update_attributes!(:sponsorable => nil)
- sponsor = assert_queries(1) { Sponsor.find(sponsor.id, :include => :sponsorable) }
+ sponsor = assert_queries(1) { Sponsor.scoped(:includes => :sponsorable).find(sponsor.id) }
assert_no_queries do
assert_equal nil, sponsor.sponsorable
end
end
def test_loading_from_an_association
- posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id")
+ posts = authors(:david).posts.scoped(:includes => :comments, :order => "posts.id").all
assert_equal 2, posts.first.comments.size
end
def test_loading_from_an_association_that_has_a_hash_of_conditions
assert_nothing_raised do
- Author.find(:all, :include => :hello_posts_with_hash_conditions)
+ Author.scoped(:includes => :hello_posts_with_hash_conditions).all
end
- assert !Author.find(authors(:david).id, :include => :hello_posts_with_hash_conditions).hello_posts.empty?
+ assert !Author.scoped(:includes => :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty?
end
def test_loading_with_no_associations
- assert_nil Post.find(posts(:authorless).id, :include => :author).author
+ assert_nil Post.scoped(:includes => :author).find(posts(:authorless).id).author
end
def test_nested_loading_with_no_associations
assert_nothing_raised do
- Post.find(posts(:authorless).id, :include => {:author => :author_addresss})
+ Post.scoped(:includes => {:author => :author_addresss}).find(posts(:authorless).id)
end
end
def test_nested_loading_through_has_one_association
- aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts})
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}).find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_order
- aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'author_addresses.id')
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'author_addresses.id').find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_order_on_association
- aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'authors.id')
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'authors.id').find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_order_on_nested_association
- aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'posts.id')
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'posts.id').find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions
- aa = AuthorAddress.find(
- author_addresses(:david_address).id, :include => {:author => :posts},
- :conditions => "author_addresses.id > 0", :references => :author_addresses
- )
+ aa = AuthorAddress.references(:author_addresses).scoped(
+ :includes => {:author => :posts},
+ :where => "author_addresses.id > 0"
+ ).find author_addresses(:david_address).id
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions_on_association
- aa = AuthorAddress.find(
- author_addresses(:david_address).id, :include => {:author => :posts},
- :conditions => "authors.id > 0", :references => :authors
- )
+ aa = AuthorAddress.references(:authors).scoped(
+ :includes => {:author => :posts},
+ :where => "authors.id > 0"
+ ).find author_addresses(:david_address).id
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions_on_nested_association
- aa = AuthorAddress.find(
- author_addresses(:david_address).id, :include => {:author => :posts},
- :conditions => "posts.id > 0", :references => :posts
- )
+ aa = AuthorAddress.references(:posts).scoped(
+ :includes => {:author => :posts},
+ :where => "posts.id > 0"
+ ).find author_addresses(:david_address).id
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_eager_association_loading_with_belongs_to_and_foreign_keys
- pets = Pet.find(:all, :include => :owner)
+ pets = Pet.scoped(:includes => :owner).all
assert_equal 3, pets.length
end
def test_eager_association_loading_with_belongs_to
- comments = Comment.find(:all, :include => :post)
+ comments = Comment.scoped(:includes => :post).all
assert_equal 11, comments.length
titles = comments.map { |c| c.post.title }
assert titles.include?(posts(:welcome).title)
@@ -313,31 +313,31 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_belongs_to_and_limit
- comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :limit => 5, :order => 'comments.id').all
assert_equal 5, comments.length
assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [5,6,7], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset
- comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [3,5,6], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [6,7,8], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
- comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [6,7,8], comments.collect { |c| c.id }
end
@@ -345,7 +345,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name
assert_nothing_raised do
ActiveSupport::Deprecation.silence do
- Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4])
+ Comment.scoped(:includes => :post, :where => ['posts.id = ?',4]).all
end
end
end
@@ -353,7 +353,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_conditions_hash
comments = []
assert_nothing_raised do
- comments = Comment.find(:all, :include => :post, :conditions => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').all
end
assert_equal 3, comments.length
assert_equal [5,6,7], comments.collect { |c| c.id }
@@ -366,14 +366,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
ActiveSupport::Deprecation.silence do
- Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4])
+ Comment.scoped(:includes => :post, :where => ["#{quoted_posts_id} = ?",4]).all
end
end
end
def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name
assert_nothing_raised do
- Comment.find(:all, :include => :post, :order => 'posts.id')
+ Comment.scoped(:includes => :post, :order => 'posts.id').all
end
end
@@ -381,55 +381,55 @@ class EagerAssociationTest < ActiveRecord::TestCase
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
ActiveSupport::Deprecation.silence do
- Comment.find(:all, :include => :post, :order => quoted_posts_id)
+ Comment.scoped(:includes => :post, :order => quoted_posts_id).all
end
end
end
def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
- posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id')
+ posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').all
assert_equal 1, posts.length
assert_equal [1], posts.collect { |p| p.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
- posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id')
+ posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').all
assert_equal 1, posts.length
assert_equal [2], posts.collect { |p| p.id }
end
def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name
- author_favorite = AuthorFavorite.find(:first, :include => :favorite_author)
+ author_favorite = AuthorFavorite.scoped(:includes => :favorite_author).first
assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author }
end
def test_eager_load_belongs_to_quotes_table_and_column_names
- job = Job.find jobs(:unicyclist).id, :include => :ideal_reference
+ job = Job.includes(:ideal_reference).find jobs(:unicyclist).id
references(:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference}
end
def test_eager_load_has_one_quotes_table_and_column_names
- michael = Person.find(people(:michael), :include => :favourite_reference)
+ michael = Person.scoped(:includes => :favourite_reference).find(people(:michael))
references(:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference}
end
def test_eager_load_has_many_quotes_table_and_column_names
- michael = Person.find(people(:michael), :include => :references)
+ michael = Person.scoped(:includes => :references).find(people(:michael))
references(:michael_magician,:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) }
end
def test_eager_load_has_many_through_quotes_table_and_column_names
- michael = Person.find(people(:michael), :include => :jobs)
+ michael = Person.scoped(:includes => :jobs).find(people(:michael))
jobs(:magician, :unicyclist)
assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) }
end
def test_eager_load_has_many_with_string_keys
subscriptions = subscriptions(:webster_awdr, :webster_rfr)
- subscriber =Subscriber.find(subscribers(:second).id, :include => :subscriptions)
+ subscriber =Subscriber.scoped(:includes => :subscriptions).find(subscribers(:second).id)
assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id)
end
@@ -447,25 +447,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_load_has_many_through_with_string_keys
books = books(:awdr, :rfr)
- subscriber = Subscriber.find(subscribers(:second).id, :include => :books)
+ subscriber = Subscriber.scoped(:includes => :books).find(subscribers(:second).id)
assert_equal books, subscriber.books.sort_by(&:id)
end
def test_eager_load_belongs_to_with_string_keys
subscriber = subscribers(:second)
- subscription = Subscription.find(subscriptions(:webster_awdr).id, :include => :subscriber)
+ subscription = Subscription.scoped(:includes => :subscriber).find(subscriptions(:webster_awdr).id)
assert_equal subscriber, subscription.subscriber
end
def test_eager_association_loading_with_explicit_join
- posts = Post.find(:all, :include => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id')
+ posts = Post.scoped(:includes => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id').all
assert_equal 1, posts.length
end
def test_eager_with_has_many_through
- posts_with_comments = people(:michael).posts.find(:all, :include => :comments, :order => 'posts.id')
- posts_with_author = people(:michael).posts.find(:all, :include => :author, :order => 'posts.id')
- posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ], :order => 'posts.id')
+ posts_with_comments = people(:michael).posts.scoped(:includes => :comments, :order => 'posts.id').all
+ posts_with_author = people(:michael).posts.scoped(:includes => :author, :order => 'posts.id').all
+ posts_with_comments_and_author = people(:michael).posts.scoped(:includes => [ :comments, :author ], :order => 'posts.id').all
assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
@@ -476,32 +476,32 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.create!(:author => author, :title => "TITLE", :body => "BODY")
author.author_favorites.create(:favorite_author_id => 1)
author.author_favorites.create(:favorite_author_id => 2)
- posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites)
+ posts_with_author_favorites = author.posts.scoped(:includes => :author_favorites).all
assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id }
end
def test_eager_with_has_many_through_an_sti_join_model
- author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id')
+ author = Author.scoped(:includes => :special_post_comments, :order => 'authors.id').first
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
end
def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
- author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id')
+ author = Author.scoped(:includes => :special_nonexistant_post_comments, :order => 'authors.id').first
assert_equal [], author.special_nonexistant_post_comments
end
def test_eager_with_has_many_through_join_model_with_conditions
- assert_equal Author.find(:first, :include => :hello_post_comments,
- :order => 'authors.id').hello_post_comments.sort_by(&:id),
- Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id)
+ assert_equal Author.scoped(:includes => :hello_post_comments,
+ :order => 'authors.id').first.hello_post_comments.sort_by(&:id),
+ Author.scoped(:order => 'authors.id').first.hello_post_comments.sort_by(&:id)
end
def test_eager_with_has_many_through_join_model_with_conditions_on_top_level
- assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first
+ assert_equal comments(:more_greetings), Author.scoped(:includes => :comments_with_order_and_conditions).find(authors(:david).id).comments_with_order_and_conditions.first
end
def test_eager_with_has_many_through_join_model_with_include
- author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a
+ author_comments = Author.scoped(:includes => :comments_with_include).find(authors(:david).id).comments_with_include.to_a
assert_no_queries do
author_comments.first.post.title
end
@@ -509,7 +509,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_through_with_conditions_join_model_with_include
post_tags = Post.find(posts(:welcome).id).misc_tags
- eager_post_tags = Post.find(1, :include => :misc_tags).misc_tags
+ eager_post_tags = Post.scoped(:includes => :misc_tags).find(1).misc_tags
assert_equal post_tags, eager_post_tags
end
@@ -520,16 +520,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit
- posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
+ posts = Post.scoped(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).all
assert_equal 2, posts.size
assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
end
def test_eager_with_has_many_and_limit_and_conditions
if current_adapter?(:OpenBaseAdapter)
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id").all
else
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").all
end
assert_equal 2, posts.size
assert_equal [4,5], posts.collect { |p| p.id }
@@ -537,9 +537,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_conditions_array
if current_adapter?(:OpenBaseAdapter)
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id").all
else
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").all
end
assert_equal 2, posts.size
assert_equal [4,5], posts.collect { |p| p.id }
@@ -547,7 +547,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
posts = ActiveSupport::Deprecation.silence do
- Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "authors.name = ?", 'David' ]).all
end
assert_equal 2, posts.size
@@ -558,41 +558,41 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit_and_high_offset
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' })
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).all
assert_equal 0, posts.size
end
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions
assert_queries(1) do
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
- :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ],
- :references => [:authors, :comments])
+ posts = Post.references(:authors, :comments).
+ scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10,
+ :where => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]).all
assert_equal 0, posts.size
end
end
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions
assert_queries(1) do
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
- :conditions => { 'authors.name' => 'David', 'comments.body' => 'go crazy' })
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10,
+ :where => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }).all
assert_equal 0, posts.size
end
end
def test_count_eager_with_has_many_and_limit_and_high_offset
- posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' })
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).count(:all)
assert_equal 0, posts
end
def test_eager_with_has_many_and_limit_with_no_results
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.title = 'magic forest'").all
assert_equal 0, posts.size
end
def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional
author = authors(:david)
author_posts_without_comments = author.posts.select { |post| post.comments.blank? }
- assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null', :references => :comments)
+ assert_equal author_posts_without_comments.size, author.posts.includes(:comments).where('comments.id is null').references(:comments).count
end
def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional
@@ -602,7 +602,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_and_belongs_to_many_and_limit
- posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
+ posts = Post.scoped(:includes => :categories, :order => "posts.id", :limit => 3).all
assert_equal 3, posts.size
assert_equal 2, posts[0].categories.size
assert_equal 1, posts[1].categories.size
@@ -628,80 +628,47 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
- posts = authors(:david).posts.find(:all,
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :references => :comments,
- :limit => 2
- )
+ posts =
+ authors(:david).posts
+ .includes(:comments)
+ .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
+ .references(:comments)
+ .limit(2)
+ .to_a
assert_equal 2, posts.size
- count = Post.count(
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :references => [:authors, :comments],
- :limit => 2
- )
+ count =
+ Post.includes(:comments, :author)
+ .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
+ .references(:authors, :comments)
+ .limit(2)
+ .count
assert_equal count, posts.size
end
def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
posts = nil
- Post.send(:with_scope, :find => {
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :references => :comments
- }) do
- posts = authors(:david).posts.find(:all, :limit => 2)
- assert_equal 2, posts.size
- end
+ Post.includes(:comments)
+ .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
+ .references(:comments)
+ .scoping do
- Post.send(:with_scope, :find => {
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :references => [:authors, :comments]
- }) do
- count = Post.count(:limit => 2)
- assert_equal count, posts.size
+ posts = authors(:david).posts.limit(2).to_a
+ assert_equal 2, posts.size
end
- end
- def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
- Post.send(:with_scope, :find => { :conditions => "1=1" }) do
- posts = authors(:david).posts.find(:all,
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :references => :comments,
- :limit => 2
- )
- assert_equal 2, posts.size
+ Post.includes(:comments, :author)
+ .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
+ .references(:authors, :comments)
+ .scoping do
- count = Post.count(
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :references => [:authors, :comments],
- :limit => 2
- )
+ count = Post.limit(2).count
assert_equal count, posts.size
end
end
- def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope
- posts_with_explicit_order = Post.find(
- :all, :conditions => 'comments.id is not null', :references => :comments,
- :include => :comments, :order => 'posts.id DESC', :limit => 2
- )
- posts_with_scoped_order = Post.send(:with_scope, :find => {:order => 'posts.id DESC'}) do
- Post.find(
- :all, :conditions => 'comments.id is not null',
- :references => :comments, :include => :comments, :limit => 2
- )
- end
- assert_equal posts_with_explicit_order, posts_with_scoped_order
- end
-
def test_eager_association_loading_with_habtm
- posts = Post.find(:all, :include => :categories, :order => "posts.id")
+ posts = Post.scoped(:includes => :categories, :order => "posts.id").all
assert_equal 2, posts[0].categories.size
assert_equal 1, posts[1].categories.size
assert_equal 0, posts[2].categories.size
@@ -710,23 +677,23 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_inheritance
- SpecialPost.find(:all, :include => [ :comments ])
+ SpecialPost.scoped(:includes => [ :comments ]).all
end
def test_eager_has_one_with_association_inheritance
- post = Post.find(4, :include => [ :very_special_comment ])
+ post = Post.scoped(:includes => [ :very_special_comment ]).find(4)
assert_equal "VerySpecialComment", post.very_special_comment.class.to_s
end
def test_eager_has_many_with_association_inheritance
- post = Post.find(4, :include => [ :special_comments ])
+ post = Post.scoped(:includes => [ :special_comments ]).find(4)
post.special_comments.each do |special_comment|
assert special_comment.is_a?(SpecialComment)
end
end
def test_eager_habtm_with_association_inheritance
- post = Post.find(6, :include => [ :special_categories ])
+ post = Post.scoped(:includes => [ :special_categories ]).find(6)
assert_equal 1, post.special_categories.size
post.special_categories.each do |special_category|
assert_equal "SpecialCategory", special_category.class.to_s
@@ -735,8 +702,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_one_dependent_does_not_destroy_dependent
assert_not_nil companies(:first_firm).account
- f = Firm.find(:first, :include => :account,
- :conditions => ["companies.name = ?", "37signals"])
+ f = Firm.scoped(:includes => :account,
+ :where => ["companies.name = ?", "37signals"]).first
assert_not_nil f.account
assert_equal companies(:first_firm, :reload).account, f.account
end
@@ -750,16 +717,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_invalid_association_reference
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- Post.find(6, :include=> :monkeys )
+ Post.scoped(:includes=> :monkeys ).find(6)
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- Post.find(6, :include=>[ :monkeys ])
+ Post.scoped(:includes=>[ :monkeys ]).find(6)
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- Post.find(6, :include=>[ 'monkeys' ])
+ Post.scoped(:includes=>[ 'monkeys' ]).find(6)
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
- Post.find(6, :include=>[ :monkeys, :elephants ])
+ Post.scoped(:includes=>[ :monkeys, :elephants ]).find(6)
}
end
@@ -804,52 +771,51 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def find_all_ordered(className, include=nil)
- className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include)
+ className.scoped(:order=>"#{className.table_name}.#{className.primary_key}", :includes=>include).all
end
def test_limited_eager_with_order
assert_equal(
posts(:thinking, :sti_comments),
- Post.find(
- :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
:order => 'UPPER(posts.title)', :limit => 2, :offset => 1
- )
+ ).all
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
- Post.find(
- :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
:order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1
- )
+ ).all
)
end
def test_limited_eager_with_multiple_order_columns
assert_equal(
posts(:thinking, :sti_comments),
- Post.find(
- :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
:order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1
- )
+ ).all
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
- Post.find(
- :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
:order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1
- )
+ ).all
)
end
def test_limited_eager_with_numeric_in_association
assert_equal(
people(:david, :susan),
- Person.find(
- :all, :include => [:readers, :primary_contact, :number1_fan],
- :conditions => "number1_fans_people.first_name like 'M%'",
- :references => :number1_fans_people,
+ Person.references(:number1_fans_people).scoped(
+ :includes => [:readers, :primary_contact, :number1_fan],
+ :where => "number1_fans_people.first_name like 'M%'",
:order => 'people.id', :limit => 2, :offset => 0
- )
+ ).all
)
end
@@ -862,9 +828,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_polymorphic_type_condition
- post = Post.find(posts(:thinking).id, :include => :taggings)
+ post = Post.scoped(:includes => :taggings).find(posts(:thinking).id)
assert post.taggings.include?(taggings(:thinking_general))
- post = SpecialPost.find(posts(:thinking).id, :include => :taggings)
+ post = SpecialPost.scoped(:includes => :taggings).find(posts(:thinking).id)
assert post.taggings.include?(taggings(:thinking_general))
end
@@ -915,13 +881,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
def test_eager_with_valid_association_as_string_not_symbol
- assert_nothing_raised { Post.find(:all, :include => 'comments') }
+ assert_nothing_raised { Post.scoped(:includes => 'comments').all }
end
def test_eager_with_floating_point_numbers
assert_queries(2) do
# Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query
- Comment.find :all, :conditions => "123.456 = 123.456", :include => :post
+ Comment.scoped(:where => "123.456 = 123.456", :includes => :post).all
end
end
@@ -965,31 +931,31 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_count_with_include
if current_adapter?(:SybaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15", :references => :comments)
+ assert_equal 3, authors(:david).posts_with_comments.where("len(comments.body) > 15").references(:comments).count
elsif current_adapter?(:OpenBaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15", :references => :comments)
+ assert_equal 3, authors(:david).posts_with_comments.where("length(FETCHBLOB(comments.body)) > 15").references(:comments).count
else
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15", :references => :comments)
+ assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count
end
end
def test_load_with_sti_sharing_association
assert_queries(2) do #should not do 1 query per subclass
- Comment.find :all, :include => :post
+ Comment.includes(:post).all
end
end
def test_conditions_on_join_table_with_include_and_limit
- assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => { 'developers_projects.access_level' => 1 }, :limit => 5).size
+ assert_equal 3, Developer.scoped(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).all.size
end
def test_order_on_join_table_with_include_and_limit
- assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
+ assert_equal 5, Developer.scoped(:includes => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).all.size
end
def test_eager_loading_with_order_on_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC')
+ Post.scoped(:joins => :comments, :includes => :author, :order => 'comments.id DESC').all
end
assert_equal posts(:eager_other), posts[1]
assert_equal authors(:mary), assert_no_queries { posts[1].author}
@@ -997,24 +963,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_loading_with_conditions_on_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
posts = assert_queries(2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
posts = assert_queries(2) do
- Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
+ Post.scoped(:includes => :author, :joins => {:taggings => :tag}, :where => "tags.name = 'General'", :order => 'posts.id').all
end
assert_equal posts(:welcome, :thinking), posts
posts = assert_queries(2) do
- Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
+ Post.scoped(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').all
end
assert_equal posts(:welcome, :thinking), posts
@@ -1022,13 +988,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_loading_with_conditions_on_string_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
posts = assert_queries(2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
@@ -1037,7 +1003,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_loading_with_select_on_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id')
+ Post.scoped(:select => 'posts.*, authors.name as author_name', :includes => :comments, :joins => :author, :order => 'posts.id').all
end
assert_equal 'David', posts[0].author_name
assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments}
@@ -1047,14 +1013,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
Author.columns
authors = assert_queries(2) do
- Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'")
+ Author.scoped(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").all
end
assert_equal authors(:david), authors[0]
assert_equal author_addresses(:david_address), authors[0].author_address
end
def test_preload_belongs_to_uses_exclusive_scope
- people = Person.males.find(:all, :include => :primary_contact)
+ people = Person.males.scoped(:includes => :primary_contact).all
assert_not_equal people.length, 0
people.each do |person|
assert_no_queries {assert_not_nil person.primary_contact}
@@ -1063,15 +1029,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_has_many_uses_exclusive_scope
- people = Person.males.find :all, :include => :agents
+ people = Person.males.includes(:agents).all
people.each do |person|
assert_equal Person.find(person.id).agents, person.agents
end
end
def test_preload_has_many_using_primary_key
- expected = Firm.find(:first).clients_using_primary_key.to_a
- firm = Firm.find :first, :include => :clients_using_primary_key
+ expected = Firm.first.clients_using_primary_key.to_a
+ firm = Firm.includes(:clients_using_primary_key).first
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
end
@@ -1081,9 +1047,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
expected = Firm.find(1).clients_using_primary_key.sort_by(&:name)
# Oracle adapter truncates alias to 30 characters
if current_adapter?(:OracleAdapter)
- firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name'
+ firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name').find(1)
else
- firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
+ firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name').find(1)
end
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
@@ -1092,7 +1058,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preload_has_one_using_primary_key
expected = accounts(:signals37)
- firm = Firm.find :first, :include => :account_using_primary_key, :order => 'companies.id'
+ firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'companies.id').first
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
@@ -1100,7 +1066,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_include_has_one_using_primary_key
expected = accounts(:signals37)
- firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
+ firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'accounts.id').all.detect {|f| f.id == 1}
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
@@ -1164,7 +1130,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_deep_including_through_habtm
- posts = Post.find(:all, :include => {:categories => :categorizations}, :order => "posts.id")
+ posts = Post.scoped(:includes => {:categories => :categorizations}, :order => "posts.id").all
assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length }
assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length }
assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length }
@@ -1174,6 +1140,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal Comment.find(1), Comment.preload(:post => :comments).scoping { Comment.find(1) }
end
+ test "circular preload does not modify unscoped" do
+ expected = FirstPost.unscoped.find(2)
+ FirstPost.preload(:comments => :first_post).find(1)
+ assert_equal expected, FirstPost.unscoped.find(2)
+ end
+
test "preload ignores the scoping" do
assert_equal(
Comment.find(1).post,
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 f457dfb9b3..ed1caa2ef5 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
@@ -123,11 +123,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert active_record.developers.include?(david)
end
- def test_triple_equality
- assert !(Array === Developer.find(1).projects)
- assert Developer.find(1).projects === Array
- end
-
def test_adding_single
jamis = Developer.find(2)
jamis.projects.reload # causing the collection to load
@@ -338,6 +333,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, project.developers.size
end
+ def test_uniq_when_association_already_loaded
+ project = projects(:active_record)
+ project.developers << [ developers(:jamis), developers(:david), developers(:jamis), developers(:david) ]
+ assert_equal 3, Project.includes(:developers).find(project.id).developers.size
+ end
+
def test_deleting
david = Developer.find(1)
active_record = Project.find(1)
@@ -355,7 +356,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_array
david = Developer.find(1)
david.projects.reload
- david.projects.delete(Project.find(:all))
+ david.projects.delete(Project.all)
assert_equal 0, david.projects.size
assert_equal 0, david.projects(true).size
end
@@ -375,10 +376,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
active_record.developers.reload
assert_equal 3, active_record.developers_by_sql.size
- active_record.developers_by_sql.delete(Developer.find(:all))
+ active_record.developers_by_sql.delete(Developer.all)
assert_equal 0, active_record.developers_by_sql(true).size
end
+ def test_deleting_all_with_sql
+ project = Project.find(1)
+ project.developers_by_sql.delete_all
+ assert_equal 0, project.developers_by_sql.size
+ end
+
def test_deleting_all
david = Developer.find(1)
david.projects.reload
@@ -497,7 +504,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_include_uses_array_include_after_loaded
project = projects(:active_record)
- project.developers.class # force load target
+ project.developers.load_target
developer = project.developers.first
@@ -548,43 +555,23 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_find_with_merged_options
assert_equal 1, projects(:active_record).limited_developers.size
- assert_equal 1, projects(:active_record).limited_developers.find(:all).size
- assert_equal 3, projects(:active_record).limited_developers.find(:all, :limit => nil).size
+ assert_equal 1, projects(:active_record).limited_developers.all.size
+ assert_equal 3, projects(:active_record).limited_developers.limit(nil).all.size
end
def test_dynamic_find_should_respect_association_order
# Developers are ordered 'name DESC, id DESC'
high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
- assert_equal high_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'")
+ assert_equal high_id_jamis, projects(:active_record).developers.scoped(:where => "name = 'Jamis'").first
assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis')
end
- def test_dynamic_find_all_should_respect_association_order
- # Developers are ordered 'name DESC, id DESC'
- low_id_jamis = developers(:jamis)
- middle_id_jamis = developers(:poor_jamis)
- high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
-
- assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'")
- assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis')
- end
-
def test_find_should_append_to_association_order
ordered_developers = projects(:active_record).developers.order('projects.id')
assert_equal ['developers.name desc, developers.id desc', 'projects.id'], ordered_developers.order_values
end
- def test_dynamic_find_all_should_respect_association_limit
- assert_equal 1, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'").length
- assert_equal 1, projects(:active_record).limited_developers.find_all_by_name('Jamis').length
- end
-
- def test_dynamic_find_all_order_should_override_association_limit
- assert_equal 2, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'", :limit => 9_000).length
- assert_equal 2, projects(:active_record).limited_developers.find_all_by_name('Jamis', :limit => 9_000).length
- end
-
def test_dynamic_find_all_should_respect_readonly_access
projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
projects(:active_record).readonly_developers.each { |d| d.readonly? }
@@ -632,7 +619,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_consider_type
- developer = Developer.find(:first)
+ developer = Developer.first
special_project = SpecialProject.create("name" => "Special Project")
other_project = developer.projects.first
@@ -667,7 +654,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
categories(:technology).select_testing_posts(true).each do |o|
assert_respond_to o, :correctness_marker
end
- assert_respond_to categories(:technology).select_testing_posts.find(:first), :correctness_marker
+ assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker
end
def test_habtm_selects_all_columns_by_default
@@ -681,10 +668,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_join_table_alias
assert_equal(
3,
- Developer.find(
- :all, :include => {:projects => :developers}, :references => :developers_projects_join,
- :conditions => 'developers_projects_join.joined_on IS NOT NULL'
- ).size
+ Developer.references(:developers_projects_join).scoped(
+ :includes => {:projects => :developers},
+ :where => 'developers_projects_join.joined_on IS NOT NULL'
+ ).to_a.size
)
end
@@ -697,16 +684,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal(
3,
- Developer.find(
- :all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL',
- :references => :developers_projects_join, :group => group.join(",")
- ).size
+ Developer.references(:developers_projects_join).scoped(
+ :includes => {:projects => :developers}, :where => 'developers_projects_join.joined_on IS NOT NULL',
+ :group => group.join(",")
+ ).to_a.size
)
end
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)
+ all_posts_from_category1 = Post.scoped(:where => "category_id = 1", :joins => :categories).all
+ grouped_posts_of_category1 = Post.scoped(:where => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories).all
assert_equal 5, all_posts_from_category1.size
assert_equal 2, grouped_posts_of_category1.size
end
@@ -780,8 +767,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, project.developers.size
assert_equal 1, developer.projects.size
- assert_equal developer, project.developers.find(:first)
- assert_equal project, developer.projects.find(:first)
+ assert_equal developer, project.developers.first
+ assert_equal project, developer.projects.first
end
def test_self_referential_habtm_without_foreign_key_set_should_raise_exception
@@ -798,13 +785,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
end
- def test_counting_on_habtm_association_and_not_array
- david = Developer.find(1)
- # Extra parameter just to make sure we aren't falling back to
- # Array#count in Ruby >=1.8.7, which would raise an ArgumentError
- assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') }
- end
-
def test_count
david = Developer.find(1)
assert_equal 2, david.projects.count
@@ -827,7 +807,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_association_proxy_transaction_method_starts_transaction_in_association_class
Post.expects(:transaction)
- Category.find(:first).posts.transaction do
+ Category.first.posts.transaction do
# nothing
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 6a4f972356..8b384c2513 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -221,48 +221,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
- def test_find_or_create_by_resets_cached_counters
- person = Person.create! :first_name => 'tenderlove'
- post = Post.first
-
- assert_equal [], person.readers
- assert_nil person.readers.find_by_post_id(post.id)
-
- person.readers.find_or_create_by_post_id(post.id)
-
- assert_equal 1, person.readers.count
- assert_equal 1, person.readers.length
- assert_equal post, person.readers.first.post
- assert_equal person, person.readers.first.person
- end
-
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| }
end
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
def test_counting_with_counter_sql
- assert_equal 2, Firm.find(:first, :order => "id").clients.count
+ assert_equal 2, Firm.scoped(:order => "id").first.clients.count
end
def test_counting
- assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count
- end
-
- def test_counting_with_empty_hash_conditions
- assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {})
- end
-
- def test_counting_with_single_conditions
- assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"])
+ assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count
end
def test_counting_with_single_hash
- assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"})
+ assert_equal 1, Firm.scoped(:order => "id").first.plain_clients.where(:name => "Microsoft").count
end
def test_counting_with_column_name_and_hash
- assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name)
+ assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count(:name)
end
def test_counting_with_association_limit
@@ -272,7 +249,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding
- assert_equal 2, Firm.find(:first, :order => "id").clients.length
+ assert_equal 2, Firm.scoped(:order => "id").first.clients.length
end
def test_finding_array_compatibility
@@ -281,14 +258,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_with_blank_conditions
[[], {}, nil, ""].each do |blank|
- assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size
+ assert_equal 2, Firm.scoped(:order => "id").first.clients.where(blank).all.size
end
end
def test_find_many_with_merged_options
assert_equal 1, companies(:first_firm).limited_clients.size
- assert_equal 1, companies(:first_firm).limited_clients.find(:all).size
- assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size
+ assert_equal 1, companies(:first_firm).limited_clients.all.size
+ assert_equal 2, companies(:first_firm).limited_clients.limit(nil).all.size
end
def test_find_should_append_to_association_order
@@ -296,104 +273,65 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal ['id DESC', 'companies.id'], ordered_clients.order_values
end
- def test_dynamic_find_last_without_specified_order
- assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client')
- end
-
def test_dynamic_find_should_respect_association_order
- assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'")
+ assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").first
assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
end
- def test_dynamic_find_all_should_respect_association_order
- assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'")
- assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
- end
-
- def test_dynamic_find_all_should_respect_association_limit
- assert_equal 1, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'").length
- assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
- end
-
- def test_dynamic_find_all_limit_should_override_association_limit
- assert_equal 2, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'", :limit => 9_000).length
- assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
- end
-
- def test_dynamic_find_all_should_respect_readonly_access
- companies(:first_firm).readonly_clients.find(:all).each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
- companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? }
- end
-
- def test_dynamic_find_or_create_from_two_attributes_using_an_association
- author = authors(:david)
- number_of_posts = Post.count
- another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
- assert_equal number_of_posts + 1, Post.count
- assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
- assert another.persisted?
- end
-
def test_cant_save_has_many_readonly_association
authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
authors(:david).readonly_comments.each { |c| assert c.readonly? }
end
- def test_triple_equality
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert !(Array === Firm.find(:first, :order => "id").clients)
- assert Firm.find(:first, :order => "id").clients === Array
- end
-
def test_finding_default_orders
- assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name
+ assert_equal "Summit", Firm.scoped(:order => "id").first.clients.first.name
end
def test_finding_with_different_class_name_and_order
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_sorted_desc.first.name
end
def test_finding_with_foreign_key
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_of_firm.first.name
end
def test_finding_with_condition
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms.first.name
end
def test_finding_with_condition_hash
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms_with_hash_conditions.first.name
end
def test_finding_using_primary_key
- assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name
+ assert_equal "Summit", Firm.scoped(:order => "id").first.clients_using_primary_key.first.name
end
def test_finding_using_sql
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
first_client = firm.clients_using_sql.first
assert_not_nil first_client
assert_equal "Microsoft", first_client.name
assert_equal 1, firm.clients_using_sql.size
- assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size
+ assert_equal 1, Firm.scoped(:order => "id").first.clients_using_sql.size
end
def test_finding_using_sql_take_into_account_only_uniq_ids
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client = firm.clients_using_sql.first
assert_equal client, firm.clients_using_sql.find(client.id, client.id)
assert_equal client, firm.clients_using_sql.find(client.id, client.id.to_s)
end
def test_counting_using_sql
- assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size
- assert Firm.find(:first, :order => "id").clients_using_counter_sql.any?
- assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size
- assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any?
+ assert_equal 1, Firm.scoped(:order => "id").first.clients_using_counter_sql.size
+ assert Firm.scoped(:order => "id").first.clients_using_counter_sql.any?
+ assert_equal 0, Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.size
+ assert !Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.any?
end
def test_counting_non_existant_items_using_sql
- assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size
+ assert_equal 0, Firm.scoped(:order => "id").first.no_clients_using_counter_sql.size
end
def test_counting_using_finder_sql
@@ -408,7 +346,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_ids
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find }
@@ -428,7 +366,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_string_ids_when_using_finder_sql
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client = firm.clients_using_finder_sql.find("2")
assert_kind_of Client, client
@@ -444,9 +382,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_all
- firm = Firm.find(:first, :order => "id")
- assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length
- assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length
+ firm = Firm.scoped(:order => "id").first
+ assert_equal 2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'").all.length
+ assert_equal 1, firm.clients.scoped(:where => "name = 'Summit'").all.length
end
def test_find_each
@@ -465,7 +403,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
assert_queries(2) do
- firm.clients.find_each(:batch_size => 1, :conditions => {:name => "Microsoft"}) do |c|
+ firm.clients.where(name: 'Microsoft').find_each(batch_size: 1) do |c|
assert_equal firm.id, c.firm_id
assert_equal "Microsoft", c.name
end
@@ -490,29 +428,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_all_sanitized
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- firm = Firm.find(:first, :order => "id")
- summit = firm.clients.find(:all, :conditions => "name = 'Summit'")
- assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"])
- assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }])
+ firm = Firm.scoped(:order => "id").first
+ summit = firm.clients.scoped(:where => "name = 'Summit'").all
+ assert_equal summit, firm.clients.scoped(:where => ["name = ?", "Summit"]).all
+ assert_equal summit, firm.clients.scoped(:where => ["name = :name", { :name => "Summit" }]).all
end
def test_find_first
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client2 = Client.find(2)
- assert_equal firm.clients.first, firm.clients.find(:first, :order => "id")
- assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id")
+ assert_equal firm.clients.first, firm.clients.scoped(:order => "id").first
+ assert_equal client2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'", :order => "id").first
end
def test_find_first_sanitized
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client2 = Client.find(2)
- assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id")
- assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id")
+ assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id").first
+ assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id").first
end
def test_find_all_with_include_and_conditions
assert_nothing_raised do
- Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'})
+ Developer.scoped(:joins => :audit_logs, :where => {'audit_logs.message' => nil, :name => 'Smith'}).all
end
end
@@ -522,8 +460,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_grouped
- all_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1")
- grouped_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count')
+ all_clients_of_firm1 = Client.scoped(:where => "firm_id = 1").all
+ grouped_clients_of_firm1 = Client.scoped(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').all
assert_equal 2, all_clients_of_firm1.size
assert_equal 1, grouped_clients_of_firm1.size
end
@@ -581,7 +519,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_with_bang_on_has_many_raises_when_record_not_saved
assert_raise(ActiveRecord::RecordInvalid) do
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
firm.plain_clients.create!
end
end
@@ -746,57 +684,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !companies(:first_firm).clients_of_firm.loaded?
end
- def test_find_or_initialize
- the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
- assert_equal companies(:first_firm).id, the_client.firm_id
- assert_equal "Yet another client", the_client.name
- assert !the_client.persisted?
- end
-
- def test_find_or_create_updates_size
- number_of_clients = companies(:first_firm).clients.size
- the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
- assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
- assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
- assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
- end
-
- def test_find_or_initialize_updates_collection_size
- number_of_clients = companies(:first_firm).clients_of_firm.size
- companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
- assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
- end
-
- def test_find_or_initialize_returns_the_instantiated_object
- client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
- assert_equal client, companies(:first_firm).clients_of_firm[-1]
- end
-
- def test_find_or_initialize_only_instantiates_a_single_object
- number_of_clients = Client.count
- companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save!
- companies(:first_firm).save!
- assert_equal number_of_clients+1, Client.count
- end
-
- def test_find_or_create_with_hash
- post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
- assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
- assert post.persisted?
- end
-
- def test_find_or_create_with_one_attribute_followed_by_hash
- post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
- assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
- assert post.persisted?
- end
-
- def test_find_or_create_should_work_with_block
- post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
- assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
- assert post.persisted?
- end
-
def test_deleting
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
@@ -813,7 +700,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_deleting_updates_counter_cache
- topic = Topic.first(:order => "id ASC")
+ topic = Topic.order("id ASC").first
assert_equal topic.replies.to_a.size, topic.replies_count
topic.replies.delete(topic.replies.first)
@@ -905,12 +792,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
client_id = firm.clients_of_firm.first.id
assert_equal 1, firm.clients_of_firm.size
- cleared = firm.clients_of_firm.clear
+ firm.clients_of_firm.clear
assert_equal 0, firm.clients_of_firm.size
assert_equal 0, firm.clients_of_firm(true).size
assert_equal [], Client.destroyed_client_ids[firm.id]
- assert_equal firm.clients_of_firm.object_id, cleared.object_id
# Should not be destroyed since the association is not dependent.
assert_nothing_raised do
@@ -976,11 +862,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_dependent_association_respects_optional_sanitized_conditions_on_delete
@@ -988,11 +874,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_dependent_association_respects_optional_hash_conditions_on_delete
@@ -1000,22 +886,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_delete_all_association_with_primary_key_deletes_correct_records
- firm = Firm.find(:first)
+ firm = Firm.first
# break the vanilla firm_id foreign key
assert_equal 2, firm.clients.count
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
- firm = Firm.find(:first)
+ firm = Firm.first
firm.destroy
assert_nil Client.find_by_id(old_record.id)
end
@@ -1123,7 +1009,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
assert_equal 2, firm.clients.size
firm.destroy
- assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty?
+ assert Client.scoped(:where => "firm_id=#{firm.id}").all.empty?
end
def test_dependence_for_associations_with_hash_condition
@@ -1133,7 +1019,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_dependent_when_deleted_from_association
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
assert_equal 2, firm.clients.size
client = firm.clients.first
@@ -1161,7 +1047,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.destroy rescue "do nothing"
- assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size
+ assert_equal 2, Client.scoped(:where => "firm_id=#{firm.id}").all.size
end
def test_dependence_on_account
@@ -1224,16 +1110,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_adding_array_and_collection
- assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients }
- end
-
- def test_find_all_without_conditions
- firm = companies(:first_firm)
- assert_equal 2, firm.clients.find(:all).length
+ assert_nothing_raised { Firm.first.clients + Firm.all.last.clients }
end
def test_replace_with_less
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
firm.clients = [companies(:first_client)]
assert firm.save, "Could not save firm"
firm.reload
@@ -1247,7 +1128,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_replace_with_new
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
firm.save
firm.reload
@@ -1347,29 +1228,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_dynamic_find_should_respect_association_order_for_through
- assert_equal Comment.find(10), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'")
+ assert_equal Comment.find(10), authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").first
assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment')
end
- def test_dynamic_find_all_should_respect_association_order_for_through
- assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'")
- assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
- end
-
- def test_dynamic_find_all_should_respect_association_limit_for_through
- assert_equal 1, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'").length
- assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
- end
-
- def test_dynamic_find_all_order_should_override_association_limit_for_through
- assert_equal 4, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'", :limit => 9_000).length
- assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
- end
-
- def test_find_all_include_over_the_same_table_for_through
- assert_equal 2, people(:michael).posts.find(:all, :include => :people).length
- end
-
def test_has_many_through_respects_hash_conditions
assert_equal authors(:david).hello_posts, authors(:david).hello_posts_with_hash_conditions
assert_equal authors(:david).hello_post_comments, authors(:david).hello_post_comments_with_hash_conditions
@@ -1377,7 +1239,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_include_uses_array_include_after_loaded
firm = companies(:first_firm)
- firm.clients.class # force load target
+ firm.clients.load_target
client = firm.clients.first
@@ -1427,7 +1289,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
firm = companies(:first_firm)
- firm.clients.class # force load target
+ firm.clients.load_target
assert firm.clients.loaded?
assert_no_queries do
@@ -1481,13 +1343,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, author.essays.size
end
- assert_equal author.essays, Essay.find_all_by_writer_id("David")
+ assert_equal author.essays, Essay.where(writer_id: "David")
end
def test_has_many_custom_primary_key
david = authors(:david)
- assert_equal david.essays, Essay.find_all_by_writer_id("David")
+ assert_equal david.essays, Essay.where(writer_id: "David")
end
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
@@ -1499,17 +1361,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query
- firm = companies(:first_firm)
- firm.clients.class # force load target
-
- assert_queries 2 do
- assert firm.clients.loaded?
- firm.clients.first(:order => 'name')
- firm.clients.last(:order => 'name')
- end
- end
-
def test_calling_first_or_last_with_integer_on_association_should_load_association
firm = companies(:first_firm)
@@ -1567,11 +1418,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = Namespaced::Firm.create({ :name => 'Some Company' })
firm.clients.create({ :name => 'Some Client' })
- stats = Namespaced::Firm.find(firm.id, {
+ stats = Namespaced::Firm.scoped(
:select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients",
:joins => :clients,
:group => "#{Namespaced::Firm.table_name}.id"
- })
+ ).find firm.id
assert_equal 1, stats.num_clients.to_i
ensure
@@ -1580,7 +1431,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_association_proxy_transaction_method_starts_transaction_in_association_class
Comment.expects(:transaction)
- Post.find(:first).comments.transaction do
+ Post.first.comments.transaction do
# nothing
end
end
@@ -1597,7 +1448,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_creating_using_primary_key
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client = firm.clients_using_primary_key.create!(:name => 'test')
assert_equal firm.name, client.firm_name
end
@@ -1721,6 +1572,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb2], car.reload.bulbs
end
+ def test_replace_returns_target
+ car = Car.create(:name => 'honda')
+ bulb1 = car.bulbs.create
+ bulb2 = car.bulbs.create
+ bulb3 = Bulb.create
+
+ assert_equal [bulb1, bulb2], car.bulbs
+ result = car.bulbs.replace([bulb3, bulb1])
+ assert_equal [bulb1, bulb3], car.bulbs
+ assert_equal [bulb1, bulb3], result
+ end
+
def test_building_has_many_association_with_restrict_dependency
option_before = ActiveRecord::Base.dependent_restrict_raises
ActiveRecord::Base.dependent_restrict_raises = true
@@ -1732,4 +1595,35 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.dependent_restrict_raises = option_before
end
+
+ def test_collection_association_with_private_kernel_method
+ firm = companies(:first_firm)
+ assert_equal [accounts(:signals37)], firm.accounts.open
+ end
+
+ test "first_or_initialize adds the record to the association" do
+ firm = Firm.create! name: 'omg'
+ client = firm.clients_of_firm.first_or_initialize
+ assert_equal [client], firm.clients_of_firm
+ end
+
+ test "first_or_create adds the record to the association" do
+ firm = Firm.create! name: 'omg'
+ firm.clients_of_firm.load_target
+ client = firm.clients_of_firm.first_or_create name: 'lol'
+ assert_equal [client], firm.clients_of_firm
+ assert_equal [client], firm.reload.clients_of_firm
+ end
+
+ test "delete_all, when not loaded, doesn't load the records" do
+ post = posts(:welcome)
+
+ assert post.taggings_with_delete_all.count > 0
+ assert !post.taggings_with_delete_all.loaded?
+
+ # 2 queries: one DELETE and another to update the counter cache
+ assert_queries(2) do
+ post.taggings_with_delete_all.delete_all
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index e9b930204f..1c06007d86 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -533,7 +533,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_count_with_include_should_alias_join_table
- assert_equal 2, people(:michael).posts.count(:include => :readers)
+ assert_equal 2, people(:michael).posts.includes(:readers).count
end
def test_inner_join_with_quoted_table_name
@@ -568,7 +568,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_association_proxy_transaction_method_starts_transaction_in_association_class
Tag.expects(:transaction)
- Post.find(:first).tags.transaction do
+ Post.first.tags.transaction do
# nothing
end
end
@@ -651,7 +651,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_collection_singular_ids_setter
company = companies(:rails_core)
- dev = Developer.find(:first)
+ dev = Developer.first
company.developer_ids = [dev.id]
assert_equal [dev], company.developers
@@ -671,7 +671,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
- ids = [Developer.find(:first).id, -9999]
+ ids = [Developer.first.id, -9999]
assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 9c05b36426..88ec65706c 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -25,13 +25,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { assert_nil firm.account }
assert_queries(0) { assert_nil firm.account }
- firms = Firm.find(:all, :include => :account)
+ firms = Firm.scoped(:includes => :account).all
assert_queries(0) { firms.each(&:account) }
end
def test_with_select
assert_equal Firm.find(1).account_with_select.attributes.size, 2
- assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2
+ assert_equal Firm.scoped(:includes => :account_with_select).find(1).account_with_select.attributes.size, 2
end
def test_finding_using_primary_key
@@ -294,13 +294,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_dependence_with_missing_association_and_nullify
Account.destroy_all
- firm = DependentFirm.find(:first)
+ firm = DependentFirm.first
assert_nil firm.account
firm.destroy
end
def test_finding_with_interpolated_condition
- firm = Firm.find(:first)
+ firm = Firm.first
superior = firm.clients.create(:name => 'SuperiorCo')
superior.rating = 10
superior.save
@@ -346,14 +346,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
Firm.find(@firm.id).save!
- Firm.find(@firm.id, :include => :account).save!
+ Firm.scoped(:includes => :account).find(@firm.id).save!
end
@firm.account.destroy
assert_nothing_raised do
Firm.find(@firm.id).save!
- Firm.find(@firm.id, :include => :account).save!
+ Firm.scoped(:includes => :account).find(@firm.id).save!
end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 2503349c08..94b9639e57 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -73,7 +73,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_eager_loading
members = assert_queries(3) do #base table, through table, clubs table
- Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
+ Member.scoped(:includes => :club, :where => ["name = ?", "Groucho Marx"]).all
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].club}
@@ -81,7 +81,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_eager_loading_through_polymorphic
members = assert_queries(3) do #base table, through table, clubs table
- Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
+ Member.scoped(:includes => :sponsor_club, :where => ["name = ?", "Groucho Marx"]).all
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].sponsor_club}
@@ -89,14 +89,14 @@ 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
+ assert_equal clubs(:moustache_club), Member.scoped(:includes => :favourite_club).find(@member.id).favourite_club
memberships(:membership_of_favourite_club).update_column(:favourite, false)
- assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club
+ assert_equal nil, Member.scoped(:includes => :favourite_club).find(@member.id).reload.favourite_club
# conditions on the source table
- assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club
+ assert_equal clubs(:moustache_club), Member.scoped(:includes => :hairy_club).find(@member.id).hairy_club
clubs(:moustache_club).update_column(:name, "Association of Clean-Shaven Persons")
- assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club
+ assert_equal nil, Member.scoped(:includes => :hairy_club).find(@member.id).reload.hairy_club
end
def test_has_one_through_polymorphic_with_source_type
@@ -104,14 +104,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
def test_eager_has_one_through_polymorphic_with_source_type
- clubs = Club.find(:all, :include => :sponsored_member, :conditions => ["name = ?","Moustache and Eyebrow Fancier Club"])
+ clubs = Club.scoped(:includes => :sponsored_member, :where => ["name = ?","Moustache and Eyebrow Fancier Club"]).all
# Only the eyebrow fanciers club has a sponsored_member
assert_not_nil assert_no_queries {clubs[0].sponsored_member}
end
def test_has_one_through_nonpreload_eagerloading
members = assert_queries(1) do
- Member.find(:all, :include => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
+ Member.scoped(:includes => :club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].club}
@@ -119,7 +119,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
members = assert_queries(1) do
- Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
+ Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].sponsor_club}
@@ -128,7 +128,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save!
members = assert_queries(1) do
- Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback
+ Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC').all #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
@@ -197,7 +197,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
@member.member_detail = @member_detail
@member.organization = @organization
@member_details = assert_queries(3) do
- MemberDetail.find(:all, :include => :member_type)
+ MemberDetail.scoped(:includes => :member_type).all
end
@new_detail = @member_details[0]
assert @new_detail.send(:association, :member_type).loaded?
@@ -210,14 +210,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
Club.find(@club.id).save!
- Club.find(@club.id, :include => :sponsored_member).save!
+ Club.scoped(:includes => :sponsored_member).find(@club.id).save!
end
@club.sponsor.destroy
assert_nothing_raised do
Club.find(@club.id).save!
- Club.find(@club.id, :include => :sponsored_member).save!
+ Club.scoped(:includes => :sponsored_member).find(@club.id).save!
end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 68a1e62328..1d61d5c474 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -72,17 +72,17 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_count_honors_implicit_inner_joins
real_count = Author.scoped.to_a.sum{|a| a.posts.count }
- assert_equal real_count, Author.count(:joins => :posts), "plain inner join count should match the number of referenced posts records"
+ assert_equal real_count, Author.joins(:posts).count, "plain inner join count should match the number of referenced posts records"
end
def test_calculate_honors_implicit_inner_joins
real_count = Author.scoped.to_a.sum{|a| a.posts.count }
- assert_equal real_count, Author.calculate(:count, 'authors.id', :joins => :posts), "plain inner join count should match the number of referenced posts records"
+ assert_equal real_count, Author.joins(:posts).calculate(:count, 'authors.id'), "plain inner join count should match the number of referenced posts records"
end
def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions
real_count = Author.scoped.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length
- authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'")
+ authors_with_welcoming_post_titles = Author.scoped(:joins => :posts, :where => "posts.title like 'Welcome%'").calculate(:count, 'authors.id', :distinct => true)
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 76282213d8..f35ffb2994 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -96,7 +96,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face)
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face).first
f = m.face
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = 'Bongo'
@@ -104,7 +104,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
f.man.name = 'Mungo'
assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face, :order => 'faces.id')
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face, :order => 'faces.id').first
f = m.face
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = 'Bongo'
@@ -114,7 +114,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_built_child
- m = Man.find(:first)
+ m = Man.first
f = m.build_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -125,7 +125,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_child
- m = Man.find(:first)
+ m = Man.first
f = m.create_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -136,7 +136,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method
- m = Man.find(:first)
+ m = Man.first
f = m.create_face!(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -147,7 +147,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_replaced_via_accessor_child
- m = Man.find(:first)
+ m = Man.first
f = Face.new(:description => 'haunted')
m.face = f
assert_not_nil f.man
@@ -159,7 +159,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.dirty_face }
end
end
@@ -179,7 +179,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_eager_loaded_children
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests)
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests).first
is = m.interests
is.each do |i|
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -189,7 +189,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
end
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests, :order => 'interests.id')
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests, :order => 'interests.id').first
is = m.interests
is.each do |i|
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -201,7 +201,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_block_style_built_child
- m = Man.find(:first)
+ m = Man.first
i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated"
assert_not_nil i.man
@@ -213,7 +213,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
- m = Man.find(:first)
+ m = Man.first
i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment')
assert_not_nil i.man
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -224,7 +224,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_block_style_created_child
- m = Man.find(:first)
+ m = Man.first
i = m.interests.create {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated"
assert_not_nil i.man
@@ -248,7 +248,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_replaced_via_accessor_children
- m = Man.find(:first)
+ m = Man.first
i = Interest.new(:topic => 'Industrial Revolution Re-enactment')
m.interests = [i]
assert_not_nil i.man
@@ -260,7 +260,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
end
@@ -278,7 +278,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
- f = Face.find(:first, :include => :man, :conditions => {:description => 'trusting'})
+ f = Face.scoped(:includes => :man, :where => {:description => 'trusting'}).first
m = f.man
assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -286,7 +286,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
m.face.description = 'pleasing'
assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.find(:first, :include => :man, :order => 'men.id', :conditions => {:description => 'trusting'})
+ f = Face.scoped(:includes => :man, :order => 'men.id', :where => {:description => 'trusting'}).first
m = f.man
assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -331,7 +331,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
def test_child_instance_should_be_shared_with_replaced_via_accessor_parent
- f = Face.find(:first)
+ f = Face.first
m = Man.new(:name => 'Charles')
f.man = m
assert_not_nil m.face
@@ -343,7 +343,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_man }
end
end
@@ -351,7 +351,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
fixtures :men, :faces, :interests
def test_child_instance_should_be_shared_with_parent_on_find
- f = Face.find(:first, :conditions => {:description => 'confused'})
+ f = Face.scoped(:where => {:description => 'confused'}).first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -361,7 +361,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
end
def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
- f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man)
+ f = Face.scoped(:where => {:description => 'confused'}, :includes => :man).first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -369,7 +369,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
m.polymorphic_face.description = 'pleasing'
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man, :order => 'men.id')
+ f = Face.scoped(:where => {:description => 'confused'}, :includes => :man, :order => 'men.id').first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -421,19 +421,19 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error
# Ideally this would, if only for symmetry's sake with other association types
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man }
+ assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man }
end
def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error
# fails because no class has the correct inverse_of for horrible_polymorphic_man
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man = Man.first }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man = Man.first }
end
def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error
# passes because Man does have the correct inverse_of
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Man.first }
+ assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first }
# fails because Interest does have the correct inverse_of
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Interest.first }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first }
end
end
@@ -444,7 +444,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
- i = Interest.find(:first)
+ i = Interest.first
i.zine
i.man
end
@@ -452,7 +452,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models
assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
- i = Interest.find(:first)
+ i = Interest.first
i.build_zine(:title => 'Get Some in Winter! 2008')
i.build_man(:name => 'Gordon')
i.save!
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 301755249c..ecc676f300 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -46,16 +46,12 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert !authors(:mary).unique_categorized_posts.loaded?
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count }
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) }
- assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, :conditions => "title is NULL") }
+ assert_queries(1) { assert_equal 0, author.unique_categorized_posts.where(title: nil).count(:title) }
assert !authors(:mary).unique_categorized_posts.loaded?
end
def test_has_many_uniq_through_find
- assert_equal 1, authors(:mary).unique_categorized_posts.find(:all).size
- end
-
- def test_has_many_uniq_through_dynamic_find
- assert_equal 1, authors(:mary).unique_categorized_posts.find_all_by_title("So I was thinking").size
+ assert_equal 1, authors(:mary).unique_categorized_posts.all.size
end
def test_polymorphic_has_many_going_through_join_model
@@ -71,7 +67,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_polymorphic_has_many_going_through_join_model_with_find
- assert_equal tags(:general), tag = posts(:welcome).tags.find(:first)
+ assert_equal tags(:general), tag = posts(:welcome).tags.first
assert_no_queries do
tag.tagging
end
@@ -85,7 +81,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find
- assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first)
+ assert_equal tags(:general), tag = posts(:welcome).funky_tags.first
assert_no_queries do
tag.tagging
end
@@ -237,8 +233,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_has_many_through
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_authors = Post.scoped(:includes => :authors, :order => 'posts.id').all
assert_equal posts.length, posts_with_authors.length
posts.length.times do |i|
assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length }
@@ -246,7 +242,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_one
- post = Post.find_by_id(posts(:welcome).id, :include => :tagging)
+ post = Post.includes(:tagging).find posts(:welcome).id
tagging = taggings(:welcome_general)
assert_no_queries do
assert_equal tagging, post.tagging
@@ -254,7 +250,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_one_defined_in_abstract_parent
- item = Item.find_by_id(items(:dvd).id, :include => :tagging)
+ item = Item.includes(:tagging).find items(:dvd).id
tagging = taggings(:godfather)
assert_no_queries do
assert_equal tagging, item.tagging
@@ -262,8 +258,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_many_through
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all
assert_equal posts.length, posts_with_tags.length
posts.length.times do |i|
assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
@@ -271,8 +267,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_many
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all
assert_equal posts.length, posts_with_taggings.length
posts.length.times do |i|
assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
@@ -280,25 +276,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_find_all
- assert_equal [categories(:general)], authors(:david).categories.find(:all)
+ assert_equal [categories(:general)], authors(:david).categories.all
end
def test_has_many_find_first
- assert_equal categories(:general), authors(:david).categories.find(:first)
+ assert_equal categories(:general), authors(:david).categories.first
end
def test_has_many_with_hash_conditions
- assert_equal categories(:general), authors(:david).categories_like_general.find(:first)
+ assert_equal categories(:general), authors(:david).categories_like_general.first
end
def test_has_many_find_conditions
- assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'")
- assert_nil authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'")
- end
-
- def test_has_many_class_methods_called_by_method_missing
- assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first
- assert_nil authors(:david).categories.find_by_name('Technology')
+ assert_equal categories(:general), authors(:david).categories.scoped(:where => "categories.name = 'General'").first
+ assert_nil authors(:david).categories.scoped(:where => "categories.name = 'Technology'").first
end
def test_has_many_array_methods_called_by_method_missing
@@ -327,14 +318,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
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
- assert_nothing_raised do
- Post.send(:with_scope, :find => {:joins => "left outer join comments on comments.id = posts.id"}) do
- Post.find :all, :select => "comments.id, authors.id", :joins => "left outer join authors on authors.id = posts.author_id"
- end
- end
- end
-
def test_belongs_to_polymorphic_with_counter_cache
assert_equal 1, posts(:welcome)[:taggings_count]
tagging = posts(:welcome).taggings.create(:tag => tags(:general))
@@ -362,7 +345,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
assert_raise ActiveRecord::EagerLoadPolymorphicError do
- tags(:general).taggings.find(:all, :include => :taggable, :references => :bogus_table, :conditions => 'bogus_table.column = 1')
+ tags(:general).taggings.includes(:taggable).where('bogus_table.column = 1').references(:bogus_table).to_a
end
end
@@ -372,7 +355,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_eager_has_many_polymorphic_with_source_type
- tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts)
+ tag_with_include = Tag.scoped(:includes => :tagged_posts).find(tags(:general).id)
desired = posts(:welcome, :thinking)
assert_no_queries do
# added sort by ID as otherwise test using JRuby was failing as array elements were in different order
@@ -382,20 +365,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_has_many_find_all
- assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first
+ assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').all.first
end
def test_has_many_through_has_many_find_all_with_custom_class
- assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first
+ assert_equal comments(:greetings), authors(:david).funky_comments.scoped(:order => 'comments.id').all.first
end
def test_has_many_through_has_many_find_first
- assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id')
+ assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').first
end
def test_has_many_through_has_many_find_conditions
- options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' }
- assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options)
+ options = { :where => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' }
+ assert_equal comments(:does_it_hurt), authors(:david).comments.scoped(options).first
end
def test_has_many_through_has_many_find_by_id
@@ -411,7 +394,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_has_many_through_polymorphic_has_many
- author = Author.find_by_id(authors(:david).id, :include => :taggings)
+ author = Author.includes(:taggings).find authors(:david).id
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
@@ -419,7 +402,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_eager_load_has_many_through_has_many
- author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
+ author = Author.scoped(:where => ['name = ?', 'David'], :includes => :comments, :order => 'comments.id').first
SpecialComment.new; VerySpecialComment.new
assert_no_queries do
assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id)
@@ -427,7 +410,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_eager_load_has_many_through_has_many_with_conditions
- post = Post.find(:first, :include => :invalid_tags)
+ post = Post.scoped(:includes => :invalid_tags).first
assert_no_queries do
post.invalid_tags
end
@@ -435,8 +418,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_eager_belongs_to_and_has_one_not_singularized
assert_nothing_raised do
- Author.find(:first, :include => :author_address)
- AuthorAddress.find(:first, :include => :author)
+ Author.scoped(:includes => :author_address).first
+ AuthorAddress.scoped(:includes => :author).first
end
end
@@ -452,7 +435,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
- author = Author.find(:first)
+ author = Author.first
assert_present author.comments
assert_blank author.nonexistant_comments
end
@@ -622,7 +605,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_polymorphic_has_many
expected = taggings(:welcome_general)
- p = Post.find(posts(:welcome).id, :include => :taggings)
+ p = Post.scoped(:includes => :taggings).find(posts(:welcome).id)
assert_no_queries {assert p.taggings.include?(expected)}
assert posts(:welcome).taggings.include?(taggings(:welcome_general))
end
@@ -630,18 +613,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_polymorphic_has_one
expected = posts(:welcome)
- tagging = Tagging.find(taggings(:welcome_general).id, :include => :taggable)
+ tagging = Tagging.scoped(:includes => :taggable).find(taggings(:welcome_general).id)
assert_no_queries { assert_equal expected, tagging.taggable}
end
def test_polymorphic_belongs_to
- p = Post.find(posts(:welcome).id, :include => {:taggings => :taggable})
+ p = Post.scoped(:includes => {:taggings => :taggable}).find(posts(:welcome).id)
assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable}
end
def test_preload_polymorphic_has_many_through
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all
assert_equal posts.length, posts_with_tags.length
posts.length.times do |i|
assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
@@ -649,7 +632,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_preload_polymorph_many_types
- taggings = Tagging.find :all, :include => :taggable, :conditions => ['taggable_type != ?', 'FakeModel']
+ taggings = Tagging.scoped(:includes => :taggable, :where => ['taggable_type != ?', 'FakeModel']).all
assert_no_queries do
taggings.first.taggable.id
taggings[1].taggable.id
@@ -662,13 +645,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_preload_nil_polymorphic_belongs_to
assert_nothing_raised do
- Tagging.find(:all, :include => :taggable, :conditions => ['taggable_type IS NULL'])
+ Tagging.scoped(:includes => :taggable, :where => ['taggable_type IS NULL']).all
end
end
def test_preload_polymorphic_has_many
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all
assert_equal posts.length, posts_with_taggings.length
posts.length.times do |i|
assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
@@ -676,7 +659,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_belongs_to_shared_parent
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 1')
+ comments = Comment.scoped(:includes => :post, :where => 'post_id = 1').all
assert_no_queries do
assert_equal comments.first.post, comments[1].post
end
@@ -684,7 +667,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_include_uses_array_include_after_loaded
david = authors(:david)
- david.categories.class # force load target
+ david.categories.load_target
category = david.categories.first
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 017905e0ac..1d0550afaf 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -74,8 +74,8 @@ class AssociationsTest < ActiveRecord::TestCase
def test_include_with_order_works
- assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
- assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}
+ assert_nothing_raised {Account.scoped(:order => 'id', :includes => :firm).first}
+ assert_nothing_raised {Account.scoped(:order => :id, :includes => :firm).first}
end
def test_bad_collection_keys
@@ -214,6 +214,10 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = developers(:david)
assert_equal david.association(:projects), david.projects.proxy_association
end
+
+ def test_scoped_allows_conditions
+ assert developers(:david).projects.scoped(where: 'foo').where_values.include?('foo')
+ end
end
class OverridingAssociationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index ef01476ae4..1093fedea1 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -244,7 +244,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# DB2 is not case-sensitive
return true if current_adapter?(:DB2Adapter)
- assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
+ assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes
end
def test_hashes_not_mangled
@@ -481,23 +481,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_typecast_attribute_from_select_to_false
- topic = Topic.create(:title => 'Budget')
+ Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
if current_adapter?(:OracleAdapter)
- topic = Topic.find(:first, :select => "topics.*, 0 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 0 as is_test").first
else
- topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 1=2 as is_test").first
end
assert !topic.is_test?
end
def test_typecast_attribute_from_select_to_true
- topic = Topic.create(:title => 'Budget')
+ Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
if current_adapter?(:OracleAdapter)
- topic = Topic.find(:first, :select => "topics.*, 1 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 1 as is_test").first
else
- topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 2=2 as is_test").first
end
assert topic.is_test?
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index d15487ab11..8ef3bfef15 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -87,7 +87,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_save_fails_for_invalid_has_one
- firm = Firm.find(:first)
+ firm = Firm.first
assert firm.valid?
firm.build_account
@@ -99,7 +99,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
- firm = Firm.find(:first)
+ firm = Firm.first
assert firm.valid?
firm.build_unvalidated_account
@@ -155,20 +155,20 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_not_resaved_when_unchanged
- firm = Firm.find(:first, :include => :account)
+ firm = Firm.scoped(:includes => :account).first
firm.name += '-changed'
assert_queries(1) { firm.save! }
- firm = Firm.find(:first)
- firm.account = Account.find(:first)
+ firm = Firm.first
+ firm.account = Account.first
assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
- firm = Firm.find(:first).dup
- firm.account = Account.find(:first)
+ firm = Firm.first.dup
+ firm.account = Account.first
assert_queries(2) { firm.save! }
- firm = Firm.find(:first).dup
- firm.account = Account.find(:first).dup
+ firm = Firm.first.dup
+ firm.account = Account.first.dup
assert_queries(2) { firm.save! }
end
@@ -228,7 +228,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
def test_assignment_before_parent_saved
- client = Client.find(:first)
+ client = Client.first
apple = Firm.new("name" => "Apple")
client.firm = apple
assert_equal apple, client.firm
@@ -342,7 +342,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
def test_build_and_then_save_parent_should_not_reload_target
- client = Client.find(:first)
+ client = Client.first
apple = client.build_firm(:name => "Apple")
client.save!
assert_no_queries { assert_equal apple, client.firm }
@@ -384,7 +384,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
end
def test_invalid_adding_with_validate_false
- firm = Firm.find(:first)
+ firm = Firm.first
client = Client.new
firm.unvalidated_clients_of_firm << client
@@ -397,7 +397,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_valid_adding_with_validate_false
no_of_clients = Client.count
- firm = Firm.find(:first)
+ firm = Firm.first
client = Client.new("name" => "Apple")
assert firm.valid?
@@ -627,7 +627,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice
@pirate.ship.mark_for_destruction
assert @pirate.save
- @pirate.ship.expects(:destroy).never
+ class << @pirate.ship
+ def destroy; raise "Should not be called" end
+ end
assert @pirate.save
end
@@ -672,7 +674,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice
@ship.pirate.mark_for_destruction
assert @ship.save
- @ship.pirate.expects(:destroy).never
+ class << @ship.pirate
+ def destroy; raise "Should not be called" end
+ end
assert @ship.save
end
@@ -868,7 +872,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
before = @pirate.parrots.map { |c| c.mark_for_destruction ; c }
- class << @pirate.parrots
+ class << @pirate.association(:parrots)
def destroy(*args)
super
raise 'Oh noes!'
@@ -1273,7 +1277,7 @@ module AutosaveAssociationOnACollectionAssociationTests
def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
- @pirate.send(@association_name).class # hack to load the target
+ @pirate.send(@association_name).load_target
assert_queries(3) do
@pirate.catchphrase = 'Yarr'
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5fb49d540f..619fb881fa 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -125,20 +125,13 @@ class BasicsTest < ActiveRecord::TestCase
unless current_adapter?(:PostgreSQLAdapter,:OracleAdapter,:SQLServerAdapter)
def test_limit_with_comma
- assert_nothing_raised do
- Topic.limit("1,2").all
- end
+ assert Topic.limit("1,2").all
end
end
def test_limit_without_comma
- assert_nothing_raised do
- assert_equal 1, Topic.limit("1").all.length
- end
-
- assert_nothing_raised do
- assert_equal 1, Topic.limit(1).all.length
- end
+ assert_equal 1, Topic.limit("1").all.length
+ assert_equal 1, Topic.limit(1).all.length
end
def test_invalid_limit
@@ -192,7 +185,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_previously_changed
- topic = Topic.find :first
+ topic = Topic.first
topic.title = '<3<3<3'
assert_equal({}, topic.previous_changes)
@@ -202,7 +195,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_previously_changed_dup
- topic = Topic.find :first
+ topic = Topic.first
topic.title = '<3<3<3'
topic.save!
@@ -228,7 +221,7 @@ class BasicsTest < ActiveRecord::TestCase
)
# For adapters which support microsecond resolution.
- if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter)
+ if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter)
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
@@ -350,13 +343,13 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_load
- topics = Topic.find(:all, :order => 'id')
+ topics = Topic.scoped(:order => 'id').all
assert_equal(4, topics.size)
assert_equal(topics(:first).title, topics.first.title)
end
def test_load_with_condition
- topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
+ topics = Topic.scoped(:where => "author_name = 'Mary'").all
assert_equal(1, topics.size)
assert_equal(topics(:second).title, topics.first.title)
@@ -478,7 +471,7 @@ class BasicsTest < ActiveRecord::TestCase
if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_update_all_with_order_and_limit
- assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
+ assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!')
end
end
@@ -511,7 +504,7 @@ class BasicsTest < ActiveRecord::TestCase
end
# Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter)
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
def test_utc_as_time_zone
Topic.default_timezone = :utc
attributes = { "bonus_time" => "5:42:00AM" }
@@ -753,6 +746,9 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
"written_on(5i)" => "12", "written_on(6i)" => "02"
@@ -859,7 +855,7 @@ class BasicsTest < ActiveRecord::TestCase
end
# Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter)
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
ActiveRecord::Base.time_zone_aware_attributes = true
ActiveRecord::Base.default_timezone = :utc
@@ -880,6 +876,9 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time_with_empty_seconds
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
@@ -935,7 +934,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
- return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
+ return true if current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
attributes = {
"bonus_time" => "5:42:00AM"
@@ -1264,10 +1263,10 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_quoting_arrays
- replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
+ replies = Reply.scoped(:where => [ "id IN (?)", topics(:first).replies.collect(&:id) ]).all
assert_equal topics(:first).replies.size, replies.size
- replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
+ replies = Reply.scoped(:where => [ "id IN (?)", [] ]).all
assert_equal 0, replies.size
end
@@ -1503,7 +1502,7 @@ class BasicsTest < ActiveRecord::TestCase
after_seq = Joke.sequence_name
assert_not_equal before_columns, after_columns
- assert_not_equal before_seq, after_seq unless before_seq.blank? && after_seq.blank?
+ assert_not_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
end
def test_dont_clear_sequence_name_when_setting_explicitly
@@ -1514,7 +1513,7 @@ class BasicsTest < ActiveRecord::TestCase
Joke.table_name = "funny_jokes"
after_seq = Joke.sequence_name
- assert_equal before_seq, after_seq unless before_seq.blank? && after_seq.blank?
+ assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
end
def test_dont_clear_inheritnce_column_when_setting_explicitly
@@ -1564,22 +1563,19 @@ class BasicsTest < ActiveRecord::TestCase
def test_count_with_join
res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
- res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
+ res2 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count
assert_equal res, res2
res3 = nil
assert_nothing_raised do
- res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
- :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
+ res3 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count
end
assert_equal res, res3
res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
res5 = nil
assert_nothing_raised do
- res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
- :joins => "p, comments co",
- :select => "p.id")
+ res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count
end
assert_equal res4, res5
@@ -1587,145 +1583,64 @@ class BasicsTest < ActiveRecord::TestCase
res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
res7 = nil
assert_nothing_raised do
- res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
- :joins => "p, comments co",
- :select => "p.id",
- :distinct => true)
+ res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count(distinct: true)
end
assert_equal res6, res7
end
- def test_scoped_find_conditions
- scoped_developers = Developer.send(:with_scope, :find => { :conditions => 'salary > 90000' }) do
- Developer.find(:all, :conditions => 'id < 5')
- end
- assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
- assert_equal 3, scoped_developers.size
- end
-
def test_no_limit_offset
assert_nothing_raised do
- Developer.find(:all, :offset => 2)
+ Developer.scoped(:offset => 2).all
end
end
- def test_scoped_find_limit_offset
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do
- Developer.find(:all, :order => 'id')
- end
- assert !scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 3, scoped_developers.size
-
- # Test without scoped find conditions to ensure we get the whole thing
- developers = Developer.find(:all, :order => 'id')
- assert_equal Developer.count, developers.size
- end
-
- def test_scoped_find_order
- # Test order in scope
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 1, :order => 'salary DESC' }) do
- Developer.find(:all)
- end
- assert_equal 'Jamis', scoped_developers.first.name
- assert scoped_developers.include?(developers(:jamis))
- # Test scope without order and order in find
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 1 }) do
- Developer.find(:all, :order => 'salary DESC')
- end
- # Test scope order + find order, order has priority
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :order => 'id DESC' }) do
- Developer.find(:all, :order => 'salary ASC')
- end
- assert scoped_developers.include?(developers(:poor_jamis))
- assert ! scoped_developers.include?(developers(:david))
- assert ! scoped_developers.include?(developers(:jamis))
- assert_equal 3, scoped_developers.size
-
- # Test without scoped find conditions to ensure we get the right thing
- assert ! scoped_developers.include?(Developer.find(1))
- assert scoped_developers.include?(Developer.find(11))
- end
-
- def test_scoped_find_limit_offset_including_has_many_association
- topics = Topic.send(:with_scope, :find => {:limit => 1, :offset => 1, :include => :replies}) do
- Topic.find(:all, :order => "topics.id")
- end
- assert_equal 1, topics.size
- assert_equal 2, topics.first.id
- end
-
- def test_scoped_find_order_including_has_many_association
- developers = Developer.send(:with_scope, :find => { :order => 'developers.salary DESC', :include => :projects }) do
- Developer.find(:all)
- end
- assert developers.size >= 2
- (1...developers.size).each do |i|
- assert developers[i-1].salary >= developers[i].salary
- end
- end
-
- def test_scoped_find_with_group_and_having
- developers = Developer.send(:with_scope, :find => { :group => 'developers.salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do
- Developer.find(:all)
- end
- assert_equal 3, developers.size
- end
-
def test_find_last
- last = Developer.find :last
- assert_equal last, Developer.find(:first, :order => 'id desc')
+ last = Developer.last
+ assert_equal last, Developer.scoped(:order => 'id desc').first
end
def test_last
- assert_equal Developer.find(:first, :order => 'id desc'), Developer.last
+ assert_equal Developer.scoped(:order => 'id desc').first, Developer.last
end
def test_all
developers = Developer.all
assert_kind_of Array, developers
- assert_equal Developer.find(:all), developers
+ assert_equal Developer.all, developers
end
def test_all_with_conditions
- assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all
+ assert_equal Developer.scoped(:order => 'id desc').all, Developer.order('id desc').all
end
def test_find_ordered_last
- last = Developer.find :last, :order => 'developers.salary ASC'
- assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last
+ last = Developer.scoped(:order => 'developers.salary ASC').last
+ assert_equal last, Developer.scoped(:order => 'developers.salary ASC').all.last
end
def test_find_reverse_ordered_last
- last = Developer.find :last, :order => 'developers.salary DESC'
- assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last
+ last = Developer.scoped(:order => 'developers.salary DESC').last
+ assert_equal last, Developer.scoped(:order => 'developers.salary DESC').all.last
end
def test_find_multiple_ordered_last
- last = Developer.find :last, :order => 'developers.name, developers.salary DESC'
- assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last
+ last = Developer.scoped(:order => 'developers.name, developers.salary DESC').last
+ assert_equal last, Developer.scoped(:order => 'developers.name, developers.salary DESC').all.last
end
def test_find_keeps_multiple_order_values
- combined = Developer.find(:all, :order => 'developers.name, developers.salary')
- assert_equal combined, Developer.find(:all, :order => ['developers.name', 'developers.salary'])
+ combined = Developer.scoped(:order => 'developers.name, developers.salary').all
+ assert_equal combined, Developer.scoped(:order => ['developers.name', 'developers.salary']).all
end
def test_find_keeps_multiple_group_values
- combined = Developer.find(:all, :group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at')
- assert_equal combined, Developer.find(:all, :group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at'])
+ combined = Developer.scoped(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at').all
+ assert_equal combined, Developer.scoped(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at']).all
end
def test_find_symbol_ordered_last
- last = Developer.find :last, :order => :salary
- assert_equal last, Developer.find(:all, :order => :salary).last
- end
-
- def test_find_scoped_ordered_last
- last_developer = Developer.send(:with_scope, :find => { :order => 'developers.salary ASC' }) do
- Developer.find(:last)
- end
- assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last
+ last = Developer.scoped(:order => :salary).last
+ assert_equal last, Developer.scoped(:order => :salary).all.last
end
def test_abstract_class
@@ -1800,7 +1715,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_to_param_should_return_string
- assert_kind_of String, Client.find(:first).to_param
+ assert_kind_of String, Client.first.to_param
end
def test_inspect_class
@@ -1819,8 +1734,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_inspect_limited_select_instance
- assert_equal %(#<Topic id: 1>), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect
- assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect
+ assert_equal %(#<Topic id: 1>), Topic.scoped(:select => 'id', :where => 'id = 1').first.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.scoped(:select => 'id, title', :where => 'id = 1').first.inspect
end
def test_inspect_class_without_table
@@ -1978,7 +1893,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_attribute_names
- assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
+ assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"],
Company.attribute_names
end
@@ -2006,7 +1921,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_cache_key_format_for_existing_record_with_updated_at
dev = Developer.first
- assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
end
def test_cache_key_format_for_existing_record_with_nil_updated_at
@@ -2070,6 +1985,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil hash['firm_name']
end
+ def test_default_values_are_deeply_dupped
+ company = Company.new
+ company.description << "foo"
+ assert_equal "", Company.new.description
+ end
+
["find_by", "find_by!"].each do |meth|
test "#{meth} delegates to scoped" do
record = stub
@@ -2083,4 +2004,9 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal record, klass.public_send(meth, :foo, :bar)
end
end
+
+ test "scoped can take a values hash" do
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal ['foo'], klass.scoped(select: 'foo').select_values
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 660098b9ad..cdd4b49042 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -27,30 +27,18 @@ class EachTest < ActiveRecord::TestCase
def test_each_should_raise_if_select_is_set_without_id
assert_raise(RuntimeError) do
- Post.find_each(:select => :title, :batch_size => 1) { |post| post }
+ Post.select(:title).find_each(:batch_size => 1) { |post| post }
end
end
def test_each_should_execute_if_id_is_in_select
assert_queries(6) do
- Post.find_each(:select => "id, title, type", :batch_size => 2) do |post|
+ Post.select("id, title, type").find_each(:batch_size => 2) do |post|
assert_kind_of Post, post
end
end
end
- def test_each_should_raise_if_the_order_is_set
- assert_raise(RuntimeError) do
- Post.find_each(:order => "title") { |post| post }
- end
- end
-
- def test_each_should_raise_if_the_limit_is_set
- assert_raise(RuntimeError) do
- Post.find_each(:limit => 1) { |post| post }
- end
- end
-
def test_warn_if_limit_scope_is_set
ActiveRecord::Base.logger.expects(:warn)
Post.limit(1).find_each { |post| post }
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 3652255c38..03aa9fdb27 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -23,6 +23,8 @@ module ActiveRecord
@listener = LogListener.new
@pk = Topic.columns.find { |c| c.primary }
ActiveSupport::Notifications.subscribe('sql.active_record', @listener)
+
+ skip_if_prepared_statement_caching_is_not_supported
end
def teardown
@@ -30,9 +32,6 @@ module ActiveRecord
end
def test_binds_are_logged
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
-
sub = @connection.substitute_at(@pk, 0)
binds = [[@pk, 1]]
sql = "select * from topics where id = #{sub}"
@@ -44,9 +43,6 @@ module ActiveRecord
end
def test_find_one_uses_binds
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
-
Topic.find(1)
binds = [[@pk, 1]]
message = @listener.calls.find { |args| args[4][:binds] == binds }
@@ -54,9 +50,6 @@ module ActiveRecord
end
def test_logs_bind_vars
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
-
pk = Topic.columns.find { |x| x.primary }
payload = {
@@ -86,5 +79,11 @@ module ActiveRecord
logger.sql event
assert_match([[pk.name, 10]].inspect, logger.debugs.first)
end
+
+ private
+
+ def skip_if_prepared_statement_caching_is_not_supported
+ skip('prepared statement caching is not supported') unless @connection.supports_statement_cache?
+ end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 0391319a00..041f8ffb7c 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -49,13 +49,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_get_maximum_of_field_with_include
- assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'")
- end
-
- def test_should_get_maximum_of_field_with_scoped_include
- Account.send :with_scope, :find => { :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'" } do
- assert_equal 55, Account.maximum(:credit_limit)
- end
+ assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(:credit_limit)
end
def test_should_get_minimum_of_field
@@ -63,12 +57,12 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_field
- c = Account.sum(:credit_limit, :group => :firm_id)
+ c = Account.group(:firm_id).sum(:credit_limit)
[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])
+ c = Account.group('firm_id', :credit_limit).count(:all)
[ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
end
@@ -81,32 +75,32 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field
- c = Account.sum(:credit_limit, :group => :firm_id)
+ c = Account.group(:firm_id).sum(:credit_limit)
assert_equal 50, c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
end
def test_should_order_by_grouped_field
- c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
+ c = Account.scoped(:group => :firm_id, :order => "firm_id").sum(:credit_limit)
assert_equal [1, 2, 6, 9], c.keys.compact
end
def test_should_order_by_calculation
- c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
+ c = Account.scoped(:group => :firm_id, :order => "sum_credit_limit desc, firm_id").sum(:credit_limit)
assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
assert_equal [6, 2, 9, 1], c.keys.compact
end
def test_should_limit_calculation
- c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
- :group => :firm_id, :order => "firm_id", :limit => 2)
+ c = Account.scoped(:where => "firm_id IS NOT NULL",
+ :group => :firm_id, :order => "firm_id", :limit => 2).sum(:credit_limit)
assert_equal [1, 2], c.keys.compact
end
def test_should_limit_calculation_with_offset
- c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
- :group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1)
+ c = Account.scoped(:where => "firm_id IS NOT NULL", :group => :firm_id,
+ :order => "firm_id", :limit => 2, :offset => 1).sum(:credit_limit)
assert_equal [2, 6], c.keys.compact
end
@@ -156,16 +150,8 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_having_condition
- c = Account.sum(:credit_limit, :group => :firm_id,
- :having => 'sum(credit_limit) > 50')
- assert_nil c[1]
- assert_equal 105, c[6]
- assert_equal 60, c[2]
- end
-
- def test_should_group_by_summed_field_having_sanitized_condition
- c = Account.sum(:credit_limit, :group => :firm_id,
- :having => ['sum(credit_limit) > ?', 50])
+ c = Account.scoped(:group => :firm_id,
+ :having => 'sum(credit_limit) > 50').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
@@ -179,19 +165,19 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_association
- c = Account.sum(:credit_limit, :group => :firm)
+ c = Account.group(:firm).sum(:credit_limit)
assert_equal 50, c[companies(:first_firm)]
assert_equal 105, c[companies(:rails_core)]
assert_equal 60, c[companies(:first_client)]
end
def test_should_sum_field_with_conditions
- assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6')
+ assert_equal 105, Account.where('firm_id = 6').sum(:credit_limit)
end
def test_should_return_zero_if_sum_conditions_return_nothing
- assert_equal 0, Account.sum(:credit_limit, :conditions => '1 = 2')
- assert_equal 0, companies(:rails_core).companies.sum(:id, :conditions => '1 = 2')
+ assert_equal 0, Account.where('1 = 2').sum(:credit_limit)
+ assert_equal 0, companies(:rails_core).companies.where('1 = 2').sum(:id)
end
def test_sum_should_return_valid_values_for_decimals
@@ -200,24 +186,24 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_with_conditions
- c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
- :group => :firm_id)
+ c = Account.scoped(:where => 'firm_id > 1',
+ :group => :firm_id).sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
end
def test_should_group_by_summed_field_with_conditions_and_having
- c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
- :group => :firm_id,
- :having => 'sum(credit_limit) > 60')
+ c = Account.scoped(:where => 'firm_id > 1',
+ :group => :firm_id,
+ :having => 'sum(credit_limit) > 60').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_nil c[2]
end
def test_should_group_by_fields_with_table_alias
- c = Account.sum(:credit_limit, :group => 'accounts.firm_id')
+ c = Account.group('accounts.firm_id').sum(:credit_limit)
assert_equal 50, c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
@@ -229,14 +215,14 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_calculate_grouped_with_invalid_field
- c = Account.count(:all, :group => 'accounts.firm_id')
+ c = Account.group('accounts.firm_id').count(:all)
assert_equal 1, c[1]
assert_equal 2, c[6]
assert_equal 1, c[2]
end
def test_should_calculate_grouped_association_with_invalid_field
- c = Account.count(:all, :group => :firm)
+ c = Account.group(:firm).count(:all)
assert_equal 1, c[companies(:first_firm)]
assert_equal 2, c[companies(:rails_core)]
assert_equal 1, c[companies(:first_client)]
@@ -255,7 +241,7 @@ class CalculationsTest < ActiveRecord::TestCase
column.expects(:type_cast).with("ABC").returns("ABC")
Account.expects(:columns).at_least_once.returns([column])
- c = Account.count(:all, :group => :firm)
+ c = Account.group(:firm).count(:all)
first_key = c.keys.first
assert_equal Firm, first_key.class
assert_equal 1, c[first_key]
@@ -263,22 +249,14 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_calculate_grouped_association_with_foreign_key_option
Account.belongs_to :another_firm, :class_name => 'Firm', :foreign_key => 'firm_id'
- c = Account.count(:all, :group => :another_firm)
+ c = Account.group(:another_firm).count(:all)
assert_equal 1, c[companies(:first_firm)]
assert_equal 2, c[companies(:rails_core)]
assert_equal 1, c[companies(:first_client)]
end
- def test_should_not_modify_options_when_using_includes
- options = {:conditions => 'companies.id > 1', :include => :firm, :references => :companies}
- options_copy = options.dup
-
- Account.count(:all, options)
- assert_equal options_copy, options
- end
-
def test_should_calculate_grouped_by_function
- c = Company.count(:all, :group => "UPPER(#{QUOTED_TYPE})")
+ c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all)
assert_equal 2, c[nil]
assert_equal 1, c['DEPENDENTFIRM']
assert_equal 4, c['CLIENT']
@@ -286,7 +264,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_calculate_grouped_by_function_with_table_alias
- c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})")
+ c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all)
assert_equal 2, c[nil]
assert_equal 1, c['DEPENDENTFIRM']
assert_equal 4, c['CLIENT']
@@ -306,25 +284,24 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_sum_scoped_field_with_conditions
- assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
+ assert_equal 8, companies(:rails_core).companies.where('id > 7').sum(:id)
end
def test_should_group_by_scoped_field
- c = companies(:rails_core).companies.sum(:id, :group => :name)
+ c = companies(:rails_core).companies.group(:name).sum(:id)
assert_equal 7, c['Leetsoft']
assert_equal 8, c['Jadedpixel']
end
def test_should_group_by_summed_field_through_association_and_having
- c = companies(:rails_core).companies.sum(:id, :group => :name,
- :having => 'sum(id) > 7')
+ c = companies(:rails_core).companies.group(:name).having('sum(id) > 7').sum(:id)
assert_nil c['Leetsoft']
assert_equal 8, c['Jadedpixel']
end
def test_should_count_selected_field_with_include
- assert_equal 6, Account.count(:distinct => true, :include => :firm)
- assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit)
+ assert_equal 6, Account.includes(:firm).count(:distinct => true)
+ assert_equal 4, Account.includes(:firm).select(:credit_limit).count(:distinct => true)
end
def test_should_not_perform_joined_include_by_default
@@ -348,11 +325,11 @@ class CalculationsTest < ActiveRecord::TestCase
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'])
+ assert_equal 1, Account.scoped(:select => "credit_limit").where('credit_limit >= 50').count
end
def test_should_count_manual_select_with_include
- assert_equal 6, Account.count(:select => "DISTINCT accounts.id", :include => :firm)
+ assert_equal 6, Account.scoped(:select => "DISTINCT accounts.id", :includes => :firm).count
end
def test_count_with_column_parameter
@@ -360,16 +337,16 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_count_with_column_and_options_parameter
- assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50 AND firm_id IS NOT NULL")
+ assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id)
end
def test_should_count_field_in_joined_table
- assert_equal 5, Account.count('companies.id', :joins => :firm)
- assert_equal 4, Account.count('companies.id', :joins => :firm, :distinct => true)
+ assert_equal 5, Account.joins(:firm).count('companies.id')
+ assert_equal 4, Account.joins(:firm).count('companies.id', :distinct => true)
end
def test_should_count_field_in_joined_table_with_group_by
- c = Account.count('companies.id', :group => 'accounts.firm_id', :joins => :firm)
+ c = Account.scoped(:group => 'accounts.firm_id', :joins => :firm).count('companies.id')
[1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) }
end
@@ -392,17 +369,17 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_count_with_from_option
- assert_equal Company.count(:all), Company.count(:all, :from => 'companies')
- assert_equal Account.count(:all, :conditions => "credit_limit = 50"),
- Account.count(:all, :from => 'accounts', :conditions => "credit_limit = 50")
- assert_equal Company.count(:type, :conditions => {:type => "Firm"}),
- Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies')
+ assert_equal Company.count(:all), Company.from('companies').count(:all)
+ assert_equal Account.where("credit_limit = 50").count(:all),
+ Account.from('accounts').where("credit_limit = 50").count(:all)
+ assert_equal Company.where(:type => "Firm").count(:type),
+ Company.where(:type => "Firm").from('companies').count(:type)
end
def test_sum_with_from_option
- assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts')
- assert_equal Account.sum(:credit_limit, :conditions => "credit_limit > 50"),
- Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").sum(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').sum(:credit_limit)
end
def test_sum_array_compatibility
@@ -410,33 +387,33 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_average_with_from_option
- assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts')
- assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"),
- Account.average(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").average(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').average(:credit_limit)
end
def test_minimum_with_from_option
- assert_equal Account.minimum(:credit_limit), Account.minimum(:credit_limit, :from => 'accounts')
- assert_equal Account.minimum(:credit_limit, :conditions => "credit_limit > 50"),
- Account.minimum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.minimum(:credit_limit), Account.from('accounts').minimum(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").minimum(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').minimum(:credit_limit)
end
def test_maximum_with_from_option
- assert_equal Account.maximum(:credit_limit), Account.maximum(:credit_limit, :from => 'accounts')
- assert_equal Account.maximum(:credit_limit, :conditions => "credit_limit > 50"),
- Account.maximum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.maximum(:credit_limit), Account.from('accounts').maximum(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").maximum(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit)
end
def test_from_option_with_specified_index
if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
- assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)')
- assert_equal Edge.count(:all, :conditions => 'sink_id < 5'),
- Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5')
+ assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all)
+ assert_equal Edge.where('sink_id < 5').count(:all),
+ Edge.from('edges USE INDEX(unique_edge_index)').where('sink_id < 5').count(:all)
end
end
def test_from_option_with_table_different_than_class
- assert_equal Account.count(:all), Company.count(:all, :from => 'accounts')
+ assert_equal Account.count(:all), Company.from('accounts').count(:all)
end
def test_distinct_is_honored_when_used_with_count_operation_after_group
@@ -488,4 +465,23 @@ class CalculationsTest < ActiveRecord::TestCase
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
assert_equal [7], Company.joins(:contracts).pluck(:developer_id)
end
+
+ def test_pluck_with_selection_clause
+ assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT credit_limit').sort
+ assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT accounts.credit_limit').sort
+ assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT(credit_limit)').sort
+
+ # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless
+ # an alias is provided. Without the alias, the column cannot be found
+ # and properly typecast.
+ assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit')
+ end
+
+ def test_pluck_expects_a_single_selection
+ assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' }
+ end
+
+ def test_plucks_with_ids
+ assert_equal Company.all.map(&:id).sort, Company.ids.sort
+ end
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index ccc57cb876..4111a5f808 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -24,6 +24,58 @@ module ActiveRecord
assert !column.type_cast('off')
assert !column.type_cast('OFF')
end
+
+ def test_type_cast_integer
+ column = Column.new("field", nil, "integer")
+ assert_equal 1, column.type_cast(1)
+ assert_equal 1, column.type_cast('1')
+ assert_equal 1, column.type_cast('1ignore')
+ assert_equal 0, column.type_cast('bad1')
+ assert_equal 0, column.type_cast('bad')
+ assert_equal 1, column.type_cast(1.7)
+ assert_nil column.type_cast(nil)
+ end
+
+ def test_type_cast_non_integer_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_raises(NoMethodError) do
+ column.type_cast([])
+ end
+ assert_raises(NoMethodError) do
+ column.type_cast(true)
+ end
+ assert_raises(NoMethodError) do
+ column.type_cast(false)
+ end
+ end
+
+ def test_type_cast_time
+ column = Column.new("field", nil, "time")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ time_string = Time.now.utc.strftime("%T")
+ assert_equal time_string, column.type_cast(time_string).strftime("%T")
+ end
+
+ def test_type_cast_datetime_and_timestamp
+ [Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column|
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
+ end
+ end
+
+ def test_type_cast_date
+ column = Column.new("field", nil, "date")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ date_string = Time.now.utc.strftime("%F")
+ assert_equal date_string, column.type_cast(date_string).strftime("%F")
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/quoting_test.rb b/activerecord/test/cases/connection_adapters/quoting_test.rb
new file mode 100644
index 0000000000..59dcb96ebc
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/quoting_test.rb
@@ -0,0 +1,13 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ module Quoting
+ class QuotingTest < ActiveRecord::TestCase
+ def test_quoting_classes
+ assert_equal "'Object'", AbstractAdapter.new(nil).quote(Object)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index da93500ce3..8dc9f761c2 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -96,6 +96,30 @@ module ActiveRecord
end
end
+ def test_full_pool_blocks
+ cs = @pool.size.times.map { @pool.checkout }
+ t = Thread.new { @pool.checkout }
+
+ # make sure our thread is in the timeout section
+ Thread.pass until t.status == "sleep"
+
+ connection = cs.first
+ connection.close
+ assert_equal connection, t.join.value
+ end
+
+ def test_removing_releases_latch
+ cs = @pool.size.times.map { @pool.checkout }
+ t = Thread.new { @pool.checkout }
+
+ # make sure our thread is in the timeout section
+ Thread.pass until t.status == "sleep"
+
+ connection = cs.first
+ @pool.remove connection
+ assert_respond_to t.join.value, :execute
+ end
+
def test_reap_and_active
@pool.checkout
@pool.checkout
diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb
index d63ecdbcc5..42ef51ef3e 100644
--- a/activerecord/test/cases/custom_locking_test.rb
+++ b/activerecord/test/cases/custom_locking_test.rb
@@ -9,7 +9,7 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
assert_match 'SHARE MODE', Person.lock('LOCK IN SHARE MODE').to_sql
assert_sql(/LOCK IN SHARE MODE/) do
- Person.find(1, :lock => 'LOCK IN SHARE MODE')
+ Person.scoped(:lock => 'LOCK IN SHARE MODE').find(1)
end
end
end
diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
new file mode 100644
index 0000000000..77a609f49a
--- /dev/null
+++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -0,0 +1,607 @@
+# This file should be deleted when active_record_deprecated_finders is removed as
+# a dependency.
+#
+# It is kept for now as there is some fairly nuanced behaviour in the dynamic
+# finders so it is useful to keep this around to guard against regressions if
+# we need to change the code.
+
+require 'cases/helper'
+require 'models/topic'
+require 'models/reply'
+require 'models/customer'
+require 'models/post'
+require 'models/company'
+require 'models/author'
+require 'models/category'
+require 'models/comment'
+require 'models/person'
+require 'models/reader'
+
+class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
+ fixtures :topics, :customers, :companies, :accounts, :posts, :categories, :categories_posts, :authors, :people, :comments, :readers
+
+ def setup
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
+
+ def test_find_all_by_one_attribute
+ topics = Topic.find_all_by_content("Have a nice day")
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
+ def test_find_all_by_one_attribute_which_is_a_symbol
+ topics = Topic.find_all_by_content("Have a nice day".to_sym)
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
+ def test_find_all_by_one_attribute_that_is_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance(balance)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_that_are_both_aggregates
+ balance = customers(:david).balance
+ address = customers(:david).address
+ assert_kind_of Money, balance
+ assert_kind_of Address, address
+ found_customers = Customer.find_all_by_balance_and_address(balance, address)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_with_one_being_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_one_attribute_with_options
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
+ assert_equal topics(:first), topics.last
+
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id")
+ assert_equal topics(:first), topics.first
+ end
+
+ def test_find_all_by_array_attribute
+ assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
+ end
+
+ def test_find_all_by_boolean_attribute
+ topics = Topic.find_all_by_approved(false)
+ assert_equal 1, topics.size
+ assert topics.include?(topics(:first))
+
+ topics = Topic.find_all_by_approved(true)
+ assert_equal 3, topics.size
+ assert topics.include?(topics(:second))
+ end
+
+ def test_find_all_by_nil_and_not_nil_attributes
+ topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
+ assert_equal 1, topics.size
+ assert_equal "Mary", topics[0].author_name
+ end
+
+ def test_find_or_create_from_one_attribute
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name("38signals")
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name("38signals")
+ assert sig38.persisted?
+ end
+
+ def test_find_or_create_from_two_attributes
+ number_of_topics = Topic.count
+ another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
+ assert_equal number_of_topics + 1, Topic.count
+ assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
+ assert another.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_bang
+ number_of_companies = Company.count
+ assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") }
+ assert_equal number_of_companies, Company.count
+ sig38 = Company.find_or_create_by_name!("38signals")
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name!("38signals")
+ assert sig38.persisted?
+ end
+
+ def test_find_or_create_from_two_attributes_bang
+ number_of_companies = Company.count
+ assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) }
+ assert_equal number_of_companies, Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17)
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17)
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ end
+
+ def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
+ assert created_customer.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
+ def test_find_or_create_from_two_attributes_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance(Money.new(123))
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
+ assert created_customer.persisted?
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute_and_hash
+ number_of_customers = Customer.count
+ balance = Money.new(123)
+ name = "Elizabeth"
+ created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert created_customer.persisted?
+ assert_equal balance, created_customer.balance
+ assert_equal name, created_customer.name
+ end
+
+ def test_find_or_initialize_from_one_attribute
+ sig38 = Company.find_or_initialize_by_name("38signals")
+ assert_equal "38signals", sig38.name
+ assert !sig38.persisted?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute
+ new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
+ assert_equal 123, new_customer.balance.amount
+ assert !new_customer.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
+ c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block_on_first_call
+ class << Company
+ undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name)
+ end
+ c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_two_attributes
+ another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
+ assert_equal "Another topic", another.title
+ assert_equal "John", another.author_name
+ assert !another.persisted?
+ end
+
+ def test_find_or_initialize_from_two_attributes_but_passing_only_one
+ assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
+ new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal 123, new_customer.balance.amount
+ assert_equal "Elizabeth", new_customer.name
+ assert !new_customer.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_and_hash
+ sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ assert !sig38.persisted?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_hash
+ balance = Money.new(123)
+ name = "Elizabeth"
+ new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
+ assert_equal balance, new_customer.balance
+ assert_equal name, new_customer.name
+ assert !new_customer.persisted?
+ end
+
+ def test_find_last_by_one_attribute
+ assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
+ assert_nil Topic.find_last_by_title("A title with no matches")
+ end
+
+ def test_find_last_by_invalid_method_syntax
+ assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") }
+ assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") }
+ end
+
+ def test_find_last_by_one_attribute_with_several_options
+ assert_equal accounts(:signals37), Account.order('id DESC').where('id != ?', 3).find_last_by_credit_limit(50)
+ end
+
+ def test_find_last_by_one_missing_attribute
+ assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") }
+ end
+
+ def test_find_last_by_two_attributes
+ topic = Topic.last
+ assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name)
+ assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
+ end
+
+ def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(2).limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(3)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_all_by_nil_attribute
+ topics = Topic.find_all_by_last_read nil
+ assert_equal 3, topics.size
+ assert topics.collect(&:last_read).all?(&:nil?)
+ end
+
+ def test_forwarding_to_dynamic_finders
+ welcome = Post.find(1)
+ assert_equal 4, Category.find_all_by_type('SpecialCategory').size
+ assert_equal 0, welcome.categories.find_all_by_type('SpecialCategory').size
+ assert_equal 2, welcome.categories.find_all_by_type('Category').size
+ end
+
+ def test_dynamic_find_all_should_respect_association_order
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").all
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit
+ assert_equal 1, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'").all.length
+ assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
+ end
+
+ def test_dynamic_find_all_limit_should_override_association_limit
+ assert_equal 2, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'", :limit => 9_000).all.length
+ assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
+ end
+
+ def test_dynamic_find_last_without_specified_order
+ assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client')
+ end
+
+ def test_dynamic_find_or_create_from_two_attributes_using_an_association
+ author = authors(:david)
+ number_of_posts = Post.count
+ another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert_equal number_of_posts + 1, Post.count
+ assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert another.persisted?
+ end
+
+ def test_dynamic_find_all_should_respect_association_order_for_through
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").all
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit_for_through
+ assert_equal 1, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'").all.length
+ assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
+ end
+
+ def test_dynamic_find_all_order_should_override_association_limit_for_through
+ assert_equal 4, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'", :limit => 9_000).all.length
+ assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
+ end
+
+ def test_find_all_include_over_the_same_table_for_through
+ assert_equal 2, people(:michael).posts.scoped(:includes => :people).all.length
+ end
+
+ def test_find_or_create_by_resets_cached_counters
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find_by_post_id(post.id)
+
+ person.readers.find_or_create_by_post_id(post.id)
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_find_or_initialize
+ the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
+ assert_equal companies(:first_firm).id, the_client.firm_id
+ assert_equal "Yet another client", the_client.name
+ assert !the_client.persisted?
+ end
+
+ def test_find_or_create_updates_size
+ number_of_clients = companies(:first_firm).clients.size
+ the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ end
+
+ def test_find_or_initialize_updates_collection_size
+ number_of_clients = companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
+ end
+
+ def test_find_or_initialize_returns_the_instantiated_object
+ client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal client, companies(:first_firm).clients_of_firm[-1]
+ end
+
+ def test_find_or_initialize_only_instantiates_a_single_object
+ number_of_clients = Client.count
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save!
+ companies(:first_firm).save!
+ assert_equal number_of_clients+1, Client.count
+ end
+
+ def test_find_or_create_with_hash
+ post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_with_one_attribute_followed_by_hash
+ post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block
+ post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert post.persisted?
+ end
+
+ def test_forwarding_to_dynamic_finders_2
+ welcome = Post.find(1)
+ assert_equal 4, Comment.find_all_by_type('Comment').size
+ assert_equal 2, welcome.comments.find_all_by_type('Comment').size
+ end
+
+ def test_dynamic_find_all_by_attributes
+ authors = Author.scoped
+
+ davids = authors.find_all_by_name('David')
+ assert_kind_of Array, davids
+ assert_equal [authors(:david)], davids
+ end
+
+ def test_dynamic_find_or_initialize_by_attributes
+ authors = Author.scoped
+
+ lifo = authors.find_or_initialize_by_name('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert !lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David')
+ end
+
+ def test_dynamic_find_or_create_by_attributes
+ authors = Author.scoped
+
+ lifo = authors.find_or_create_by_name('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David')
+ end
+
+ def test_dynamic_find_or_create_by_attributes_bang
+ authors = Author.scoped
+
+ assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') }
+
+ lifo = authors.find_or_create_by_name!('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David')
+ end
+
+ def test_finder_block
+ t = Topic.first
+ found = nil
+ Topic.find_by_id(t.id) { |f| found = f }
+ assert_equal t, found
+ end
+
+ def test_finder_block_nothing_found
+ bad_id = Topic.maximum(:id) + 1
+ assert_nil Topic.find_by_id(bad_id) { |f| raise }
+ end
+
+ def test_find_returns_block_value
+ t = Topic.first
+ x = Topic.find_by_id(t.id) { |f| "hi mom!" }
+ assert_equal "hi mom!", x
+ end
+
+ def test_dynamic_finder_with_invalid_params
+ assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
+ end
+
+ def test_find_by_one_attribute_with_order_option
+ assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
+ assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
+ end
+
+ def test_dynamic_find_by_attributes_should_yield_found_object
+ david = authors(:david)
+ yielded_value = nil
+ Author.find_by_name(david.name) do |author|
+ yielded_value = author
+ end
+ assert_equal david, yielded_value
+ end
+end
+
+class DynamicScopeTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def setup
+ @test_klass = Class.new(Post) do
+ def self.name; "Post"; end
+ end
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
+
+ def test_dynamic_scope
+ assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1)
+ assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.scoped(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first
+ end
+
+ def test_dynamic_scope_should_create_methods_after_hitting_method_missing
+ assert_blank @test_klass.methods.grep(/scoped_by_type/)
+ @test_klass.scoped_by_type(nil)
+ assert_present @test_klass.methods.grep(/scoped_by_type/)
+ end
+
+ def test_dynamic_scope_with_less_number_of_arguments
+ assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) }
+ end
+end
+
+class DynamicScopeMatchTest < ActiveRecord::TestCase
+ def test_scoped_by_no_match
+ assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all")
+ end
+
+ def test_scoped_by
+ match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location")
+ assert_not_nil match
+ assert_equal %w(age sex location), match.attribute_names
+ end
+end
diff --git a/activerecord/test/cases/deprecated_finder_test.rb b/activerecord/test/cases/deprecated_finder_test.rb
deleted file mode 100644
index 2afc91b769..0000000000
--- a/activerecord/test/cases/deprecated_finder_test.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require "cases/helper"
-require 'models/entrant'
-
-class DeprecatedFinderTest < ActiveRecord::TestCase
- fixtures :entrants
-
- def test_deprecated_find_all_was_removed
- assert_raise(NoMethodError) { Entrant.find_all }
- end
-
- def test_deprecated_find_first_was_removed
- assert_raise(NoMethodError) { Entrant.find_first }
- end
-
- def test_deprecated_find_on_conditions_was_removed
- assert_raise(NoMethodError) { Entrant.find_on_conditions }
- end
-
- def test_count
- assert_equal(0, Entrant.count(:conditions => "id > 3"))
- assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
- assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
- end
-
- def test_count_by_sql
- assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
- assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
- assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
- end
-end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 54e0b40b4f..2650040a80 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -288,7 +288,7 @@ class DirtyTest < ActiveRecord::TestCase
with_partial_updates Pirate, false do
assert_queries(2) { 2.times { pirate.save! } }
- Pirate.update_all({ :updated_on => old_updated_on }, :id => pirate.id)
+ Pirate.where(id: pirate.id).update_all(:updated_on => old_updated_on)
end
with_partial_updates Pirate, true do
@@ -306,7 +306,7 @@ class DirtyTest < ActiveRecord::TestCase
with_partial_updates Person, false do
assert_queries(2) { 2.times { person.save! } }
- Person.update_all({ :first_name => 'baz' }, :id => person.id)
+ Person.where(id: person.id).update_all(:first_name => 'baz')
end
with_partial_updates Person, true do
diff --git a/activerecord/test/cases/dynamic_finder_match_test.rb b/activerecord/test/cases/dynamic_finder_match_test.rb
deleted file mode 100644
index db619faa83..0000000000
--- a/activerecord/test/cases/dynamic_finder_match_test.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- class DynamicFinderMatchTest < ActiveRecord::TestCase
- def test_find_or_create_by
- match = DynamicFinderMatch.match("find_or_create_by_age_and_sex_and_location")
- assert_not_nil match
- assert !match.finder?
- assert match.instantiator?
- assert_equal :first, match.finder
- assert_equal :create, match.instantiator
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_or_initialize_by
- match = DynamicFinderMatch.match("find_or_initialize_by_age_and_sex_and_location")
- assert_not_nil match
- assert !match.finder?
- assert match.instantiator?
- assert_equal :first, match.finder
- assert_equal :new, match.instantiator
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_no_match
- assert_nil DynamicFinderMatch.match("not_a_finder")
- end
-
- def find_by_bang
- match = DynamicFinderMatch.match("find_by_age_and_sex_and_location!")
- assert_not_nil match
- assert match.finder?
- assert match.bang?
- assert_equal :first, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_by
- match = DynamicFinderMatch.match("find_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.finder?
- assert_equal :first, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_by_with_symbol
- m = DynamicFinderMatch.match(:find_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_all_by_with_symbol
- m = DynamicFinderMatch.match(:find_all_by_foo)
- assert_equal :all, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_all_by
- match = DynamicFinderMatch.match("find_all_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.finder?
- assert_equal :all, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_last_by
- m = DynamicFinderMatch.match(:find_last_by_foo)
- assert_equal :last, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_by!
- m = DynamicFinderMatch.match(:find_by_foo!)
- assert_equal :first, m.finder
- assert m.bang?, 'should be banging'
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_or_create
- m = DynamicFinderMatch.match(:find_or_create_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :create, m.instantiator
- end
-
- def test_find_or_create!
- m = DynamicFinderMatch.match(:find_or_create_by_foo!)
- assert_equal :first, m.finder
- assert m.bang?, 'should be banging'
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :create, m.instantiator
- end
-
- def test_find_or_initialize
- m = DynamicFinderMatch.match(:find_or_initialize_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :new, m.instantiator
- end
-
- def test_garbage
- assert !DynamicFinderMatch.match(:fooo), 'should be false'
- assert !DynamicFinderMatch.match(:find_by), 'should be false'
- end
- end
-end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 83c9b6e107..cb7781f8e7 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -28,7 +28,9 @@ if ActiveRecord::Base.connection.supports_explain?
original = base.logger
base.logger = nil
- base.logger.expects(:warn).never
+ class << base.logger
+ def warn; raise "Should not be called" end
+ end
with_threshold(0) do
car = Car.where(:name => 'honda').first
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 96c8eb6417..f7ecab28ce 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -32,6 +32,7 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.exists?(:author_name => "Mary", :approved => true)
assert Topic.exists?(["parent_id = ?", 1])
assert !Topic.exists?(45)
+ assert !Topic.exists?(Topic.new)
begin
assert !Topic.exists?("foo")
@@ -82,12 +83,6 @@ class FinderTest < ActiveRecord::TestCase
Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
- def test_exists_with_scoped_include
- Developer.send(:with_scope, :find => { :include => :projects, :order => "projects.name" }) do
- assert Developer.exists?
- end
- end
-
def test_exists_does_not_instantiate_records
Developer.expects(:instantiate).never
Developer.exists?
@@ -104,14 +99,14 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_by_ids_with_limit_and_offset
- assert_equal 2, Entrant.find([1,3,2], :limit => 2).size
- assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size
+ assert_equal 2, Entrant.scoped(:limit => 2).find([1,3,2]).size
+ assert_equal 1, Entrant.scoped(:limit => 3, :offset => 2).find([1,3,2]).size
# Also test an edge case: If you have 11 results, and you set a
# limit of 3 and offset of 9, then you should find that there
# will be only 2 results, regardless of the limit.
- devs = Developer.find :all
- last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9
+ devs = Developer.all
+ last_devs = Developer.scoped(:limit => 3, :offset => 9).find devs.map(&:id)
assert_equal 2, last_devs.size
end
@@ -119,58 +114,12 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [], Topic.find([])
end
- def test_find_by_ids_missing_one
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
- end
-
- def test_find_all_with_limit
- assert_equal(2, Entrant.find(:all, :limit => 2).size)
- assert_equal(0, Entrant.find(:all, :limit => 0).size)
- end
-
- def test_find_all_with_prepared_limit_and_offset
- entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)
-
- assert_equal(2, entrants.size)
- assert_equal(entrants(:second).name, entrants.first.name)
-
- assert_equal 3, Entrant.count
- entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
- assert_equal(1, entrants.size)
- assert_equal(entrants(:third).name, entrants.first.name)
- end
-
- 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
- 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],[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
-
-
- def test_find_with_group
- developers = Developer.find(:all, :group => "salary", :select => "salary")
- assert_equal 4, developers.size
- assert_equal 4, developers.map(&:salary).uniq.size
- end
-
- def test_find_with_group_and_having
- developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary")
- assert_equal 3, developers.size
- assert_equal 3, developers.map(&:salary).uniq.size
- assert developers.all? { |developer| developer.salary > 10000 }
+ def test_find_doesnt_have_implicit_ordering
+ assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
end
- def test_find_with_group_and_sanitized_having
- developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary")
- assert_equal 3, developers.size
- assert_equal 3, developers.map(&:salary).uniq.size
- assert developers.all? { |developer| developer.salary > 10000 }
+ def test_find_by_ids_missing_one
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
end
def test_find_with_group_and_sanitized_having_method
@@ -199,14 +148,24 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [Account], accounts.collect(&:class).uniq
end
- def test_find_first
- first = Topic.find(:first, :conditions => "title = 'The First Topic'")
- assert_equal(topics(:first).title, first.title)
+ def test_take
+ assert_equal topics(:first), Topic.take
+ end
+
+ def test_take_failing
+ assert_nil Topic.where("title = 'This title does not exist'").take
end
- def test_find_first_failing
- first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
- assert_nil(first)
+ def test_take_bang_present
+ assert_nothing_raised do
+ assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").take!
+ end
+ end
+
+ def test_take_bang_missing
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.where("title = 'This title does not exist'").take!
+ end
end
def test_first
@@ -229,6 +188,12 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_first_have_primary_key_order_by_default
+ expected = topics(:first)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.first
+ end
+
def test_model_class_responds_to_first_bang
assert Topic.first!
Topic.delete_all
@@ -257,7 +222,8 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_first_and_last_with_integer_should_use_sql_limit
+ def test_take_and_first_and_last_with_integer_should_use_sql_limit
+ assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
end
@@ -278,7 +244,8 @@ class FinderTest < ActiveRecord::TestCase
assert_no_match(/LIMIT/, query.first)
end
- def test_first_and_last_with_integer_should_return_an_array
+ def test_take_and_first_and_last_with_integer_should_return_an_array
+ assert_kind_of Array, Topic.take(5)
assert_kind_of Array, Topic.first(5)
assert_kind_of Array, Topic.last(5)
end
@@ -292,7 +259,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_only_some_columns
- topic = Topic.find(1, :select => "author_name")
+ topic = Topic.scoped(:select => "author_name").find(1)
assert_raise(ActiveModel::MissingAttributeError) {topic.title}
assert_nil topic.read_attribute("title")
assert_equal "David", topic.author_name
@@ -302,36 +269,24 @@ class FinderTest < ActiveRecord::TestCase
assert_respond_to topic, "author_name"
end
- def test_find_on_blank_conditions
- [nil, " ", [], {}].each do |blank|
- assert_nothing_raised { Topic.find(:first, :conditions => blank) }
- end
- end
-
- def test_find_on_blank_bind_conditions
- [ [""], ["",{}] ].each do |blank|
- assert_nothing_raised { Topic.find(:first, :conditions => blank) }
- end
- end
-
def test_find_on_array_conditions
- assert Topic.find(1, :conditions => ["approved = ?", false])
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
+ assert Topic.scoped(:where => ["approved = ?", false]).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => ["approved = ?", true]).find(1) }
end
def test_find_on_hash_conditions
- assert Topic.find(1, :conditions => { :approved => false })
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) }
+ assert Topic.scoped(:where => { :approved => false }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :approved => true }).find(1) }
end
def test_find_on_hash_conditions_with_explicit_table_name
- assert Topic.find(1, :conditions => { 'topics.approved' => false })
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
+ assert Topic.scoped(:where => { 'topics.approved' => false }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { 'topics.approved' => true }).find(1) }
end
def test_find_on_hash_conditions_with_hashed_table_name
- assert Topic.find(1, :conditions => {:topics => { :approved => false }})
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) }
+ assert Topic.scoped(:where => {:topics => { :approved => false }}).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => {:topics => { :approved => true }}).find(1) }
end
def test_find_with_hash_conditions_on_joined_table
@@ -341,89 +296,89 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_hash_conditions_on_joined_table_and_with_range
- firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
+ firms = DependentFirm.scoped :joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
assert_equal 1, firms.size
assert_equal companies(:rails_core), firms.first
end
def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
david = customers(:david)
- assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })
+ assert Customer.scoped(:where => { 'customers.name' => david.name, :address => david.address }).find(david.id)
assert_raise(ActiveRecord::RecordNotFound) {
- Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address })
+ Customer.scoped(:where => { 'customers.name' => david.name + "1", :address => david.address }).find(david.id)
}
end
def test_find_on_association_proxy_conditions
- assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
+ assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort
end
def test_find_on_hash_conditions_with_range
- assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) }
+ assert_equal [1,2], Topic.scoped(:where => { :id => 1..2 }).all.map(&:id).sort
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2..3 }).find(1) }
end
def test_find_on_hash_conditions_with_end_exclusive_range
- assert_equal [1,2,3], Topic.find(:all, :conditions => { :id => 1..3 }).map(&:id).sort
- assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1...3 }).map(&:id).sort
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(3, :conditions => { :id => 2...3 }) }
+ assert_equal [1,2,3], Topic.scoped(:where => { :id => 1..3 }).all.map(&:id).sort
+ assert_equal [1,2], Topic.scoped(:where => { :id => 1...3 }).all.map(&:id).sort
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2...3 }).find(3) }
end
def test_find_on_hash_conditions_with_multiple_ranges
- assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort
- assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort
+ assert_equal [1,2,3], Comment.scoped(:where => { :id => 1..3, :post_id => 1..2 }).all.map(&:id).sort
+ assert_equal [1], Comment.scoped(:where => { :id => 1..1, :post_id => 1..10 }).all.map(&:id).sort
end
def test_find_on_hash_conditions_with_array_of_integers_and_ranges
- assert_equal [1,2,3,5,6,7,8,9], Comment.find(:all, :conditions => {:id => [1..2, 3, 5, 6..8, 9]}).map(&:id).sort
+ assert_equal [1,2,3,5,6,7,8,9], Comment.scoped(:where => {:id => [1..2, 3, 5, 6..8, 9]}).all.map(&:id).sort
end
def test_find_on_multiple_hash_conditions
- assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false })
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) }
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
+ assert Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }).find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
end
def test_condition_interpolation
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
+ assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on
end
def test_condition_array_interpolation
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
+ assert_kind_of Firm, Company.scoped(:where => ["name = '%s'", "37signals"]).first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on
end
def test_condition_hash_interpolation
- assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"})
- assert_nil Company.find(:first, :conditions => { :name => "37signals!"})
- assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on
+ assert_kind_of Firm, Company.scoped(:where => { :name => "37signals"}).first
+ assert_nil Company.scoped(:where => { :name => "37signals!"}).first
+ assert_kind_of Time, Topic.scoped(:where => {:id => 1}).first.written_on
end
def test_hash_condition_find_malformed
assert_raise(ActiveRecord::StatementInvalid) {
- Company.find(:first, :conditions => { :id => 2, :dhh => true })
+ Company.scoped(:where => { :id => 2, :dhh => true }).first
}
end
def test_hash_condition_find_with_escaped_characters
Company.create("name" => "Ain't noth'n like' \#stuff")
- assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })
+ assert Company.scoped(:where => { :name => "Ain't noth'n like' \#stuff" }).first
end
def test_hash_condition_find_with_array
- p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc')
+ p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all
+ assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2] }, :order => 'id asc').all
+ assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2.id] }, :order => 'id asc').all
end
def test_hash_condition_find_with_nil
- topic = Topic.find(:first, :conditions => { :last_read => nil } )
+ topic = Topic.scoped(:where => { :last_read => nil } ).first
assert_not_nil topic
assert_nil topic.last_read
end
@@ -431,42 +386,42 @@ class FinderTest < ActiveRecord::TestCase
def test_hash_condition_find_with_aggregate_having_one_mapping
balance = customers(:david).balance
assert_kind_of Money, balance
- found_customer = Customer.find(:first, :conditions => {:balance => balance})
+ found_customer = Customer.scoped(:where => {:balance => balance}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
gps_location = customers(:david).gps_location
assert_kind_of GpsLocation, gps_location
- found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location})
+ found_customer = Customer.scoped(:where => {:gps_location => gps_location}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value
balance = customers(:david).balance
assert_kind_of Money, balance
- found_customer = Customer.find(:first, :conditions => {:balance => balance.amount})
+ found_customer = Customer.scoped(:where => {:balance => balance.amount}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value
gps_location = customers(:david).gps_location
assert_kind_of GpsLocation, gps_location
- found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location.gps_location})
+ found_customer = Customer.scoped(:where => {:gps_location => gps_location.gps_location}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_having_three_mappings
address = customers(:david).address
assert_kind_of Address, address
- found_customer = Customer.find(:first, :conditions => {:address => address})
+ found_customer = Customer.scoped(:where => {:address => address}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not
address = customers(:david).address
assert_kind_of Address, address
- found_customer = Customer.find(:first, :conditions => {:address => address, :name => customers(:david).name})
+ found_customer = Customer.scoped(:where => {:address => address, :name => customers(:david).name}).first
assert_equal customers(:david), found_customer
end
@@ -474,7 +429,7 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getutc])
+ assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getutc]).first
end
end
end
@@ -483,7 +438,7 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getutc})
+ assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getutc}).first
end
end
end
@@ -492,7 +447,7 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getlocal])
+ assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getlocal]).first
end
end
end
@@ -501,32 +456,32 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getlocal})
+ assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getlocal}).first
end
end
end
def test_bind_variables
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
- assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
- assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
+ assert_kind_of Firm, Company.scoped(:where => ["name = ?", "37signals"]).first
+ assert_nil Company.scoped(:where => ["name = ?", "37signals!"]).first
+ assert_nil Company.scoped(:where => ["name = ?", "37signals!' OR 1=1"]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = ?", 1]).first.written_on
assert_raise(ActiveRecord::PreparedStatementInvalid) {
- Company.find(:first, :conditions => ["id=? AND name = ?", 2])
+ Company.scoped(:where => ["id=? AND name = ?", 2]).first
}
assert_raise(ActiveRecord::PreparedStatementInvalid) {
- Company.find(:first, :conditions => ["id=?", 2, 3, 4])
+ Company.scoped(:where => ["id=?", 2, 3, 4]).first
}
end
def test_bind_variables_with_quotes
Company.create("name" => "37signals' go'es agains")
- assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
+ assert Company.scoped(:where => ["name = ?", "37signals' go'es agains"]).first
end
def test_named_bind_variables_with_quotes
Company.create("name" => "37signals' go'es agains")
- assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
+ assert Company.scoped(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first
end
def test_bind_arity
@@ -544,10 +499,10 @@ class FinderTest < ActiveRecord::TestCase
assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
- assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
- assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
+ assert_kind_of Firm, Company.scoped(:where => ["name = :name", { :name => "37signals" }]).first
+ assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!" }]).first
+ assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!' OR 1=1" }]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = :id", { :id => 1 }]).first.written_on
end
class SimpleEnumerable
@@ -618,12 +573,6 @@ class FinderTest < ActiveRecord::TestCase
assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
end
- def test_count
- assert_equal(0, Entrant.count(:conditions => "id > 3"))
- assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
- assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
- end
-
def test_count_by_sql
assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
@@ -640,13 +589,8 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
end
- def test_find_by_one_attribute_with_order_option
- assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
- assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
- end
-
def test_find_by_one_attribute_with_conditions
- assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
+ assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
end
def test_find_by_one_attribute_that_is_an_aggregate
@@ -686,12 +630,12 @@ class FinderTest < ActiveRecord::TestCase
def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
# ensure this test can run independently of order
class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
- a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
- assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached
+ a = Account.where('firm_id = ?', 6).find_by_credit_limit(50)
+ assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached
end
def test_find_by_one_attribute_with_several_options
- assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
+ assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
end
def test_find_by_one_missing_attribute
@@ -714,367 +658,26 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
end
- def test_find_last_by_one_attribute
- assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
- assert_nil Topic.find_last_by_title("A title with no matches")
- end
-
- def test_find_last_by_invalid_method_syntax
- assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") }
- assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") }
- end
-
- def test_find_last_by_one_attribute_with_several_options
- assert_equal accounts(:signals37), Account.find_last_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
- end
-
- def test_find_last_by_one_missing_attribute
- assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") }
- end
-
- def test_find_last_by_two_attributes
- topic = Topic.last
- assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name)
- assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
- end
-
- def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
- scope = Topic.limit(2)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
- scope = Topic.offset(2).limit(2)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
- scope = Topic.offset(3)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_all_by_one_attribute
- topics = Topic.find_all_by_content("Have a nice day")
- assert_equal 2, topics.size
- assert topics.include?(topics(:first))
-
- assert_equal [], Topic.find_all_by_title("The First Topic!!")
- end
-
- def test_find_all_by_one_attribute_which_is_a_symbol
- topics = Topic.find_all_by_content("Have a nice day".to_sym)
- assert_equal 2, topics.size
- assert topics.include?(topics(:first))
-
- assert_equal [], Topic.find_all_by_title("The First Topic!!")
- end
-
- def test_find_all_by_one_attribute_that_is_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance(balance)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_that_are_both_aggregates
- balance = customers(:david).balance
- address = customers(:david).address
- assert_kind_of Money, balance
- assert_kind_of Address, address
- found_customers = Customer.find_all_by_balance_and_address(balance, address)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_with_one_being_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_one_attribute_with_options
- topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
- assert_equal topics(:first), topics.last
-
- topics = Topic.find_all_by_content("Have a nice day", :order => "id")
- assert_equal topics(:first), topics.first
- end
-
- def test_find_all_by_array_attribute
- assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
- end
-
- def test_find_all_by_boolean_attribute
- topics = Topic.find_all_by_approved(false)
- assert_equal 1, topics.size
- assert topics.include?(topics(:first))
-
- topics = Topic.find_all_by_approved(true)
- assert_equal 3, topics.size
- assert topics.include?(topics(:second))
- end
-
def test_find_by_nil_attribute
topic = Topic.find_by_last_read nil
assert_not_nil topic
assert_nil topic.last_read
end
- def test_find_all_by_nil_attribute
- topics = Topic.find_all_by_last_read nil
- assert_equal 3, topics.size
- assert topics.collect(&:last_read).all?(&:nil?)
- end
-
def test_find_by_nil_and_not_nil_attributes
topic = Topic.find_by_last_read_and_author_name nil, "Mary"
assert_equal "Mary", topic.author_name
end
- def test_find_all_by_nil_and_not_nil_attributes
- topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
- assert_equal 1, topics.size
- assert_equal "Mary", topics[0].author_name
- end
-
- def test_find_or_create_from_one_attribute
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name("38signals")
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name("38signals")
- assert sig38.persisted?
- end
-
- def test_find_or_create_from_two_attributes
- number_of_topics = Topic.count
- another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
- assert_equal number_of_topics + 1, Topic.count
- assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
- assert another.persisted?
- end
-
- def test_find_or_create_from_one_attribute_bang
- number_of_companies = Company.count
- assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") }
- assert_equal number_of_companies, Company.count
- sig38 = Company.find_or_create_by_name!("38signals")
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name!("38signals")
- assert sig38.persisted?
- end
-
- def test_find_or_create_from_two_attributes_bang
- number_of_companies = Company.count
- assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) }
- assert_equal number_of_companies, Company.count
- sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17)
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17)
- assert sig38.persisted?
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- end
-
- def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_attribute_and_hash
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert sig38.persisted?
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- end
-
- def test_find_or_create_from_two_attributes_and_hash
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert sig38.persisted?
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- end
-
- def test_find_or_create_from_one_aggregate_attribute
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance(Money.new(123))
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_aggregate_attribute_and_hash
- number_of_customers = Customer.count
- balance = Money.new(123)
- name = "Elizabeth"
- created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert created_customer.persisted?
- assert_equal balance, created_customer.balance
- assert_equal name, created_customer.name
- end
-
- def test_find_or_initialize_from_one_attribute
- sig38 = Company.find_or_initialize_by_name("38signals")
- assert_equal "38signals", sig38.name
- assert !sig38.persisted?
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute
- new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
- assert_equal 123, new_customer.balance.amount
- assert !new_customer.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
- c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
- assert_equal "Fortune 1000", c.name
- assert_not_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
- c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
- assert_equal "Fortune 1000", c.name
- assert_not_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
- c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
- c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
- c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
- c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
- c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_should_set_protected_attributes_if_given_as_block
- c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_create_should_work_with_block_on_first_call
- class << Company
- undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name)
- end
- c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_two_attributes
- another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
- assert_equal "Another topic", another.title
- assert_equal "John", another.author_name
- assert !another.persisted?
- end
-
- def test_find_or_initialize_from_two_attributes_but_passing_only_one
- assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
- new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal 123, new_customer.balance.amount
- assert_equal "Elizabeth", new_customer.name
- assert !new_customer.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_and_hash
- sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- assert !sig38.persisted?
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute_and_hash
- balance = Money.new(123)
- name = "Elizabeth"
- new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
- assert_equal balance, new_customer.balance
- assert_equal name, new_customer.name
- assert !new_customer.persisted?
- end
-
def test_find_with_bad_sql
assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
end
- def test_find_with_invalid_params
- assert_raise(ArgumentError) { Topic.find :first, :join => "It should be `joins'" }
- assert_raise(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
- end
-
- def test_dynamic_finder_with_invalid_params
- assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
- end
-
def test_find_all_with_join
- developers_on_project_one = Developer.find(
- :all,
+ developers_on_project_one = Developer.scoped(
:joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
- :conditions => 'project_id=1'
- )
+ :where => 'project_id=1'
+ ).all
assert_equal 3, developers_on_project_one.length
developer_names = developers_on_project_one.map { |d| d.name }
assert developer_names.include?('David')
@@ -1082,17 +685,15 @@ class FinderTest < ActiveRecord::TestCase
end
def test_joins_dont_clobber_id
- first = Firm.find(
- :first,
+ first = Firm.scoped(
:joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
- :conditions => 'companies.id = 1'
- )
+ :where => 'companies.id = 1'
+ ).first
assert_equal 1, first.id
end
def test_joins_with_string_array
- person_with_reader_and_post = Post.find(
- :all,
+ person_with_reader_and_post = Post.scoped(
:joins => [
"INNER JOIN categorizations ON categorizations.post_id = posts.id",
"INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'"
@@ -1103,15 +704,14 @@ class FinderTest < ActiveRecord::TestCase
def test_find_by_id_with_conditions_with_or
assert_nothing_raised do
- Post.find([1,2,3],
- :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'")
+ Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
end
end
# http://dev.rubyonrails.org/ticket/6778
def test_find_ignores_previously_inserted_record
Post.create!(:title => 'test', :body => 'it out')
- assert_equal [], Post.find_all_by_id(nil)
+ assert_equal [], Post.where(id: nil)
end
def test_find_by_empty_ids
@@ -1119,13 +719,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_by_empty_in_condition
- assert_equal [], Post.find(:all, :conditions => ['id in (?)', []])
+ assert_equal [], Post.where('id in (?)', [])
end
def test_find_by_records
- p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc')
+ p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all
+ assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2]], :order => 'id asc')
+ assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2.id]], :order => 'id asc')
end
def test_select_value
@@ -1152,16 +752,15 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
- assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).size
+ assert_equal 2, Post.scoped(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).all.size
- assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address},
- :order => 'author_addresses_authors.id DESC ', :limit => 3).size
+ assert_equal 3, Post.scoped(:includes => { :author => :author_address, :authors => :author_address},
+ :order => 'author_addresses_authors.id DESC ', :limit => 3).all.size
end
def test_find_with_nil_inside_set_passed_for_one_attribute
- client_of = Company.find(
- :all,
- :conditions => {
+ client_of = Company.scoped(
+ :where => {
:client_of => [2, 1, nil],
:name => ['37signals', 'Summit', 'Microsoft'] },
:order => 'client_of DESC'
@@ -1172,9 +771,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_nil_inside_set_passed_for_attribute
- client_of = Company.find(
- :all,
- :conditions => { :client_of => [nil] },
+ client_of = Company.scoped(
+ :where => { :client_of => [nil] },
:order => 'client_of DESC'
).map { |x| x.client_of }
@@ -1182,22 +780,14 @@ class FinderTest < ActiveRecord::TestCase
end
def test_with_limiting_with_custom_select
- posts = Post.find(
- :all, :include => :author, :select => ' posts.*, authors.id as "author_id"',
- :references => :authors, :limit => 3, :order => 'posts.id'
- )
+ posts = Post.references(:authors).scoped(
+ :includes => :author, :select => ' posts.*, authors.id as "author_id"',
+ :limit => 3, :order => 'posts.id'
+ ).all
assert_equal 3, posts.size
assert_equal [0, 1, 1], posts.map(&:author_id).sort
end
- def test_finder_with_scoped_from
- all_topics = Topic.find(:all)
-
- Topic.send(:with_scope, :find => { :from => 'fake_topics' }) do
- assert_equal all_topics, Topic.from('topics').to_a
- end
- end
-
def test_find_one_message_with_custom_primary_key
Toy.primary_key = :name
begin
@@ -1208,7 +798,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_finder_with_offset_string
- assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.find(:all, :offset => "3") }
+ assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.scoped(:offset => "3").all }
end
protected
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index e660b2ca35..c28f8de682 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -716,7 +716,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase
end
def test_only_generates_a_pk_if_necessary
- m = Matey.find(:first)
+ m = Matey.first
m.pirate = pirates(:blackbeard)
m.target = pirates(:redbeard)
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 5c3560a33b..37fa13f771 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -2,6 +2,7 @@ require File.expand_path('../../../../load_paths', __FILE__)
require 'config'
+gem 'minitest'
require 'minitest/autorun'
require 'stringio'
require 'mocha'
@@ -36,7 +37,7 @@ def current_adapter?(*types)
end
def in_memory_db?
- current_adapter?(:SQLiteAdapter) &&
+ current_adapter?(:SQLite3Adapter) &&
ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"
end
@@ -80,8 +81,8 @@ class ActiveSupport::TestCase
self.use_instantiated_fixtures = false
self.use_transactional_fixtures = true
- def create_fixtures(*table_names, &block)
- ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
+ def create_fixtures(*fixture_set_names, &block)
+ ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block)
end
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 02df464469..06de51f5cd 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -15,7 +15,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_class_with_blank_sti_name
- company = Company.find(:first)
+ company = Company.first
company = company.dup
company.extend(Module.new {
def read_attribute(name)
@@ -24,7 +24,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
})
company.save!
- company = Company.find(:all).find { |x| x.id == company.id }
+ company = Company.all.find { |x| x.id == company.id }
assert_equal ' ', company.type
end
@@ -98,7 +98,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_inheritance_find_all
- companies = Company.find(:all, :order => 'id')
+ companies = Company.scoped(:order => 'id').all
assert_kind_of Firm, companies[0], "37signals should be a firm"
assert_kind_of Client, companies[1], "Summit should be a client"
end
@@ -149,9 +149,9 @@ class InheritanceTest < ActiveRecord::TestCase
def test_update_all_within_inheritance
Client.update_all "name = 'I am a client'"
- assert_equal "I am a client", Client.find(:all).first.name
+ assert_equal "I am a client", Client.all.first.name
# Order by added as otherwise Oracle tests were failing because of different order of results
- assert_equal "37signals", Firm.find(:all, :order => "id").first.name
+ assert_equal "37signals", Firm.scoped(:order => "id").all.first.name
end
def test_alt_update_all_within_inheritance
@@ -173,9 +173,9 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_find_first_within_inheritance
- assert_kind_of Firm, Company.find(:first, :conditions => "name = '37signals'")
- assert_kind_of Firm, Firm.find(:first, :conditions => "name = '37signals'")
- assert_nil Client.find(:first, :conditions => "name = '37signals'")
+ assert_kind_of Firm, Company.scoped(:where => "name = '37signals'").first
+ assert_kind_of Firm, Firm.scoped(:where => "name = '37signals'").first
+ assert_nil Client.scoped(:where => "name = '37signals'").first
end
def test_alt_find_first_within_inheritance
@@ -187,10 +187,10 @@ class InheritanceTest < ActiveRecord::TestCase
def test_complex_inheritance
very_special_client = VerySpecialClient.create("name" => "veryspecial")
assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first
- assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
- assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
- assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
- assert_equal 1, Client.find(:all, :conditions => "name = 'Summit'").size
+ assert_equal very_special_client, SpecialClient.scoped(:where => "name = 'veryspecial'").first
+ assert_equal very_special_client, Company.scoped(:where => "name = 'veryspecial'").first
+ assert_equal very_special_client, Client.scoped(:where => "name = 'veryspecial'").first
+ assert_equal 1, Client.scoped(:where => "name = 'Summit'").all.size
assert_equal very_special_client, Client.find(very_special_client.id)
end
@@ -201,14 +201,14 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_eager_load_belongs_to_something_inherited
- account = Account.find(1, :include => :firm)
+ account = Account.scoped(:includes => :firm).find(1)
assert account.association_cache.key?(:firm), "nil proves eager load failed"
end
def test_eager_load_belongs_to_primary_key_quoting
con = Account.connection
assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do
- Account.find(1, :include => :firm)
+ Account.scoped(:includes => :firm).find(1)
end
end
@@ -230,7 +230,7 @@ class InheritanceTest < ActiveRecord::TestCase
private
def switch_to_alt_inheritance_column
# we don't want misleading test results, so get rid of the values in the type column
- Company.find(:all, :order => 'id').each do |c|
+ Company.scoped(:order => 'id').all.each do |c|
c['type'] = nil
c.save
end
@@ -259,7 +259,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
def test_instantiation_doesnt_try_to_require_corresponding_file
ActiveRecord::Base.store_full_sti_class = false
- foo = Firm.find(:first).clone
+ foo = Firm.first.clone
foo.ruby_type = foo.type = 'FirmOnTheFly'
foo.save!
diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb
index 98cda010ae..426a350379 100644
--- a/activerecord/test/cases/invalid_date_test.rb
+++ b/activerecord/test/cases/invalid_date_test.rb
@@ -7,8 +7,6 @@ class InvalidDateTest < ActiveRecord::TestCase
invalid_dates = [[2007, 11, 31], [1993, 2, 29], [2007, 2, 29]]
- topic = Topic.new
-
valid_dates.each do |date_src|
topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s)
# Oracle DATE columns are datetime columns and Oracle adapter returns Time value
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 75e5dfa49b..0b78f2e46b 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -137,7 +137,7 @@ class LifecycleTest < ActiveRecord::TestCase
def test_auto_observer
topic_observer = TopicaAuditor.instance
assert_nil TopicaAuditor.observed_class
- assert_equal [Topic], TopicaAuditor.instance.observed_classes.to_a
+ assert_equal [Topic], TopicaAuditor.observed_classes.to_a
topic = Topic.find(1)
assert_equal topic.title, topic_observer.topic.title
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index cc6baa6153..afb0bd6fd9 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -323,7 +323,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
def counter_test(model, expected_count)
add_counter_column_to(model)
- object = model.find(:first)
+ object = model.first
assert_equal 0, object.test_count
assert_equal 0, object.send(model.locking_column)
yield object.id
@@ -358,18 +358,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
def test_sane_find_with_lock
assert_nothing_raised do
Person.transaction do
- Person.find 1, :lock => true
- end
- end
- end
-
- # Test scoped lock.
- def test_sane_find_with_scoped_lock
- assert_nothing_raised do
- Person.transaction do
- Person.send(:with_scope, :find => { :lock => true }) do
- Person.find 1
- end
+ Person.lock.find(1)
end
end
end
@@ -380,7 +369,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
def test_eager_find_with_lock
assert_nothing_raised do
Person.transaction do
- Person.find 1, :include => :readers, :lock => true
+ Person.includes(:readers).lock.find(1)
end
end
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 8122857f52..2f98d3c646 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -247,6 +247,23 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert !Task.new.respond_to?("#{method}=")
end
end
+end
+
+
+# This class should be deleted when we removed active_record_deprecated_finders as a
+# dependency.
+class MassAssignmentSecurityDeprecatedFindersTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+
+ def setup
+ super
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
def test_find_or_initialize_by_with_attr_accessible_attributes
p = TightPerson.find_or_initialize_by_first_name('Josh', attributes_hash)
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
deleted file mode 100644
index ebf6e26385..0000000000
--- a/activerecord/test/cases/method_scoping_test.rb
+++ /dev/null
@@ -1,558 +0,0 @@
-# This file can be removed once with_exclusive_scope and with_scope are removed.
-# All the tests were already ported to relation_scoping_test.rb when the new
-# relation scoping API was added.
-
-require "cases/helper"
-require 'models/post'
-require 'models/author'
-require 'models/developer'
-require 'models/project'
-require 'models/comment'
-
-class MethodScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
-
- def test_set_conditions
- Developer.send(:with_scope, :find => { :conditions => 'just a test...' }) do
- assert_match '(just a test...)', Developer.scoped.to_sql
- end
- end
-
- def test_scoped_find
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_nothing_raised { Developer.find(1) }
- end
- end
-
- def test_scoped_find_first
- Developer.send(:with_scope, :find => { :conditions => "salary = 100000" }) do
- assert_equal Developer.find(10), Developer.find(:first, :order => 'name')
- end
- end
-
- def test_scoped_find_last
- highest_salary = Developer.find(:first, :order => "salary DESC")
-
- Developer.send(:with_scope, :find => { :order => "salary" }) do
- assert_equal highest_salary, Developer.last
- end
- end
-
- def test_scoped_find_last_preserves_scope
- lowest_salary = Developer.find(:first, :order => "salary ASC")
- highest_salary = Developer.find(:first, :order => "salary DESC")
-
- Developer.send(:with_scope, :find => { :order => "salary" }) do
- assert_equal highest_salary, Developer.last
- assert_equal lowest_salary, Developer.first
- end
- end
-
- def test_scoped_find_combines_conditions
- Developer.send(:with_scope, :find => { :conditions => "salary = 9000" }) do
- assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
- end
- end
-
- def test_scoped_find_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_equal developers(:poor_jamis), Developer.find(:first)
- end
- end
-
- def test_scoped_find_combines_and_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
- end
- end
-
- def test_scoped_find_all
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal [developers(:david)], Developer.all
- end
- end
-
- def test_scoped_find_select
- Developer.send(:with_scope, :find => { :select => "id, name" }) do
- developer = Developer.find(:first, :conditions => "name = 'David'")
- assert_equal "David", developer.name
- assert !developer.has_attribute?(:salary)
- end
- end
-
- def test_scope_select_concatenates
- Developer.send(:with_scope, :find => { :select => "name" }) do
- developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'")
- assert_equal 80000, developer.salary
- assert developer.has_attribute?(:id)
- assert developer.has_attribute?(:name)
- assert developer.has_attribute?(:salary)
- end
- end
-
- def test_scoped_count
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal 1, Developer.count
- end
-
- Developer.send(:with_scope, :find => { :conditions => 'salary = 100000' }) do
- assert_equal 8, Developer.count
- assert_equal 1, Developer.count(:conditions => "name LIKE 'fixture_1%'")
- end
- end
-
- def test_scoped_find_include
- # with the include, will retrieve only developers for the given project
- scoped_developers = Developer.send(:with_scope, :find => { :include => :projects }) do
- Developer.find(:all, :conditions => { 'projects.id' => 2 })
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- end
-
- def test_scoped_find_joins
- scoped_developers = Developer.send(:with_scope, :find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do
- Developer.find(:all, :conditions => 'developers_projects.project_id = 2')
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- assert_equal developers(:david).attributes, scoped_developers.first.attributes
- end
-
- def test_scoped_find_using_new_style_joins
- scoped_developers = Developer.send(:with_scope, :find => { :joins => :projects }) do
- Developer.find(:all, :conditions => 'projects.id = 2')
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- assert_equal developers(:david).attributes, scoped_developers.first.attributes
- end
-
- def test_scoped_find_merges_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id ' }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_new_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => :comments, :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_new_and_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_string_array_style_and_string_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => ["INNER JOIN posts ON posts.author_id = authors.id"]}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_string_array_style_and_hash_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN comments ON posts.id = comments.post_id'], :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_joins_and_eliminates_duplicate_string_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON posts.author_id = authors.id'}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => ["INNER JOIN posts ON posts.author_id = authors.id", "INNER JOIN comments ON posts.id = comments.post_id"], :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_strips_spaces_from_string_joins_and_eliminates_duplicate_string_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => ' INNER JOIN posts ON posts.author_id = authors.id '}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN posts ON posts.author_id = authors.id'], :conditions => 'posts.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_count_include
- # with the include, will retrieve only developers for the given project
- Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.count(:conditions => { 'projects.id' => 2 })
- end
- end
-
- def test_scope_for_create_only_uses_equal
- table = VerySpecialComment.arel_table
- relation = VerySpecialComment.scoped
- relation.where_values << table[:id].not_eq(1)
- assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create))
- end
-
- def test_scoped_create
- new_comment = nil
-
- VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do
- assert_equal({:post_id => 1, :type => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
- new_comment = VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_join_and_merge
- Comment.where(:body => "but Who's Buying?").joins(:post).merge(Post.where(:body => 'Peace Sells...')).with_scope do
- assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create)
- end
- end
-
- def test_immutable_scope
- options = { :conditions => "name = 'David'" }
- Developer.send(:with_scope, :find => options) do
- assert_equal %w(David), Developer.all.map(&:name)
- options[:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.all.map(&:name)
- end
-
- scope = { :find => { :conditions => "name = 'David'" }}
- Developer.send(:with_scope, scope) do
- assert_equal %w(David), Developer.all.map(&:name)
- scope[:find][:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
-
- def test_scoped_with_duck_typing
- scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] })
- Developer.send(:with_scope, scoping) do
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
-
- def test_ensure_that_method_scoping_is_correctly_restored
- begin
- Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
- raise "an exception"
- end
- rescue
- end
-
- assert !Developer.scoped.where_values.include?("name = 'Jamis'")
- end
-end
-
-class NestedScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts
-
- def test_merge_options
- Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
- Developer.send(:with_scope, :find => { :limit => 10 }) do
- devs = Developer.scoped
- assert_match '(salary = 80000)', devs.to_sql
- assert_equal 10, devs.taken
- end
- end
- end
-
- def test_merge_inner_scope_has_priority
- Developer.send(:with_scope, :find => { :limit => 5 }) do
- Developer.send(:with_scope, :find => { :limit => 10 }) do
- assert_equal 10, Developer.scoped.taken
- end
- end
- end
-
- def test_replace_options
- Developer.send(:with_scope, :find => { :conditions => {:name => 'David'} }) do
- Developer.send(:with_exclusive_scope, :find => { :conditions => {:name => 'Jamis'} }) do
- assert_equal 'Jamis', Developer.scoped.send(:scope_for_create)[:name]
- end
-
- assert_equal 'David', Developer.scoped.send(:scope_for_create)[:name]
- end
- end
-
- def test_with_exclusive_scope_with_relation
- assert_raise(ArgumentError) do
- Developer.all_johns
- end
- end
-
- def test_append_conditions
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
- devs = Developer.scoped
- assert_match "(name = 'David') AND (salary = 80000)", devs.to_sql
- assert_equal(1, Developer.count)
- end
- Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
- assert_equal(0, Developer.count)
- end
- end
- end
-
- def test_merge_and_append_options
- Developer.send(:with_scope, :find => { :conditions => 'salary = 80000', :limit => 10 }) do
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- devs = Developer.scoped
- assert_match "(salary = 80000) AND (name = 'David')", devs.to_sql
- assert_equal 10, devs.taken
- end
- end
- end
-
- def test_nested_scoped_find
- Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
- Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do
- assert_nothing_raised { Developer.find(1) }
- assert_equal('David', Developer.find(:first).name)
- end
- assert_equal('Jamis', Developer.find(:first).name)
- end
- end
-
- def test_nested_scoped_find_include
- Developer.send(:with_scope, :find => { :include => :projects }) do
- Developer.send(:with_scope, :find => { :conditions => { 'projects.id' => 2 } }) do
- assert_nothing_raised { Developer.find(1) }
- assert_equal('David', Developer.find(:first).name)
- end
- end
- end
-
- def test_nested_scoped_find_merged_include
- # :include's remain unique and don't "double up" when merging
- Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do
- Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal 'David', Developer.find(:first).name
- end
- end
-
- # the nested scope doesn't remove the first :include
- Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do
- Developer.send(:with_scope, :find => { :include => [] }) do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal('David', Developer.find(:first).name)
- end
- end
-
- # mixing array and symbol include's will merge correctly
- Developer.send(:with_scope, :find => { :include => [:projects], :conditions => { 'projects.id' => 2 } }) do
- Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal('David', Developer.find(:first).name)
- end
- end
- end
-
- def test_nested_scoped_find_replace_include
- Developer.send(:with_scope, :find => { :include => :projects }) do
- Developer.send(:with_exclusive_scope, :find => { :include => [] }) do
- assert_equal 0, Developer.scoped.includes_values.length
- end
- end
- end
-
- def test_three_level_nested_exclusive_scoped_find
- Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
- assert_equal('Jamis', Developer.find(:first).name)
-
- Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal('David', Developer.find(:first).name)
-
- Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'Maiha'" }) do
- assert_equal(nil, Developer.find(:first))
- end
-
- # ensure that scoping is restored
- assert_equal('David', Developer.find(:first).name)
- end
-
- # ensure that scoping is restored
- assert_equal('Jamis', Developer.find(:first).name)
- end
- end
-
- def test_merged_scoped_find
- poor_jamis = developers(:poor_jamis)
- Developer.send(:with_scope, :find => { :conditions => "salary < 100000" }) do
- Developer.send(:with_scope, :find => { :offset => 1, :order => 'id asc' }) do
- # Oracle adapter does not generated space after asc therefore trailing space removed from regex
- assert_sql(/ORDER BY\s+id asc/) do
- assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
- end
- end
- end
- end
-
- def test_merged_scoped_find_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
- Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) }
- end
- end
- end
-
- def test_nested_scoped_find_combines_and_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
- Developer.send(:with_exclusive_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_equal developers(:poor_jamis), Developer.find(:first)
- assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
- end
- end
- end
-
- def test_merged_scoped_find_combines_and_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
- Developer.send(:with_scope, :find => { :conditions => ['salary > ?', 9000] }) do
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
- end
-
- def test_nested_scoped_create
- comment = nil
- Comment.send(:with_scope, :create => { :post_id => 1}) do
- Comment.send(:with_scope, :create => { :post_id => 2}) do
- assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create))
- comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
- end
- end
- assert_equal 2, comment.post_id
- end
-
- def test_nested_exclusive_scope_for_create
- comment = nil
-
- Comment.send(:with_scope, :create => { :body => "Hey guys, nested scopes are broken. Please fix!" }) do
- Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do
- assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create))
- assert_blank Comment.new.body
- comment = Comment.create :body => "Hey guys"
- end
- end
- assert_equal 1, comment.post_id
- assert_equal 'Hey guys', comment.body
- end
-
- def test_merged_scoped_find_on_blank_conditions
- [nil, " ", [], {}].each do |blank|
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- assert_nothing_raised { Developer.find(:first) }
- end
- end
- end
- end
-
- def test_merged_scoped_find_on_blank_bind_conditions
- [ [""], ["",{}] ].each do |blank|
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- assert_nothing_raised { Developer.find(:first) }
- end
- end
- end
- end
-
- def test_immutable_nested_scope
- options1 = { :conditions => "name = 'Jamis'" }
- options2 = { :conditions => "name = 'David'" }
- Developer.send(:with_scope, :find => options1) do
- Developer.send(:with_exclusive_scope, :find => options2) do
- assert_equal %w(David), Developer.all.map(&:name)
- options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
- end
-
- def test_immutable_merged_scope
- options1 = { :conditions => "name = 'Jamis'" }
- options2 = { :conditions => "salary > 10000" }
- Developer.send(:with_scope, :find => options1) do
- Developer.send(:with_scope, :find => options2) do
- assert_equal %w(Jamis), Developer.all.map(&:name)
- options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(Jamis), Developer.all.map(&:name)
- end
- end
- end
-
- def test_ensure_that_method_scoping_is_correctly_restored
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- begin
- Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
- raise "an exception"
- end
- rescue
- end
-
- assert Developer.scoped.where_values.include?("name = 'David'")
- assert !Developer.scoped.where_values.include?("name = 'Maiha'")
- end
- end
-
- def test_nested_scoped_find_merges_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id' }) do
- Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
- end
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_nested_scoped_find_merges_new_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.send(:with_scope, :find => { :joins => :comments }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
- end
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_nested_scoped_find_merges_new_and_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => '', :conditions => 'comments.id = 1')
- end
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index f0b1f74bd3..ab61a4dcef 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -83,7 +83,6 @@ module ActiveRecord
t.column :one_int, :integer, :limit => 1
t.column :four_int, :integer, :limit => 4
t.column :eight_int, :integer, :limit => 8
- t.column :eleven_int, :integer, :limit => 11
end
columns = connection.columns(:testings)
@@ -94,20 +93,17 @@ module ActiveRecord
one = columns.detect { |c| c.name == "one_int" }
four = columns.detect { |c| c.name == "four_int" }
eight = columns.detect { |c| c.name == "eight_int" }
- eleven = columns.detect { |c| c.name == "eleven_int" }
if current_adapter?(:PostgreSQLAdapter)
assert_equal 'integer', default.sql_type
assert_equal 'smallint', one.sql_type
assert_equal 'integer', four.sql_type
assert_equal 'bigint', eight.sql_type
- assert_equal 'integer', eleven.sql_type
elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_match 'int(11)', default.sql_type
assert_match 'tinyint', one.sql_type
assert_match 'int', four.sql_type
assert_match 'bigint', eight.sql_type
- assert_match 'int(11)', eleven.sql_type
elsif current_adapter?(:OracleAdapter)
assert_equal 'NUMBER(38)', default.sql_type
assert_equal 'NUMBER(1)', one.sql_type
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
new file mode 100644
index 0000000000..063209389f
--- /dev/null
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -0,0 +1,221 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class TableTest < ActiveRecord::TestCase
+ class MockConnection < MiniTest::Mock
+ def native_database_types
+ {
+ :string => 'varchar(255)',
+ :integer => 'integer',
+ }
+ end
+
+ def type_to_sql(type, limit, precision, scale)
+ native_database_types[type]
+ end
+ end
+
+ def setup
+ @connection = MockConnection.new
+ end
+
+ def teardown
+ assert @connection.verify
+ end
+
+ def with_change_table
+ yield ConnectionAdapters::Table.new(:delete_me, @connection)
+ end
+
+ def test_references_column_type_adds_id
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ t.references :customer
+ end
+ end
+
+ def test_remove_references_column_type_removes_id
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ t.remove_references :customer
+ end
+ end
+
+ def test_add_belongs_to_works_like_add_references
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ t.belongs_to :customer
+ end
+ end
+
+ def test_remove_belongs_to_works_like_remove_references
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ t.remove_belongs_to :customer
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_adds_type
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}]
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}]
+ t.references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_removes_type
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
+ t.remove_references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}]
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}]
+ t.references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
+ t.remove_references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expect :add_timestamps, nil, [:delete_me]
+ t.timestamps
+ end
+ end
+
+ def test_remove_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expect :remove_timestamps, nil, [:delete_me]
+ t.remove_timestamps
+ end
+ end
+
+ def string_column
+ @connection.native_database_types[:string]
+ end
+
+ def integer_column
+ @connection.native_database_types[:integer]
+ end
+
+ def test_integer_creates_integer_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, integer_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, integer_column, {}]
+ t.integer :foo, :bar
+ end
+ end
+
+ def test_string_creates_string_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, string_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, string_column, {}]
+ t.string :foo, :bar
+ end
+ end
+
+ def test_column_creates_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
+ t.column :bar, :integer
+ end
+ end
+
+ def test_column_creates_column_with_options
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {:null => false}]
+ t.column :bar, :integer, :null => false
+ end
+ end
+
+ def test_index_creates_index
+ with_change_table do |t|
+ @connection.expect :add_index, nil, [:delete_me, :bar, {}]
+ t.index :bar
+ end
+ end
+
+ def test_index_creates_index_with_options
+ with_change_table do |t|
+ @connection.expect :add_index, nil, [:delete_me, :bar, {:unique => true}]
+ t.index :bar, :unique => true
+ end
+ end
+
+ def test_index_exists
+ with_change_table do |t|
+ @connection.expect :index_exists?, nil, [:delete_me, :bar, {}]
+ t.index_exists?(:bar)
+ end
+ end
+
+ def test_index_exists_with_options
+ with_change_table do |t|
+ @connection.expect :index_exists?, nil, [:delete_me, :bar, {:unique => true}]
+ t.index_exists?(:bar, :unique => true)
+ end
+ end
+
+ def test_change_changes_column
+ with_change_table do |t|
+ @connection.expect :change_column, nil, [:delete_me, :bar, :string, {}]
+ t.change :bar, :string
+ end
+ end
+
+ def test_change_changes_column_with_options
+ with_change_table do |t|
+ @connection.expect :change_column, nil, [:delete_me, :bar, :string, {:null => true}]
+ t.change :bar, :string, :null => true
+ end
+ end
+
+ def test_change_default_changes_column
+ with_change_table do |t|
+ @connection.expect :change_column_default, nil, [:delete_me, :bar, :string]
+ t.change_default :bar, :string
+ end
+ end
+
+ def test_remove_drops_single_column
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, :bar]
+ t.remove :bar
+ end
+ end
+
+ def test_remove_drops_multiple_columns
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, :bar, :baz]
+ t.remove :bar, :baz
+ end
+ end
+
+ def test_remove_index_removes_index_with_options
+ with_change_table do |t|
+ @connection.expect :remove_index, nil, [:delete_me, {:unique => true}]
+ t.remove_index :unique => true
+ end
+ end
+
+ def test_rename_renames_column
+ with_change_table do |t|
+ @connection.expect :rename_column, nil, [:delete_me, :bar, :baz]
+ t.rename :bar, :baz
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 040445ef12..18f8d82bfe 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -64,7 +64,7 @@ module ActiveRecord
end
# SELECT
- row = TestModel.find(:first)
+ row = TestModel.first
assert_kind_of BigDecimal, row.wealth
# If this assert fails, that means the SELECT is broken!
@@ -79,7 +79,7 @@ module ActiveRecord
TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789")
# SELECT
- row = TestModel.find(:first)
+ row = TestModel.first
assert_kind_of BigDecimal, row.wealth
# If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
@@ -132,7 +132,7 @@ module ActiveRecord
:birthday => 18.years.ago, :favorite_day => 10.days.ago,
:moment_of_truth => "1782-10-10 21:40:18", :male => true
- bob = TestModel.find(:first)
+ bob = TestModel.first
assert_equal 'bob', bob.first_name
assert_equal 'bobsen', bob.last_name
assert_equal "I was born ....", bob.bio
@@ -183,6 +183,16 @@ module ActiveRecord
assert_instance_of TrueClass, bob.male?
assert_kind_of BigDecimal, bob.wealth
end
+
+ def test_out_of_range_limit_should_raise
+ skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+
+ assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 }
+
+ unless current_adapter?(:PostgreSQLAdapter)
+ assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
new file mode 100644
index 0000000000..8ab1c59724
--- /dev/null
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -0,0 +1,99 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class ReferencesIndexTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ end
+
+ def test_creates_index
+ connection.create_table table_name do |t|
+ t.references :foo, :index => true
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index
+ connection.create_table table_name do |t|
+ t.references :foo
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_explicit
+ connection.create_table table_name do |t|
+ t.references :foo, :index => false
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_creates_index_with_options
+ connection.create_table table_name do |t|
+ t.references :foo, :index => {:name => :index_testings_on_yo_momma}
+ t.references :bar, :index => {:unique => true}
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma)
+ assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true)
+ end
+
+ def test_creates_polymorphic_index
+ connection.create_table table_name do |t|
+ t.references :foo, :polymorphic => true, :index => true
+ end
+
+ assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
+ end
+
+ def test_creates_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :index => true
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_for_existing_table_explicit
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :index => false
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_creates_polymorphic_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :polymorphic => true, :index => true
+ end
+
+ assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
+ end
+
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb
index 16e09fd80e..d1a85ee5e4 100644
--- a/activerecord/test/cases/migration/rename_column_test.rb
+++ b/activerecord/test/cases/migration/rename_column_test.rb
@@ -18,7 +18,7 @@ module ActiveRecord
rename_column "test_models", "girlfriend", "exgirlfriend"
TestModel.reset_column_information
- bob = TestModel.find(:first)
+ bob = TestModel.first
assert_equal "bobette", bob.exgirlfriend
end
@@ -33,7 +33,7 @@ module ActiveRecord
rename_column :test_models, :first_name, :nick_name
TestModel.reset_column_information
assert TestModel.column_names.include?("nick_name")
- assert_equal ['foo'], TestModel.find(:all).map(&:nick_name)
+ assert_equal ['foo'], TestModel.all.map(&:nick_name)
end
# FIXME: another integration test. We should decouple this from the
@@ -46,7 +46,7 @@ module ActiveRecord
rename_column "test_models", "first_name", "nick_name"
TestModel.reset_column_information
assert TestModel.column_names.include?("nick_name")
- assert_equal ['foo'], TestModel.find(:all).map(&:nick_name)
+ assert_equal ['foo'], TestModel.all.map(&:nick_name)
end
def test_rename_column_preserves_default_value_not_null
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 92dc150104..5d1bad0d54 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -36,7 +36,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.initialize_schema_migrations_table
ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
- %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table|
+ %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table|
Thing.connection.drop_table(table) rescue nil
end
Thing.reset_column_information
@@ -109,7 +109,7 @@ class MigrationTest < ActiveRecord::TestCase
:value_of_e => BigDecimal("2.7182818284590452353602875")
)
- b = BigNumber.find(:first)
+ b = BigNumber.first
assert_not_nil b
assert_not_nil b.bank_balance
@@ -153,7 +153,7 @@ class MigrationTest < ActiveRecord::TestCase
end
GiveMeBigNumbers.down
- assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
+ assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
end
def test_filtering_migrations
@@ -165,13 +165,13 @@ class MigrationTest < ActiveRecord::TestCase
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter)
Person.reset_column_information
assert !Person.column_methods_hash.include?(:last_name)
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
end
class MockMigration < ActiveRecord::Migration
@@ -278,19 +278,19 @@ class MigrationTest < ActiveRecord::TestCase
def test_rename_table_with_prefix_and_suffix
assert !Thing.table_exists?
- ActiveRecord::Base.table_name_prefix = 'prefix_'
- ActiveRecord::Base.table_name_suffix = '_suffix'
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
Thing.reset_table_name
Thing.reset_sequence_name
WeNeedThings.up
assert Thing.create("content" => "hello world")
- assert_equal "hello world", Thing.find(:first).content
+ assert_equal "hello world", Thing.first.content
RenameThings.up
- Thing.table_name = "prefix_awesome_things_suffix"
+ Thing.table_name = "p_awesome_things_s"
- assert_equal "hello world", Thing.find(:first).content
+ assert_equal "hello world", Thing.first.content
ensure
ActiveRecord::Base.table_name_prefix = ''
ActiveRecord::Base.table_name_suffix = ''
@@ -306,10 +306,10 @@ class MigrationTest < ActiveRecord::TestCase
Reminder.reset_sequence_name
WeNeedReminders.up
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
+ assert_equal "hello world", Reminder.first.content
WeNeedReminders.down
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
ensure
ActiveRecord::Base.table_name_prefix = ''
ActiveRecord::Base.table_name_suffix = ''
@@ -375,6 +375,27 @@ class MigrationTest < ActiveRecord::TestCase
end
end
+ def test_out_of_range_limit_should_raise
+ skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+
+ Person.connection.drop_table :test_limits rescue nil
+ assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do
+ Person.connection.create_table :test_integer_limits, :force => true do |t|
+ t.column :bigone, :integer, :limit => 10
+ end
+ end
+
+ unless current_adapter?(:PostgreSQLAdapter)
+ assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do
+ Person.connection.create_table :test_text_limits, :force => true do |t|
+ t.column :bigtext, :text, :limit => 0xfffffffff
+ end
+ end
+ end
+
+ Person.connection.drop_table :test_limits rescue nil
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
@@ -400,227 +421,6 @@ class ReservedWordsMigrationTest < ActiveRecord::TestCase
end
end
-
-class ChangeTableMigrationsTest < ActiveRecord::TestCase
- def setup
- @connection = Person.connection
- @connection.create_table :delete_me, :force => true do |t|
- end
- end
-
- def teardown
- Person.connection.drop_table :delete_me rescue nil
- end
-
- def test_references_column_type_adds_id
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.references :customer
- end
- end
-
- def test_remove_references_column_type_removes_id
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_references :customer
- end
- end
-
- def test_add_belongs_to_works_like_add_references
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.belongs_to :customer
- end
- end
-
- def test_remove_belongs_to_works_like_remove_references
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_belongs_to :customer
- end
- end
-
- def test_references_column_type_with_polymorphic_adds_type
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {})
- t.references :taggable, :polymorphic => true
- end
- end
-
- def test_remove_references_column_type_with_polymorphic_removes_type
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true
- end
- end
-
- def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false})
- t.references :taggable, :polymorphic => true, :null => false
- end
- end
-
- def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true, :null => false
- end
- end
-
- def test_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:add_timestamps).with(:delete_me)
- t.timestamps
- end
- end
-
- def test_remove_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:remove_timestamps).with(:delete_me)
- t.remove_timestamps
- end
- end
-
- def string_column
- if current_adapter?(:PostgreSQLAdapter)
- "character varying(255)"
- elsif current_adapter?(:OracleAdapter)
- 'VARCHAR2(255)'
- else
- 'varchar(255)'
- end
- end
-
- def integer_column
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- 'int(11)'
- elsif current_adapter?(:OracleAdapter)
- 'NUMBER(38)'
- else
- 'integer'
- end
- end
-
- def test_integer_creates_integer_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {})
- t.integer :foo, :bar
- end
- end
-
- def test_string_creates_string_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, string_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, string_column, {})
- t.string :foo, :bar
- end
- end
-
- def test_column_creates_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {})
- t.column :bar, :integer
- end
- end
-
- def test_column_creates_column_with_options
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false})
- t.column :bar, :integer, :null => false
- end
- end
-
- def test_index_creates_index
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {})
- t.index :bar
- end
- end
-
- def test_index_creates_index_with_options
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true})
- t.index :bar, :unique => true
- end
- end
-
- def test_index_exists
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {})
- t.index_exists?(:bar)
- end
- end
-
- def test_index_exists_with_options
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true})
- t.index_exists?(:bar, :unique => true)
- end
- end
-
- def test_change_changes_column
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
- t.change :bar, :string
- end
- end
-
- def test_change_changes_column_with_options
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true})
- t.change :bar, :string, :null => true
- end
- end
-
- def test_change_default_changes_column
- with_change_table do |t|
- @connection.expects(:change_column_default).with(:delete_me, :bar, :string)
- t.change_default :bar, :string
- end
- end
-
- def test_remove_drops_single_column
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar])
- t.remove :bar
- end
- end
-
- def test_remove_drops_multiple_columns
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar, :baz])
- t.remove :bar, :baz
- end
- end
-
- def test_remove_index_removes_index_with_options
- with_change_table do |t|
- @connection.expects(:remove_index).with(:delete_me, {:unique => true})
- t.remove_index :unique => true
- end
- end
-
- def test_rename_renames_column
- with_change_table do |t|
- @connection.expects(:rename_column).with(:delete_me, :bar, :baz)
- t.rename :bar, :baz
- end
- end
-
- protected
- def with_change_table
- Person.connection.change_table :delete_me do |t|
- yield t
- end
- end
-end
-
if ActiveRecord::Base.connection.supports_bulk_alter?
class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
def setup
@@ -882,8 +682,8 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_skipping_migrations
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
@existing_migrations = Dir[@migrations_path + "/*.rb"]
-
- sources = {}
+
+ sources = {}
sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision"
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index f7a5d05582..a03c4f552e 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -27,19 +27,19 @@ class ModulesTest < ActiveRecord::TestCase
end
def test_module_spanning_associations
- firm = MyApplication::Business::Firm.find(:first)
+ firm = MyApplication::Business::Firm.first
assert !firm.clients.empty?, "Firm should have clients"
assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name"
end
def test_module_spanning_has_and_belongs_to_many_associations
- project = MyApplication::Business::Project.find(:first)
+ project = MyApplication::Business::Project.first
project.developers << MyApplication::Business::Developer.create("name" => "John")
assert_equal "John", project.developers.last.name
end
def test_associations_spanning_cross_modules
- account = MyApplication::Billing::Account.find(:first, :order => 'id')
+ account = MyApplication::Billing::Account.scoped(:order => 'id').first
assert_kind_of MyApplication::Business::Firm, account.firm
assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm
assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm
@@ -48,7 +48,7 @@ class ModulesTest < ActiveRecord::TestCase
end
def test_find_account_and_include_company
- account = MyApplication::Billing::Account.find(1, :include => :firm)
+ account = MyApplication::Billing::Account.scoped(:includes => :firm).find(1)
assert_kind_of MyApplication::Business::Firm, account.firm
end
@@ -72,8 +72,8 @@ class ModulesTest < ActiveRecord::TestCase
clients = []
assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
- clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL', :references => :accounts)
- clients << MyApplication::Business::Client.find(3, :include => {:firm => :account})
+ clients << MyApplication::Business::Client.references(:accounts).scoped(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3)
+ clients << MyApplication::Business::Client.scoped(:includes => {:firm => :account}).find(3)
end
clients.each do |client|
@@ -123,7 +123,7 @@ class ModulesTest < ActiveRecord::TestCase
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
- collection = Shop::Collection.find(:first)
+ collection = Shop::Collection.first
assert !collection.products.empty?, "Collection should have products"
assert_nothing_raised { collection.destroy }
ensure
@@ -134,7 +134,7 @@ class ModulesTest < ActiveRecord::TestCase
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
- product = Shop::Product.find(:first)
+ product = Shop::Product.first
assert !product.variants.empty?, "Product should have variants"
assert_nothing_raised { product.destroy }
ensure
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 0d3c0b20a4..bf825c002a 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -10,12 +10,12 @@ class NamedScopeTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
- assert !Topic.find(:all).empty?
+ assert !Topic.all.empty?
- assert_equal Topic.find(:all), Topic.base
- assert_equal Topic.find(:all), Topic.base.to_a
- assert_equal Topic.find(:first), Topic.base.first
- assert_equal Topic.find(:all), Topic.base.map { |i| i }
+ assert_equal Topic.all, Topic.base
+ assert_equal Topic.all, Topic.base.to_a
+ assert_equal Topic.first, Topic.base.first
+ assert_equal Topic.all, Topic.base.map { |i| i }
end
def test_found_items_are_cached
@@ -38,10 +38,10 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_delegates_finds_and_calculations_to_the_base_class
- assert !Topic.find(:all).empty?
+ assert !Topic.all.empty?
- assert_equal Topic.find(:all), Topic.base.find(:all)
- assert_equal Topic.find(:first), Topic.base.find(:first)
+ assert_equal Topic.all, Topic.base.all
+ assert_equal Topic.first, Topic.base.first
assert_equal Topic.count, Topic.base.count
assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
end
@@ -58,10 +58,10 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
- assert !Topic.find(:all, :conditions => {:approved => true}).empty?
+ assert !Topic.scoped(:where => {:approved => true}).all.empty?
- assert_equal Topic.find(:all, :conditions => {:approved => true}), Topic.approved
- assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count
+ assert_equal Topic.scoped(:where => {:approved => true}).all, Topic.approved
+ assert_equal Topic.where(:approved => true).count, Topic.approved.count
end
def test_scopes_with_string_name_can_be_composed
@@ -70,13 +70,9 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.replied.approved, Topic.replied.approved_as_string
end
- def test_scopes_can_be_specified_with_deep_hash_conditions
- assert_equal Topic.replied.approved, Topic.replied.approved_as_hash_condition
- end
-
def test_scopes_are_composable
- assert_equal((approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved)
- assert_equal((replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied)
+ assert_equal((approved = Topic.scoped(:where => {:approved => true}).all), Topic.approved)
+ assert_equal((replied = Topic.scoped(:where => 'replies_count > 0').all), Topic.replied)
assert !(approved == replied)
assert !(approved & replied).empty?
@@ -84,8 +80,8 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_procedural_scopes
- topics_written_before_the_third = Topic.find(:all, :conditions => ['written_on < ?', topics(:third).written_on])
- topics_written_before_the_second = Topic.find(:all, :conditions => ['written_on < ?', topics(:second).written_on])
+ topics_written_before_the_third = Topic.where('written_on < ?', topics(:third).written_on)
+ topics_written_before_the_second = Topic.where('written_on < ?', topics(:second).written_on)
assert_not_equal topics_written_before_the_second, topics_written_before_the_third
assert_equal topics_written_before_the_third, Topic.written_before(topics(:third).written_on)
@@ -93,30 +89,11 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_procedural_scopes_returning_nil
- all_topics = Topic.find(:all)
+ all_topics = Topic.all
assert_equal all_topics, Topic.written_before(nil)
end
- def test_scopes_with_joins
- address = author_addresses(:david_address)
- posts_with_authors_at_address = Post.find(
- :all, :joins => 'JOIN authors ON authors.id = posts.author_id',
- :conditions => [ 'authors.author_address_id = ?', address.id ]
- )
- assert_equal posts_with_authors_at_address, Post.with_authors_at_address(address)
- end
-
- def test_scopes_with_joins_respects_custom_select
- address = author_addresses(:david_address)
- posts_with_authors_at_address_titles = Post.find(:all,
- :select => 'title',
- :joins => 'JOIN authors ON authors.id = posts.author_id',
- :conditions => [ 'authors.author_address_id = ?', address.id ]
- )
- assert_equal posts_with_authors_at_address_titles.map(&:title), Post.with_authors_at_address(address).find(:all, :select => 'title').map(&:title)
- end
-
def test_scope_with_object
objects = Topic.with_object
assert_operator objects.length, :>, 0
@@ -153,20 +130,16 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_active_records_have_scope_named__all__
- assert !Topic.find(:all).empty?
+ assert !Topic.all.empty?
- assert_equal Topic.find(:all), Topic.base
+ assert_equal Topic.all, Topic.base
end
def test_active_records_have_scope_named__scoped__
- assert !Topic.find(:all, scope = {:conditions => "content LIKE '%Have%'"}).empty?
+ scope = Topic.where("content LIKE '%Have%'")
+ assert !scope.empty?
- assert_equal Topic.find(:all, scope), Topic.scoped(scope)
- end
-
- def test_first_and_last_should_support_find_options
- assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
- assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
+ assert_equal scope, Topic.scoped(where: "content LIKE '%Have%'")
end
def test_first_and_last_should_allow_integers_for_limit
@@ -183,15 +156,6 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_first_and_last_find_options_should_use_query_when_results_are_loaded
- topics = Topic.base
- topics.reload # force load
- assert_queries(2) do
- topics.first(:order => 'title')
- topics.last(:order => 'title')
- end
- end
-
def test_empty_should_not_load_results
topics = Topic.base
assert_queries(2) do
@@ -254,9 +218,9 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_many_should_return_false_if_none_or_one
- topics = Topic.base.scoped(:conditions => {:id => 0})
+ topics = Topic.base.where(:id => 0)
assert !topics.many?
- topics = Topic.base.scoped(:conditions => {:id => 1})
+ topics = Topic.base.where(:id => 1)
assert !topics.many?
end
@@ -308,7 +272,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_should_use_where_in_query_for_scope
- assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set
+ assert_equal Developer.where(name: 'Jamis').to_set, Developer.where(id: Developer.jamises).to_set
end
def test_size_should_use_count_when_results_are_not_loaded
@@ -334,7 +298,7 @@ class NamedScopeTest < ActiveRecord::TestCase
def test_chaining_with_duplicate_joins
join = "INNER JOIN comments ON comments.post_id = posts.id"
post = Post.find(1)
- assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
+ assert_equal post.comments.size, Post.joins(join).joins(join).where("posts.id = #{post.id}").size
end
def test_chaining_should_use_latest_conditions_when_creating
@@ -460,26 +424,11 @@ class NamedScopeTest < ActiveRecord::TestCase
klass.table_name = 'posts'
assert_deprecated do
- klass.scope :welcome, { :conditions => { :id => posts(:welcome).id } }
- end
- assert_equal [posts(:welcome).title], klass.welcome.map(&:title)
-
- assert_deprecated do
klass.scope :welcome_2, klass.where(:id => posts(:welcome).id)
end
assert_equal [posts(:welcome).title], klass.welcome_2.map(&:title)
end
- def test_eager_default_scope_hashes_are_deprecated
- klass = Class.new(ActiveRecord::Base)
- klass.table_name = 'posts'
-
- assert_deprecated do
- klass.send(:default_scope, :conditions => { :id => posts(:welcome).id })
- end
- assert_equal [posts(:welcome).title], klass.all.map(&:title)
- end
-
def test_eager_default_scope_relations_are_deprecated
klass = Class.new(ActiveRecord::Base)
klass.table_name = 'posts'
@@ -490,41 +439,3 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal [posts(:welcome).title], klass.all.map(&:title)
end
end
-
-class DynamicScopeMatchTest < ActiveRecord::TestCase
- def test_scoped_by_no_match
- assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
- end
-
- def test_scoped_by
- match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.scope?
- assert_equal %w(age sex location), match.attribute_names
- end
-end
-
-class DynamicScopeTest < ActiveRecord::TestCase
- fixtures :posts
-
- def setup
- @test_klass = Class.new(Post) do
- def self.name; "Post"; end
- end
- end
-
- def test_dynamic_scope
- assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1)
- assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
- end
-
- def test_dynamic_scope_should_create_methods_after_hitting_method_missing
- assert_blank @test_klass.methods.grep(/scoped_by_type/)
- @test_klass.scoped_by_type(nil)
- assert_present @test_klass.methods.grep(/scoped_by_type/)
- end
-
- def test_dynamic_scope_with_less_number_of_arguments
- assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) }
- end
-end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index adfd8e83a1..0933a4ff3d 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -13,12 +13,14 @@ require 'models/warehouse_thing'
require 'models/parrot'
require 'models/minivan'
require 'models/person'
+require 'models/pet'
+require 'models/toy'
require 'rexml/document'
require 'active_support/core_ext/exception'
class PersistencesTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys
# Oracle UPDATE does not support ORDER BY
unless current_adapter?(:OracleAdapter)
@@ -53,7 +55,7 @@ class PersistencesTest < ActiveRecord::TestCase
author = authors(:david)
assert_nothing_raised do
assert_equal 1, author.posts_sorted_by_id_limited.size
- assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size
+ assert_equal 2, author.posts_sorted_by_id_limited.scoped(:limit => 2).all.size
assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
assert_equal "bulk update!", posts(:welcome).body
assert_not_equal "bulk update!", posts(:thinking).body
@@ -76,10 +78,20 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal Topic.count, Topic.delete_all
end
- def test_update_by_condition
- Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
- assert_equal "Have a nice day", Topic.find(1).content
- assert_equal "bulk updated!", Topic.find(2).content
+ def test_delete_all_with_joins_and_where_part_is_hash
+ where_args = {:toys => {:name => 'Bone'}}
+ count = Pet.joins(:toys).where(where_args).count
+
+ assert_equal count, 1
+ assert_equal count, Pet.joins(:toys).where(where_args).delete_all
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_not_hash
+ where_args = ['toys.name = ?', 'Bone']
+ count = Pet.joins(:toys).where(where_args).count
+
+ assert_equal count, 1
+ assert_equal count, Pet.joins(:toys).where(where_args).delete_all
end
def test_increment_attribute
@@ -108,7 +120,7 @@ class PersistencesTest < ActiveRecord::TestCase
def test_destroy_all
conditions = "author_name = 'Mary'"
- topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
+ topics_by_mary = Topic.scoped(:where => conditions, :order => 'id').to_a
assert ! topics_by_mary.empty?
assert_difference('Topic.count', -topics_by_mary.size) do
@@ -119,7 +131,7 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_destroy_many
- clients = Client.find([2, 3], :order => 'id')
+ clients = Client.scoped(:order => 'id').find([2, 3])
assert_difference('Client.count', -2) do
destroyed = Client.destroy([2, 3]).sort_by(&:id)
@@ -320,7 +332,7 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_update_all_with_non_standard_table_name
- assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1])
+ assert_equal 1, WarehouseThing.where(id: 1).update_all(['value = ?', 0])
assert_equal 0, WarehouseThing.find(1).value
end
@@ -393,7 +405,6 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_attribute_with_one_updated
t = Topic.first
- title = t.title
t.update_attribute(:title, 'super_title')
assert_equal 'super_title', t.title
assert !t.changed?, "topic should not have changed"
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index d93478513b..a712e5f689 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -96,7 +96,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_clear_after_close
mw = ActiveRecord::QueryCache.new lambda { |env|
- Post.find(:first)
+ Post.first
[200, {}, nil]
}
body = mw.call({}).last
@@ -233,8 +233,8 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_cache_is_expired_by_habtm_update
ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
ActiveRecord::Base.cache do
- c = Category.find(:first)
- p = Post.find(:first)
+ c = Category.first
+ p = Post.first
p.categories << c
end
end
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index e21109baae..df0399f548 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -23,11 +23,12 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save }
assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
+ assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy }
end
def test_find_with_readonly_option
- Developer.find(:all).each { |d| assert !d.readonly? }
+ Developer.all.each { |d| assert !d.readonly? }
Developer.readonly(false).each { |d| assert !d.readonly? }
Developer.readonly(true).each { |d| assert d.readonly? }
Developer.readonly.each { |d| assert d.readonly? }
@@ -48,7 +49,7 @@ class ReadOnlyTest < ActiveRecord::TestCase
post = Post.find(1)
assert !post.comments.empty?
assert !post.comments.any?(&:readonly?)
- assert !post.comments.find(:all).any?(&:readonly?)
+ assert !post.comments.all.any?(&:readonly?)
assert post.comments.readonly(true).all?(&:readonly?)
end
@@ -70,13 +71,13 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
def test_readonly_scoping
- Post.send(:with_scope, :find => { :conditions => '1=1' }) do
+ Post.where('1=1').scoped do
assert !Post.find(1).readonly?
assert Post.readonly(true).find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
end
- Post.send(:with_scope, :find => { :joins => ' ' }) do
+ Post.joins(' ').scoped do
assert !Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
@@ -85,14 +86,14 @@ class ReadOnlyTest < ActiveRecord::TestCase
# Oracle barfs on this because the join includes unqualified and
# conflicting column names
unless current_adapter?(:OracleAdapter)
- Post.send(:with_scope, :find => { :joins => ', developers' }) do
+ Post.joins(', developers').scoped do
assert Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
end
end
- Post.send(:with_scope, :find => { :readonly => true }) do
+ Post.readonly(true).scoped do
assert Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 16f05f2198..7dd5698dcf 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -63,7 +63,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_column_null_not_null
- subscriber = Subscriber.find(:first)
+ subscriber = Subscriber.first
assert subscriber.column_for_attribute("name").null
assert !subscriber.column_for_attribute("nick").null
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index edf38cb7a3..cf367242f2 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -53,8 +53,8 @@ class RelationScopingTest < ActiveRecord::TestCase
end
def test_scoped_find_last_preserves_scope
- lowest_salary = Developer.first :order => "salary ASC"
- highest_salary = Developer.first :order => "salary DESC"
+ lowest_salary = Developer.order("salary ASC").first
+ highest_salary = Developer.order("salary DESC").first
Developer.order("salary").scoping do
assert_equal highest_salary, Developer.last
@@ -84,7 +84,7 @@ class RelationScopingTest < ActiveRecord::TestCase
def test_scope_select_concatenates
Developer.select("id, name").scoping do
- developer = Developer.select('id, salary').where("name = 'David'").first
+ developer = Developer.select('salary').where("name = 'David'").first
assert_equal 80000, developer.salary
assert developer.has_attribute?(:id)
assert developer.has_attribute?(:name)
@@ -254,11 +254,6 @@ class HasManyScopingTest< ActiveRecord::TestCase
assert_equal 2, @welcome.comments.search_by_type('Comment').size
end
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Comment.find_all_by_type('Comment').size
- assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
- end
-
def test_nested_scope_finder
Comment.where('1=0').scoping do
assert_equal 0, @welcome.comments.count
@@ -300,12 +295,6 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
assert_equal 'a category...', @welcome.categories.what_are_you
end
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Category.find_all_by_type('SpecialCategory').size
- assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
- assert_equal 2, @welcome.categories.find_all_by_type('Category').size
- end
-
def test_nested_scope_finder
Category.where('1=0').scoping do
assert_equal 0, @welcome.categories.count
@@ -323,8 +312,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts
def test_default_scope
- expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ expected = Developer.scoped(:order => 'salary DESC').all.collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary }
assert_equal expected, received
end
@@ -362,12 +351,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
+ assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
assert_equal nil, DeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
+ assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
end
@@ -395,23 +384,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 50000, wheres[:salary]
end
- def test_method_scope
- expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
- assert_equal expected, received
- end
-
- def test_nested_scope
- expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- end
- assert_equal expected, received
- end
-
def test_scope_overwrites_default
- expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
+ expected = Developer.scoped(:order => 'salary DESC, name DESC').all.collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.by_name.all.collect { |dev| dev.name }
assert_equal expected, received
end
@@ -427,17 +402,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_nested_exclusive_scope
- expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- end
- assert_equal expected, received
- end
-
def test_order_in_default_scope_should_prevail
- expected = Developer.find(:all, :order => 'salary desc').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
+ expected = Developer.scoped(:order => 'salary desc').all.collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.scoped(:order => 'salary').all.collect { |dev| dev.salary }
assert_equal expected, received
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index ac6dee3c6a..89f818a689 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -19,33 +19,12 @@ module ActiveRecord
assert !relation.loaded, 'relation is not loaded'
end
- def test_single_values
- assert_equal [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq].map(&:to_s).sort,
- Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort
- end
-
def test_initialize_single_values
relation = Relation.new :a, :b
- Relation::SINGLE_VALUE_METHODS.each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
- end
-
- def test_association_methods
- assert_equal [:includes, :eager_load, :preload].map(&:to_s).sort,
- Relation::ASSOCIATION_METHODS.map(&:to_s).sort
- end
-
- def test_initialize_association_methods
- relation = Relation.new :a, :b
- Relation::ASSOCIATION_METHODS.each do |method|
- assert_equal [], relation.send("#{method}_values"), method.to_s
- end
- end
-
- def test_multi_value_methods
- assert_equal [:select, :group, :order, :joins, :where, :having, :bind, :references].map(&:to_s).sort,
- Relation::MULTI_VALUE_METHODS.map(&:to_s).sort
+ assert_equal({}, relation.create_with_value)
end
def test_multi_value_initialize
@@ -64,19 +43,19 @@ module ActiveRecord
relation = Relation.new :a, :b
assert_equal({}, relation.where_values_hash)
- relation.where_values << :hello
+ relation.where! :hello
assert_equal({}, relation.where_values_hash)
end
def test_has_values
relation = Relation.new Post, Post.arel_table
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
assert_equal({:id => 10}, relation.where_values_hash)
end
def test_values_wrong_table
relation = Relation.new Post, Post.arel_table
- relation.where_values << Comment.arel_table[:id].eq(10)
+ relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
@@ -85,7 +64,7 @@ module ActiveRecord
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
- relation.where_values << combine
+ relation.where! combine
assert_equal({}, relation.where_values_hash)
end
@@ -108,7 +87,7 @@ module ActiveRecord
def test_create_with_value_with_wheres
relation = Relation.new Post, Post.arel_table
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
end
@@ -118,7 +97,7 @@ module ActiveRecord
relation = Relation.new Post, Post.arel_table
assert_equal({}, relation.scope_for_create)
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
relation.create_with_value = {:hello => 'world'}
@@ -132,7 +111,7 @@ module ActiveRecord
def test_eager_load_values
relation = Relation.new :a, :b
- relation.eager_load_values << :b
+ relation.eager_load! :b
assert relation.eager_loading?
end
@@ -149,10 +128,121 @@ module ActiveRecord
assert_equal ['foo'], relation.references_values
end
- def test_apply_finder_options_takes_references
+ test 'merging a hash into a relation' do
relation = Relation.new :a, :b
- relation = relation.apply_finder_options(:references => :foo)
- assert_equal ['foo'], relation.references_values
+ relation = relation.merge where: :lol, readonly: true
+
+ assert_equal [:lol], relation.where_values
+ assert_equal true, relation.readonly_value
+ end
+
+ test 'merging an empty hash into a relation' do
+ assert_equal [], Relation.new(:a, :b).merge({}).where_values
+ end
+
+ test 'merging a hash with unknown keys raises' do
+ assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') }
+ end
+
+ test '#values returns a dup of the values' do
+ relation = Relation.new(:a, :b).where! :foo
+ values = relation.values
+
+ values[:where] = nil
+ assert_not_nil relation.where_values
+ end
+
+ test 'relations can be created with a values hash' do
+ relation = Relation.new(:a, :b, where: [:foo])
+ assert_equal [:foo], relation.where_values
+ end
+
+ test 'merging a single where value' do
+ relation = Relation.new(:a, :b)
+ relation.merge!(where: :foo)
+ assert_equal [:foo], relation.where_values
+ end
+
+ test 'merging a hash interpolates conditions' do
+ klass = stub
+ klass.stubs(:sanitize_sql).with(['foo = ?', 'bar']).returns('foo = bar')
+
+ relation = Relation.new(klass, :b)
+ relation.merge!(where: ['foo = ?', 'bar'])
+ assert_equal ['foo = bar'], relation.where_values
+ end
+ end
+
+ class RelationMutationTest < ActiveSupport::TestCase
+ def relation
+ @relation ||= Relation.new :a, :b
+ end
+
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal [:foo], relation.public_send("#{method}_values")
+ end
+ end
+
+ test '#references!' do
+ assert relation.references!(:foo).equal?(relation)
+ assert relation.references_values.include?('foo')
+ end
+
+ test 'extending!' do
+ mod = Module.new
+
+ assert relation.extending!(mod).equal?(relation)
+ assert [mod], relation.extending_values
+ assert relation.is_a?(mod)
+ end
+
+ test 'extending! with empty args' do
+ relation.extending!
+ assert_equal [], relation.extending_values
+ end
+
+ (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal :foo, relation.public_send("#{method}_value")
+ end
+ end
+
+ test '#from!' do
+ assert relation.from!('foo').equal?(relation)
+ assert_equal ['foo', nil], relation.from_value
+ end
+
+ test '#lock!' do
+ assert relation.lock!('foo').equal?(relation)
+ assert_equal 'foo', relation.lock_value
+ end
+
+ test '#reorder!' do
+ relation = self.relation.order('foo')
+
+ assert relation.reorder!('bar').equal?(relation)
+ assert_equal ['bar'], relation.order_values
+ assert relation.reordering_value
+ end
+
+ test 'reverse_order!' do
+ assert relation.reverse_order!.equal?(relation)
+ assert relation.reverse_order_value
+ relation.reverse_order!
+ assert !relation.reverse_order_value
+ end
+
+ test 'create_with!' do
+ assert relation.create_with!(foo: 'bar').equal?(relation)
+ assert_equal({foo: 'bar'}, relation.create_with_value)
+ end
+
+ test 'merge!' do
+ assert relation.merge!(where: :foo).equal?(relation)
+ assert_equal [:foo], relation.where_values
end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 25eb7c1672..4a56ae0d23 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -133,6 +133,13 @@ class RelationTest < ActiveRecord::TestCase
assert topics.loaded?
end
+ def test_finiding_with_subquery
+ relation = Topic.where(:approved => true)
+ assert_equal relation.to_a, Topic.select('*').from(relation).to_a
+ assert_equal relation.to_a, Topic.select('subquery.*').from(relation).to_a
+ assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a
+ end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name)
@@ -219,6 +226,7 @@ class RelationTest < ActiveRecord::TestCase
assert_no_queries do
assert_equal [], Developer.none
assert_equal [], Developer.scoped.none
+ assert Developer.none.is_a?(ActiveRecord::NullRelation)
end
end
@@ -228,6 +236,38 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_none_chained_to_methods_firing_queries_straight_to_db
+ assert_no_queries do
+ assert_equal [], Developer.none.pluck(:id) # => uses select_all
+ assert_equal 0, Developer.none.delete_all
+ assert_equal 0, Developer.none.update_all(:name => 'David')
+ assert_equal 0, Developer.none.delete(1)
+ assert_equal false, Developer.none.exists?(1)
+ end
+ end
+
+ def test_null_relation_content_size_methods
+ assert_no_queries do
+ assert_equal 0, Developer.none.size
+ assert_equal 0, Developer.none.count
+ assert_equal true, Developer.none.empty?
+ assert_equal false, Developer.none.any?
+ assert_equal false, Developer.none.many?
+ end
+ end
+
+ def test_null_relation_calculations_methods
+ assert_no_queries do
+ assert_equal 0, Developer.none.count
+ assert_equal nil, Developer.none.calculate(:average, 'salary')
+ end
+ end
+
+ def test_null_relation_metadata_methods
+ assert_equal "", Developer.none.to_sql
+ assert_equal({}, Developer.none.where_values_hash)
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -249,7 +289,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_find_on_hash_conditions
- assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.where({ :approved => false }).to_a
+ assert_equal Topic.scoped(:where => {:approved => false}).all, Topic.where({ :approved => false }).to_a
end
def test_joins_with_string_array
@@ -297,7 +337,6 @@ class RelationTest < ActiveRecord::TestCase
end
def test_respond_to_class_methods_and_scopes
- assert DeveloperOrderedBySalary.scoped.respond_to?(:all_ordered_by_name)
assert Topic.scoped.respond_to?(:by_lifo)
end
@@ -369,18 +408,18 @@ class RelationTest < ActiveRecord::TestCase
end
def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
+ assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
assert_nil DeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
+ assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
end
def test_default_scoping_finder_methods
developers = DeveloperCalledDavid.order('id').map(&:id).sort
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, developers
+ assert_equal Developer.where(name: 'David').map(&:id).sort, developers
end
def test_loading_with_one_association
@@ -404,15 +443,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.find(1).last_comment, post.last_comment
end
- def test_dynamic_find_by_attributes_should_yield_found_object
- david = authors(:david)
- yielded_value = nil
- Author.find_by_name(david.name) do |author|
- yielded_value = author
- end
- assert_equal david, yielded_value
- end
-
def test_dynamic_find_by_attributes
david = authors(:david)
author = Author.preload(:taggings).find_by_id(david.id)
@@ -434,46 +464,6 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Author.scoped.find_by_id_and_name!(20, 'invalid') }
end
- def test_dynamic_find_all_by_attributes
- authors = Author.scoped
-
- davids = authors.find_all_by_name('David')
- assert_kind_of Array, davids
- assert_equal [authors(:david)], davids
- end
-
- def test_dynamic_find_or_initialize_by_attributes
- authors = Author.scoped
-
- lifo = authors.find_or_initialize_by_name('Lifo')
- assert_equal "Lifo", lifo.name
- assert !lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David')
- end
-
- def test_dynamic_find_or_create_by_attributes
- authors = Author.scoped
-
- lifo = authors.find_or_create_by_name('Lifo')
- assert_equal "Lifo", lifo.name
- assert lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David')
- end
-
- def test_dynamic_find_or_create_by_attributes_bang
- authors = Author.scoped
-
- assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') }
-
- lifo = authors.find_or_create_by_name!('Lifo')
- assert_equal "Lifo", lifo.name
- assert lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David')
- end
-
def test_find_id
authors = Author.scoped
@@ -612,6 +602,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! davids.exists?(authors(:mary).id)
assert ! davids.exists?("42")
assert ! davids.exists?(42)
+ assert ! davids.exists?(davids.new)
fake = Author.where(:name => 'fake author')
assert ! fake.exists?
@@ -656,6 +647,10 @@ class RelationTest < ActiveRecord::TestCase
assert davids.loaded?
end
+ def test_delete_all_limit_error
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
+ end
+
def test_select_argument_error
assert_raises(ArgumentError) { Developer.select }
end
@@ -1027,10 +1022,6 @@ 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
@@ -1042,10 +1033,6 @@ 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
@@ -1067,36 +1054,26 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.order(Post.arel_table[:title]).all, Post.order("title").all
end
- def test_order_with_find_with_order
- assert_equal 'zyke', CoolCar.order('name desc').find(:first, :order => 'id').name
- assert_equal 'zyke', FastCar.order('name desc').find(:first, :order => 'id').name
- end
-
def test_default_scope_order_with_scope_order
assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name
- assert_equal 'zyke', CoolCar.order_using_old_style.limit(1).first.name
assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name
- assert_equal 'zyke', FastCar.order_using_old_style.limit(1).first.name
end
def test_order_using_scoping
car1 = CoolCar.order('id DESC').scoping do
- CoolCar.find(:first, :order => 'id asc')
+ CoolCar.scoped(:order => 'id asc').first
end
assert_equal 'zyke', car1.name
car2 = FastCar.order('id DESC').scoping do
- FastCar.find(:first, :order => 'id asc')
+ FastCar.scoped(:order => 'id asc').first
end
assert_equal 'zyke', car2.name
end
def test_unscoped_block_style
assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name}
- assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_old_style.limit(1).first.name}
-
assert_equal 'honda', FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name}
- assert_equal 'honda', FastCar.unscoped { FastCar.order_using_old_style.limit(1).first.name}
end
def test_intersection_with_array
@@ -1107,10 +1084,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [rails_author], relation & [rails_author]
end
- def test_removing_limit_with_options
- assert_not_equal 1, Post.limit(1).all(:limit => nil).count
- end
-
def test_primary_key
assert_equal "id", Post.scoped.primary_key
end
@@ -1273,6 +1246,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal nil, Post.scoped.find_by("1 = 0")
end
+ test "find_by doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by(author_id: 2) }
+ end
+
test "find_by! with hash conditions returns the first matching record" do
assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2)
end
@@ -1285,6 +1262,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2)
end
+ test "find_by! doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(author_id: 2) }
+ end
+
test "find_by! raises RecordNotFound if the record is missing" do
assert_raises(ActiveRecord::RecordNotFound) do
Post.scoped.find_by!("1 = 0")
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 15ceaa1fcc..ab80dd1d6d 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -236,6 +236,27 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dump_includes_inet_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_network_address"} =~ output
+ assert_match %r{t.inet "inet_address"}, output
+ end
+ end
+
+ def test_schema_dump_includes_cidr_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_network_address"} =~ output
+ assert_match %r{t.cidr "cidr_address"}, output
+ end
+ end
+
+ def test_schema_dump_includes_macaddr_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_network_address"} =~ output
+ assert_match %r{t.macaddr "macaddr_address"}, output
+ end
+ end
+
def test_schema_dump_includes_hstores_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_hstores"} =~ output
diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass_test.rb
index 7402b2afd6..6749d4ce98 100644
--- a/activerecord/test/cases/session_store/sql_bypass.rb
+++ b/activerecord/test/cases/session_store/sql_bypass_test.rb
@@ -18,6 +18,11 @@ module ActiveRecord
assert !Session.table_exists?
end
+ def test_new_record?
+ s = SqlBypass.new :data => 'foo', :session_id => 10
+ assert s.new_record?, 'this is a new record!'
+ end
+
def test_persisted?
s = SqlBypass.new :data => 'foo', :session_id => 10
assert !s.persisted?, 'this is a new record!'
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 40520d6da2..e1d0f1f799 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -4,7 +4,7 @@ require 'models/admin/user'
class StoreTest < ActiveRecord::TestCase
setup do
- @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true)
+ @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => true)
end
test "reading store attributes through accessors" do
@@ -40,4 +40,38 @@ class StoreTest < ActiveRecord::TestCase
@john.remember_login = false
assert_equal false, @john.remember_login
end
+
+ test "reading store attributes through accessors encoded with JSON" do
+ assert_equal 'tall', @john.height
+ assert_nil @john.weight
+ end
+
+ test "writing store attributes through accessors encoded with JSON" do
+ @john.height = 'short'
+ @john.weight = 'heavy'
+
+ assert_equal 'short', @john.height
+ assert_equal 'heavy', @john.weight
+ end
+
+ test "accessing attributes not exposed by accessors encoded with JSON" do
+ @john.json_data['somestuff'] = 'somecoolstuff'
+ @john.save
+
+ assert_equal 'somecoolstuff', @john.reload.json_data['somestuff']
+ end
+
+ test "updating the store will mark it as changed encoded with JSON" do
+ @john.height = 'short'
+ assert @john.json_data_changed?
+ end
+
+ test "object initialization with not nullable column encoded with JSON" do
+ assert_equal true, @john.is_a_good_guy
+ end
+
+ test "writing with not nullable column encoded with JSON" do
+ @john.is_a_good_guy = false
+ assert_equal false, @john.is_a_good_guy
+ end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index f8b3e01a49..961ba8d9ba 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -287,6 +287,74 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase
raise ActiveRecord::Rollback
end
+ assert topic.id.nil?
+ assert !topic.persisted?
assert_equal %w{ after_rollback }, topic.history
end
+
+ class TopicWithManualRollbackObserverAttached < ActiveRecord::Base
+ self.table_name = :topics
+ def history
+ @history ||= []
+ end
+ end
+
+ class TopicWithManualRollbackObserverAttachedObserver < ActiveRecord::Observer
+ def after_save(record)
+ record.history.push "after_save"
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def test_after_save_called_with_manual_rollback
+ assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer'
+
+ topic = TopicWithManualRollbackObserverAttached.new
+
+ assert !topic.save
+ assert_equal nil, topic.id
+ assert !topic.persisted?
+ assert_equal %w{ after_save }, topic.history
+ end
+ def test_after_save_called_with_manual_rollback_bang
+ assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer'
+
+ topic = TopicWithManualRollbackObserverAttached.new
+
+ topic.save!
+ assert_equal nil, topic.id
+ assert !topic.persisted?
+ assert_equal %w{ after_save }, topic.history
+ end
+end
+
+class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class TopicWithSaveInCallback < ActiveRecord::Base
+ self.table_name = :topics
+ after_commit :cache_topic, :on => :create
+ after_commit :call_update, :on => :update
+ attr_accessor :cached, :record_updated
+
+ def call_update
+ self.record_updated = true
+ end
+
+ def cache_topic
+ unless cached
+ self.cached = true
+ self.save
+ else
+ self.cached = false
+ end
+ end
+ end
+
+ def test_after_commit_in_save
+ topic = TopicWithSaveInCallback.new()
+ topic.save
+ assert_equal true, topic.cached
+ assert_equal true, topic.record_updated
+ end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index c72b7b35cd..7ac34bc71e 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -86,7 +86,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert !r.valid?
assert r.errors[:topic].any?
- r.topic = Topic.find :first
+ r.topic = Topic.first
assert r.valid?
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index ec09479c95..c173ee9a15 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -189,7 +189,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t_utf8.save, "Should save t_utf8 as unique"
# If database hasn't UTF-8 character set, this test fails
- if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!"
+ if Topic.scoped(:select => 'LOWER(title) AS title').find(t_utf8).title == "я тоже уникальный!"
t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
assert !t2_utf8.valid?, "Shouldn't be valid"
assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
@@ -261,10 +261,10 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert i1.errors[:value].any?, "Should not be empty"
end
- def test_validates_uniqueness_inside_with_scope
+ def test_validates_uniqueness_inside_scoping
Topic.validates_uniqueness_of(:title)
- Topic.send(:with_scope, :find => { :conditions => { :author_name => "David" } }) do
+ Topic.where(:author_name => "David").scoping do
t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
assert t1.save
t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index e575a98170..b11b330374 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -93,30 +93,6 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
- def test_scoped_create_without_attributes
- WrongReply.send(:with_scope, :create => {}) do
- assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
- end
- end
-
- def test_create_with_exceptions_using_scope_for_protected_attributes
- assert_nothing_raised do
- ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do
- person = ProtectedPerson.create! :addon => "Addon"
- assert_equal person.first_name, "Mary", "scope should ignore attr_protected"
- end
- end
- end
-
- def test_create_with_exceptions_using_scope_and_empty_attributes
- assert_nothing_raised do
- ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do
- person = ProtectedPerson.create!
- assert_equal person.first_name, "Mary", "should be ok when no attributes are passed to create!"
- end
- end
- end
-
def test_create_without_validation
reply = WrongReply.new
assert !reply.save
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index d0e628bd50..ad30039304 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -2,4 +2,6 @@ class Admin::User < ActiveRecord::Base
belongs_to :account
store :settings, :accessors => [ :color, :homepage ]
store :preferences, :accessors => [ :remember_login ]
+ store :json_data, :accessors => [ :height, :weight ], :coder => JSON
+ store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => JSON
end
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index 42ac81690f..b4bc0ad5fa 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -15,7 +15,6 @@ class Car < ActiveRecord::Base
scope :incl_engines, -> { includes(:engines) }
scope :order_using_new_style, -> { order('name asc') }
- scope :order_using_old_style, -> { { :order => 'name asc' } }
end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 00f5a74070..3e9f1b0635 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -9,6 +9,8 @@ class Comment < ActiveRecord::Base
belongs_to :post, :counter_cache => true
has_many :ratings
+ belongs_to :first_post, :foreign_key => :post_id
+
has_many :children, :class_name => 'Comment', :foreign_key => :parent_id
belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count
@@ -17,7 +19,7 @@ class Comment < ActiveRecord::Base
end
def self.search_by_type(q)
- self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q])
+ self.scoped(:where => ["#{QUOTED_TYPE} = ?", q]).all
end
def self.all_as_method
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index fbdfaa2c29..7b993d5a2c 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -198,6 +198,11 @@ class Account < ActiveRecord::Base
@destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] }
end
+ # Test private kernel method through collection proxy using has_many.
+ def self.open
+ where('firm_name = ?', '37signals')
+ end
+
before_destroy do |account|
if account.firm
Account.destroyed_account_ids[account.firm.id] << account.id
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 4cc4947e3b..83482f4d07 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -2,20 +2,20 @@ require 'ostruct'
module DeveloperProjectsAssociationExtension
def find_most_recent
- find(:first, :order => "id DESC")
+ scoped(:order => "id DESC").first
end
end
module DeveloperProjectsAssociationExtension2
def find_least_recent
- find(:first, :order => "id ASC")
+ scoped(:order => "id ASC").first
end
end
class Developer < ActiveRecord::Base
has_and_belongs_to_many :projects do
def find_most_recent
- find(:first, :order => "id DESC")
+ scoped(:order => "id DESC").first
end
end
@@ -37,7 +37,7 @@ class Developer < ActiveRecord::Base
:association_foreign_key => "project_id",
:extend => DeveloperProjectsAssociationExtension do
def find_least_recent
- find(:first, :order => "id ASC")
+ scoped(:order => "id ASC").first
end
end
@@ -57,12 +57,6 @@ class Developer < ActiveRecord::Base
def log=(message)
audit_logs.build :message => message
end
-
- def self.all_johns
- self.with_exclusive_scope :find => where(:name => 'John') do
- self.all
- end
- end
end
class AuditLog < ActiveRecord::Base
@@ -102,12 +96,6 @@ class DeveloperOrderedBySalary < ActiveRecord::Base
default_scope { order('salary DESC') }
scope :by_name, -> { order('name DESC') }
-
- def self.all_ordered_by_name
- with_scope(:find => { :order => 'name DESC' }) do
- find(:all)
- end
- end
end
class DeveloperCalledDavid < ActiveRecord::Base
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 5002ab9ff8..1aaf9a1b82 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -9,11 +9,6 @@ class Post < ActiveRecord::Base
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 ],
- :joins => 'JOIN authors ON authors.id = posts.author_id'
- }
- }
belongs_to :author do
def greeting
@@ -32,13 +27,11 @@ class Post < ActiveRecord::Base
scope :with_special_comments, -> { joins(:comments).where(:comments => {:type => 'SpecialComment'}) }
scope :with_very_special_comments, -> { joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) }
- scope :with_post, lambda {|post_id|
- { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } }
- }
+ scope :with_post, ->(post_id) { joins(:comments).where(:comments => { :post_id => post_id }) }
has_many :comments do
def find_most_recent
- find(:first, :order => "id DESC")
+ scoped(:order => "id DESC").first
end
def newest
@@ -71,8 +64,8 @@ class Post < ActiveRecord::Base
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings do
def add_joins_and_select
- find :all, :select => 'tags.*, authors.id as author_id',
- :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
+ scoped(:select => 'tags.*, authors.id as author_id',
+ :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id').all
end
end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 785839be75..079e403444 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -2,7 +2,7 @@ class Topic < ActiveRecord::Base
scope :base, -> { scoped }
scope :written_before, lambda { |time|
if time
- { :conditions => ['written_on < ?', time] }
+ where 'written_on < ?', time
end
}
scope :approved, -> { where(:approved => true) }
@@ -11,11 +11,7 @@ class Topic < ActiveRecord::Base
scope :scope_with_lambda, lambda { scoped }
scope :by_lifo, -> { where(:author_name => 'lifo') }
-
- ActiveSupport::Deprecation.silence do
- scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
- scope :replied, :conditions => ['replies_count > 0']
- end
+ scope :replied, -> { where 'replies_count > 0' }
scope 'approved_as_string', -> { where(:approved => true) }
scope :anonymous_extension, -> { scoped } do
@@ -35,18 +31,6 @@ class Topic < ActiveRecord::Base
2
end
end
- module MultipleExtensionOne
- def extension_one
- 1
- end
- end
- module MultipleExtensionTwo
- def extension_two
- 2
- end
- end
- scope :named_extension, -> { { :extend => NamedExtension } }
- scope :multiple_extensions, -> { { :extend => [MultipleExtensionTwo, MultipleExtensionOne] } }
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 25b416a906..e51db50ae3 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,8 +1,8 @@
ActiveRecord::Schema.define do
%w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
- postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones).each do |table_name|
- execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
+ postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
+ execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
execute 'DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE'
@@ -10,6 +10,8 @@ ActiveRecord::Schema.define do
execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')"
execute 'DROP SEQUENCE IF EXISTS companies_id_seq'
+ execute 'DROP FUNCTION IF EXISTS partitioned_insert_trigger()'
+
%w(accounts_id_seq developers_id_seq projects_id_seq topics_id_seq customers_id_seq orders_id_seq).each do |seq_name|
execute "SELECT setval('#{seq_name}', 100)"
end
@@ -125,6 +127,37 @@ _SQL
);
_SQL
+begin
+ execute <<_SQL
+ CREATE TABLE postgresql_partitioned_table_parent (
+ id SERIAL PRIMARY KEY,
+ number integer
+ );
+ CREATE TABLE postgresql_partitioned_table ( )
+ INHERITS (postgresql_partitioned_table_parent);
+
+ CREATE OR REPLACE FUNCTION partitioned_insert_trigger()
+ RETURNS TRIGGER AS $$
+ BEGIN
+ INSERT INTO postgresql_partitioned_table VALUES (NEW.*);
+ RETURN NULL;
+ END;
+ $$
+ LANGUAGE plpgsql;
+
+ CREATE TRIGGER insert_partitioning_trigger
+ BEFORE INSERT ON postgresql_partitioned_table_parent
+ FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger();
+_SQL
+rescue ActiveRecord::StatementInvalid => e
+ if e.message =~ /language "plpgsql" does not exist/
+ execute "CREATE LANGUAGE 'plpgsql';"
+ retry
+ else
+ raise e
+ end
+end
+
begin
execute <<_SQL
CREATE TABLE postgresql_xml_data_type (
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 377fde5c96..5bcb9652cd 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -38,7 +38,11 @@ ActiveRecord::Schema.define do
create_table :admin_users, :force => true do |t|
t.string :name
t.text :settings, :null => true
- t.text :preferences, :null => false, :default => ""
+ # MySQL does not allow default values for blobs. Fake it out with a
+ # big varchar below.
+ t.string :preferences, :null => false, :default => '', :limit => 1024
+ t.string :json_data, :null => true, :limit => 1024
+ t.string :json_data_empty, :null => false, :default => "", :limit => 1024
t.references :account
end
@@ -76,6 +80,7 @@ ActiveRecord::Schema.define do
create_table :binaries, :force => true do |t|
t.string :name
t.binary :data
+ t.binary :short_data, :limit => 2048
end
create_table :birds, :force => true do |t|
@@ -173,6 +178,7 @@ ActiveRecord::Schema.define do
t.integer :client_of
t.integer :rating, :default => 1
t.integer :account_id
+ t.string :description, :null => false, :default => ""
end
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
@@ -426,6 +432,7 @@ ActiveRecord::Schema.define do
t.string :name
t.column :updated_at, :datetime
t.column :happy_at, :datetime
+ t.column :eats_at, :time
t.string :essay_id
end
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
index ea05b35fe0..e9ddeb32cf 100644
--- a/activerecord/test/schema/sqlite_specific_schema.rb
+++ b/activerecord/test/schema/sqlite_specific_schema.rb
@@ -1,5 +1,5 @@
ActiveRecord::Schema.define do
- # For sqlite 3.1.0+, make a table with a autoincrement column
+ # For sqlite 3.1.0+, make a table with an autoincrement column
if supports_autoincrement?
create_table :table_with_autoincrement, :force => true do |t|
t.column :name, :string
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 8165b89cde..636aca2b8f 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,30 +1,56 @@
## Rails 4.0.0 (unreleased) ##
-* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev*
+* `Object#try` can't call private methods. *Vasiliy Ermolovich*
-* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea*
+* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez*
-* AS::Callbacks: `:per_key` option is no longer supported
+* `deep_dup` works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances. *Alexey Gaziev*
-* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option.
+* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan*
-* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva*
+* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo*
-* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva*
+* Make Module#delegate stop using `send` - can no longer delegate to private methods. *dasch*
-* Deprecates the compatibility method Module#local_constant_names,
- use Module#local_constants instead (which returns symbols). *fxn*
+* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev*
-* Deletes the compatibility method Module#method_names,
- use Module#methods from now on (which returns symbols). *fxn*
+* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea*
-* Deletes the compatibility method Module#instance_method_names,
- use Module#instance_methods from now on (which returns symbols). *fxn*
+* AS::Callbacks: `:per_key` option is no longer supported
-* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger
- from Ruby stdlib.
+* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option.
-* Unicode database updated to 6.1.0.
+* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva*
+
+* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva*
+
+* Deprecates the compatibility method Module#local_constant_names,
+ use Module#local_constants instead (which returns symbols). *fxn*
+
+* Deletes the compatibility method Module#method_names,
+ use Module#methods from now on (which returns symbols). *fxn*
+
+* Deletes the compatibility method Module#instance_method_names,
+ use Module#instance_methods from now on (which returns symbols). *fxn*
+
+* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger
+ from Ruby stdlib.
+
+* Unicode database updated to 6.1.0.
+
+* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead
+ of wrapping them in strings for safety.
+
+
+## Rails 3.2.4 (unreleased) ##
+
+* Added #beginning_of_hour and #end_of_hour to Time and DateTime core
+ extensions. *Mark J. Titorenko*
+
+
+## Rails 3.2.3 (March 30, 2012) ##
+
+* No changes.
## Rails 3.2.2 (March 1, 2012) ##
@@ -219,7 +245,7 @@
* Hash.from_xml no longer loses attributes on tags containing only whitespace *André Arko*
-## Rails 3.0.6 (April 5, 2011) ##
+## Rails 3.0.6 (April 5, 2011) ##
* No changes.
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 822c9d98ae..822c9d98ae 100755..100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index d26d71b615..2c874e932e 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -19,6 +19,6 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
s.add_dependency('i18n', '~> 0.6')
- s.add_dependency('multi_json', '~> 1.0')
+ s.add_dependency('multi_json', '~> 1.3')
s.add_dependency('tzinfo', '~> 0.3.31')
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index dbf0c25c5c..8f018dcbc6 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -22,20 +22,6 @@
#++
require 'securerandom'
-
-module ActiveSupport
- class << self
- attr_accessor :load_all_hooks
- def on_load_all(&hook) load_all_hooks << hook end
- def load_all!; load_all_hooks.each { |hook| hook.call } end
- end
- self.load_all_hooks = []
-
- on_load_all do
- [Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte]
- end
-end
-
require "active_support/dependencies/autoload"
require "active_support/version"
require "active_support/logger"
@@ -44,6 +30,7 @@ module ActiveSupport
extend ActiveSupport::Autoload
autoload :Concern
+ autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
autoload :LogSubscriber
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index e97bb25b9f..7c3a41288b 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -8,8 +8,6 @@ module ActiveSupport
# instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the
# backtrace, so that you can focus on the rest.
#
- # ==== Example:
- #
# bc = BacktraceCleaner.new
# bc.add_filter { |line| line.gsub(Rails.root, '') }
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
@@ -42,8 +40,6 @@ module ActiveSupport
# Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter.
#
- # Example:
- #
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
def add_filter(&block)
@@ -53,8 +49,6 @@ module ActiveSupport
# Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from
# the clean backtrace.
#
- # Example:
- #
# # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
# backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
def add_silencer(&block)
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index cc94041a1d..f149a7f0ed 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -35,7 +35,7 @@ module ActiveSupport
options[:level] ||= :info
result = nil
- ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
+ ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
result
else
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index b9f196d7a9..55791bfa56 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -280,7 +280,7 @@ module ActiveSupport
end
end
if entry && entry.expired?
- race_ttl = options[:race_condition_ttl].to_f
+ race_ttl = options[:race_condition_ttl].to_i
if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
entry.expires_at = Time.now + race_ttl
write_entry(key, entry, :expires_in => race_ttl * 2)
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 8e6a3bc5a8..89bdb741d0 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/object/inclusion'
-require 'rack/utils'
+require 'uri/common'
module ActiveSupport
module Cache
@@ -23,7 +23,7 @@ module ActiveSupport
end
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS)}
+ root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS + [".gitkeep"])}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
end
@@ -126,7 +126,7 @@ module ActiveSupport
# Translate a key into a file path.
def key_file_path(key)
- fname = Rack::Utils.escape(key)
+ fname = URI.encode_www_form_component(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
dir_2 = hash.modulo(0x1000)
@@ -144,7 +144,7 @@ module ActiveSupport
# Translate a file path into a key.
def file_path_key(path)
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
- Rack::Utils.unescape(fname)
+ URI.decode_www_form_component(fname, Encoding::UTF_8)
end
# Delete empty directories in the cache.
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index b15bb42c88..7fd5e3b53d 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -137,6 +137,7 @@ module ActiveSupport
def write_entry(key, entry, options) # :nodoc:
synchronize do
old_entry = @data[key]
+ return false if @data.key?(key) && options[:unless_exist]
@cache_size -= old_entry.size if old_entry
@cache_size += entry.size
@key_access[key] = Time.now.to_f
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 6e36edee4f..a9253c186d 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -23,8 +23,6 @@ module ActiveSupport
# methods, procs or lambdas, or callback objects that respond to certain predetermined
# methods. See +ClassMethods.set_callback+ for details.
#
- # ==== Example
- #
# class Record
# include ActiveSupport::Callbacks
# define_callbacks :save
@@ -54,7 +52,6 @@ module ActiveSupport
# saving...
# - save
# saved
- #
module Callbacks
extend Concern
@@ -73,10 +70,9 @@ module ActiveSupport
# run_callbacks :save do
# save
# end
- #
- def run_callbacks(kind, key = nil, &block)
- #TODO: deprecate key argument
- self.class.__run_callbacks(kind, self, &block)
+ def run_callbacks(kind, &block)
+ runner_name = self.class.__define_callbacks(kind, self)
+ send(runner_name, &block)
end
private
@@ -187,7 +183,7 @@ module ActiveSupport
# Compile around filters with conditions into proxy methods
# that contain the conditions.
#
- # For `around_save :filter_name, :if => :condition':
+ # For `set_callback :save, :around, :filter_name, :if => :condition':
#
# def _conditional_callback_save_17
# if condition
@@ -198,7 +194,6 @@ module ActiveSupport
# yield self
# end
# end
- #
def define_conditional_callback
name = "_conditional_callback_#{@kind}_#{next_id}"
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
@@ -252,7 +247,6 @@ module ActiveSupport
# Objects::
# a method is created that calls the before_foo method
# on the object.
- #
def _compile_filter(filter)
method_name = "_callback_#{@kind}_#{next_id}"
case filter
@@ -316,43 +310,33 @@ module ActiveSupport
method << "value = nil"
method << "halted = false"
- callbacks = "value = yield if block_given? && !halted"
+ callbacks = "value = !halted && (!block_given? || yield)"
reverse_each do |callback|
callbacks = callback.apply(callbacks)
end
method << callbacks
- method << "halted ? false : (block_given? ? value : true)"
- method.flatten.compact.join("\n")
+ method << "value"
+ method.join("\n")
end
end
module ClassMethods
- # This method runs callback chain for the given kind.
- # If this called first time it creates a new callback method for the kind.
+ # This method defines callback chain method for the given kind
+ # if it was not yet defined.
# This generated method plays caching role.
- #
- def __run_callbacks(kind, object, &blk) #:nodoc:
- name = __callback_runner_name(kind)
+ def __define_callbacks(kind, object) #:nodoc:
+ chain = object.send("_#{kind}_callbacks")
+ name = "_run_callbacks_#{chain.object_id.abs}"
unless object.respond_to?(name, true)
- str = object.send("_#{kind}_callbacks").compile
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}() #{str} end
+ def #{name}() #{chain.compile} end
protected :#{name}
RUBY_EVAL
end
- object.send(name, &blk)
- end
-
- def __reset_runner(symbol)
- name = __callback_runner_name(symbol)
- undef_method(name) if method_defined?(name)
- end
-
- def __callback_runner_name(kind)
- "_run__#{self.name.hash.abs}__#{kind}__callbacks"
+ name
end
# This is used internally to append, prepend and skip callbacks to the
@@ -366,7 +350,6 @@ module ActiveSupport
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
chain = target.send("_#{name}_callbacks")
yield target, chain.dup, type, filters, options
- target.__reset_runner(name)
end
end
@@ -405,7 +388,6 @@ module ActiveSupport
# will be called only when it returns a false value.
# * <tt>:prepend</tt> - If true, the callback will be prepended to the existing
# chain rather than appended.
- #
def set_callback(name, *filter_list, &block)
mapped = nil
@@ -430,7 +412,6 @@ module ActiveSupport
# class Writer < Person
# skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 }
# end
- #
def skip_callback(name, *filter_list, &block)
__update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
filters.each do |filter|
@@ -449,7 +430,6 @@ module ActiveSupport
end
# Remove all set callbacks for the given event.
- #
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
@@ -457,12 +437,9 @@ module ActiveSupport
chain = target.send("_#{symbol}_callbacks").dup
callbacks.each { |c| chain.delete(c) }
target.send("_#{symbol}_callbacks=", chain)
- target.__reset_runner(symbol)
end
self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
-
- __reset_runner(symbol)
end
# Define sets of events in the object lifecycle that support callbacks.
@@ -530,7 +507,6 @@ module ActiveSupport
# define_callbacks :save, :scope => [:name]
#
# would call <tt>Audit#save</tt>.
- #
def define_callbacks(*callbacks)
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
callbacks.each do |callback|
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 6162f7af27..44d90ef732 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -16,7 +16,7 @@ class Array
# %w( a b c d ).to(10) # => %w( a b c d )
# %w().to(0) # => %w()
def to(position)
- self.first position + 1
+ first position + 1
end
# Equal to <tt>self[1]</tt>.
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index f3d06ecb2f..24aa28b895 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -9,28 +9,32 @@ class Array
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
def to_sentence(options = {})
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
+
+ default_connectors = {
+ :words_connector => ', ',
+ :two_words_connector => ' and ',
+ :last_word_connector => ', and '
+ }
if defined?(I18n)
- default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
- default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
- default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
- else
- default_words_connector = ", "
- default_two_words_connector = " and "
- default_last_word_connector = ", and "
+ namespace = 'support.array.'
+ default_connectors.each_key do |name|
+ i18n_key = (namespace + name.to_s).to_sym
+ default_connectors[name] = I18n.translate i18n_key, :locale => options[:locale]
+ end
end
- options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
- options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
+ options.reverse_merge! default_connectors
case length
- when 0
- ""
- when 1
- self[0].to_s.dup
- when 2
- "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
- else
- "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
+ when 0
+ ''
+ when 1
+ self[0].to_s.dup
+ when 2
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
+ else
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
end
@@ -45,14 +49,14 @@ class Array
# Blog.all.to_formatted_s(:db) # => "1,2,3"
def to_formatted_s(format = :default)
case format
- when :db
- if respond_to?(:empty?) && self.empty?
- "null"
- else
- collect { |element| element.id }.join(",")
- end
+ when :db
+ if empty?
+ 'null'
else
- to_default_s
+ collect { |element| element.id }.join(',')
+ end
+ else
+ to_default_s
end
end
alias_method :to_default_s, :to_s
@@ -86,20 +90,20 @@ class Array
# </project>
# </projects>
#
- # Otherwise the root element is "records":
+ # Otherwise the root element is "objects":
#
# [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
- # <records type="array">
- # <record>
+ # <objects type="array">
+ # <object>
# <bar type="integer">2</bar>
# <foo type="integer">1</foo>
- # </record>
- # <record>
+ # </object>
+ # <object>
# <baz type="integer">3</baz>
- # </record>
- # </records>
+ # </object>
+ # </objects>
#
# If the collection is empty the root element is "nil-classes" by default:
#
@@ -139,26 +143,28 @@ class Array
options = options.dup
options[:indent] ||= 2
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
- options[:root] ||= if first.class.to_s != "Hash" && all? { |e| e.is_a?(first.class) }
- underscored = ActiveSupport::Inflector.underscore(first.class.name)
- ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
- else
- "objects"
- end
+ options[:root] ||= \
+ if first.class != Hash && all? { |e| e.is_a?(first.class) }
+ underscored = ActiveSupport::Inflector.underscore(first.class.name)
+ ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
+ else
+ 'objects'
+ end
builder = options[:builder]
builder.instruct! unless options.delete(:skip_instruct)
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
children = options.delete(:children) || root.singularize
+ attributes = options[:skip_types] ? {} : {:type => 'array'}
- attributes = options[:skip_types] ? {} : {:type => "array"}
- return builder.tag!(root, attributes) if empty?
-
- builder.__send__(:method_missing, root, attributes) do
- each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
- yield builder if block_given?
+ if empty?
+ builder.tag!(root, attributes)
+ else
+ builder.__send__(:method_missing, root, attributes) do
+ each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
+ yield builder if block_given?
+ end
end
end
-
end
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 2b3f639cb1..ac1ae53db0 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -82,11 +82,9 @@ class Array
#
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
- def split(value = nil)
- using_block = block_given?
-
+ def split(value = nil, &block)
inject([[]]) do |results, element|
- if (using_block && yield(element)) || (value == element)
+ if block && block.call(element) || value == element
results << []
else
results.last << element
diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb
index ac3dedc0e3..3bedfa9a61 100644
--- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb
+++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb
@@ -6,8 +6,7 @@ class Array
# [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
#
def uniq_by(&block)
- ActiveSupport::Deprecation.warn "uniq_by " \
- "is deprecated. Use Array#uniq instead", caller
+ ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead', caller
uniq(&block)
end
@@ -15,8 +14,7 @@ class Array
#
# Same as +uniq_by+, but modifies +self+.
def uniq_by!(&block)
- ActiveSupport::Deprecation.warn "uniq_by! " \
- "is deprecated. Use Array#uniq! instead", caller
+ ActiveSupport::Deprecation.warn 'uniq_by! is deprecated. Use Array#uniq! instead', caller
uniq!(&block)
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index 4834eca8b1..9ea93d7226 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -25,9 +25,6 @@ class Array
# Array(:foo => :bar) # => [[:foo, :bar]]
# Array.wrap(:foo => :bar) # => [{:foo => :bar}]
#
- # Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
- # Array.wrap("foo\nbar") # => ["foo\nbar"]
- #
# There's also a related idiom that uses the splat operator:
#
# [*object]
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 305ed4964b..c64685a694 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -109,7 +109,7 @@ class Class
end
private
- def singleton_class?
- ancestors.first != self
- end
+ def singleton_class?
+ ancestors.first != self
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 95eb94fdf6..fa1dbfdf06 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -2,34 +2,37 @@ require 'active_support/core_ext/array/extract_options'
# Extends the class object with class and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
-#
-# Note that unlike +class_attribute+, if a subclass changes the value then that would
-# also change the value for parent class. Similarly if parent class changes the value
-# then that would change the value of subclasses too.
-#
-# class Person
-# cattr_accessor :hair_colors
-# end
-#
-# Person.hair_colors = [:brown, :black, :blonde, :red]
-# Person.hair_colors # => [:brown, :black, :blonde, :red]
-# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
-#
-# To opt out of the instance writer method, pass :instance_writer => false.
-# To opt out of the instance reader method, pass :instance_reader => false.
-# To opt out of both instance methods, pass :instance_accessor => false.
-#
-# class Person
-# cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false
-# end
-#
-# Person.new.hair_colors = [:brown] # => NoMethodError
-# Person.new.hair_colors # => NoMethodError
class Class
+ # Defines a class attribute if it's not defined and creates a reader method that
+ # returns the attribute value.
+ #
+ # class Person
+ # cattr_reader :hair_colors
+ # end
+ #
+ # Person.class_variable_set("@@hair_colors", [:brown, :black])
+ # Person.hair_colors # => [:brown, :black]
+ # Person.new.hair_colors # => [:brown, :black]
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # class Person
+ # cattr_reader :"1_Badname "
+ # end
+ # # => NameError: invalid attribute name
+ #
+ # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
+ # or <tt>instance_accessor: false</tt>.
+ #
+ # class Person
+ # cattr_reader :hair_colors, instance_reader: false
+ # end
+ #
+ # Person.new.hair_colors # => NoMethodError
def cattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
unless defined? @@#{sym}
@@#{sym} = nil
@@ -50,10 +53,47 @@ class Class
end
end
+ # Defines a class attribute if it's not defined and creates a writer method to allow
+ # assignment to the attribute.
+ #
+ # class Person
+ # cattr_writer :hair_colors
+ # end
+ #
+ # Person.hair_colors = [:brown, :black]
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
+ # Person.new.hair_colors = [:blonde, :red]
+ # Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # class Person
+ # cattr_writer :"1_Badname "
+ # end
+ # # => NameError: invalid attribute name
+ #
+ # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
+ # or <tt>instance_accessor: false</tt>.
+ #
+ # class Person
+ # cattr_writer :hair_colors, instance_writer: false
+ # end
+ #
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # class Person
+ # cattr_writer :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def cattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
unless defined? @@#{sym}
@@#{sym} = nil
@@ -71,10 +111,58 @@ class Class
end
EOS
end
- self.send("#{sym}=", yield) if block_given?
+ send("#{sym}=", yield) if block_given?
end
end
+ # Defines both class and instance accessors for class attributes.
+ #
+ # class Person
+ # cattr_accessor :hair_colors
+ # end
+ #
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
+ #
+ # If a subclass changes the value then that would also change the value for
+ # parent class. Similarly if parent class changes the value then that would
+ # change the value of subclasses too.
+ #
+ # class Male < Person
+ # end
+ #
+ # Male.hair_colors << :blue
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # class Person
+ # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class Person
+ # cattr_accessor :hair_colors, instance_accessor: false
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Also you can pass a block to set up the attribute with a default value.
+ #
+ # class Person
+ # cattr_accessor :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
def cattr_accessor(*syms, &blk)
cattr_reader(*syms)
cattr_writer(*syms, &blk)
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
index 0634f20e3c..ff870f5fd1 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -20,23 +20,21 @@ class Class
define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
end
-private
-
- # Take the object being set and store it in a method. This gives us automatic
- # inheritance behavior, without having to store the object in an instance
- # variable and look up the superclass chain manually.
- def _stash_object_in_method(object, method, instance_reader = true)
- singleton_class.remove_possible_method(method)
- singleton_class.send(:define_method, method) { object }
- remove_possible_method(method)
- define_method(method) { object } if instance_reader
- end
-
- def _superclass_delegating_accessor(name, options = {})
- singleton_class.send(:define_method, "#{name}=") do |value|
- _stash_object_in_method(value, name, options[:instance_reader] != false)
+ private
+ # Take the object being set and store it in a method. This gives us automatic
+ # inheritance behavior, without having to store the object in an instance
+ # variable and look up the superclass chain manually.
+ def _stash_object_in_method(object, method, instance_reader = true)
+ singleton_class.remove_possible_method(method)
+ singleton_class.send(:define_method, method) { object }
+ remove_possible_method(method)
+ define_method(method) { object } if instance_reader
end
- send("#{name}=", nil)
- end
+ def _superclass_delegating_accessor(name, options = {})
+ singleton_class.send(:define_method, "#{name}=") do |value|
+ _stash_object_in_method(value, name, options[:instance_reader] != false)
+ end
+ send("#{name}=", nil)
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 6d4270f8b0..3e36c54eba 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -5,7 +5,15 @@ require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/time/zones'
class Date
- DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
+ DAYS_INTO_WEEK = {
+ :monday => 0,
+ :tuesday => 1,
+ :wednesday => 2,
+ :thursday => 3,
+ :friday => 4,
+ :saturday => 5,
+ :sunday => 6
+ }
class << self
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
@@ -31,7 +39,7 @@ class Date
# Returns true if the Date object's date is today.
def today?
- self.to_date == ::Date.current # we need the to_date because of DateTime
+ to_date == ::Date.current # we need the to_date because of DateTime
end
# Returns true if the Date object's date lies in the future.
@@ -99,15 +107,13 @@ class Date
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
#
- # Examples:
- #
# Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1)
# Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12)
def change(options)
::Date.new(
- options[:year] || self.year,
- options[:month] || self.month,
- options[:day] || self.day
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day)
)
end
@@ -166,7 +172,7 @@ class Date
def end_of_week(start_day = :monday)
days_to_end = 6 - days_to_week_start(start_day)
result = self + days_to_end.days
- self.acts_like?(:time) ? result.end_of_day : result
+ acts_like?(:time) ? result.end_of_day : result
end
alias :at_end_of_week :end_of_week
@@ -180,7 +186,7 @@ class Date
# week. Default is +:monday+. +DateTime+ objects have their time set to 0:00.
def prev_week(day = :monday)
result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day]
- self.acts_like?(:time) ? result.change(:hour => 0) : result
+ acts_like?(:time) ? result.change(:hour => 0) : result
end
alias :last_week :prev_week
@@ -193,43 +199,57 @@ class Date
# Returns a new Date/DateTime representing the start of the given day in next week (default is :monday).
def next_week(day = :monday)
result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day]
- self.acts_like?(:time) ? result.change(:hour => 0) : result
+ acts_like?(:time) ? result.change(:hour => 0) : result
end
# Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
- self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
+ acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00)
def end_of_month
- last_day = ::Time.days_in_month( self.month, self.year )
- self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day)
+ last_day = ::Time.days_in_month(month, year)
+ if acts_like?(:time)
+ change(:day => last_day, :hour => 23, :min => 59, :sec => 59)
+ else
+ change(:day => last_day)
+ end
end
alias :at_end_of_month :end_of_month
# Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00)
def beginning_of_quarter
- beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
+ first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
+ beginning_of_month.change(:month => first_quarter_month)
end
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59)
def end_of_quarter
- beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
+ last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
+ beginning_of_month.change(:month => last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
# Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
def beginning_of_year
- self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1)
+ if acts_like?(:time)
+ change(:month => 1, :day => 1, :hour => 0)
+ else
+ change(:month => 1, :day => 1)
+ end
end
alias :at_beginning_of_year :beginning_of_year
# Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
def end_of_year
- self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
+ if acts_like?(:time)
+ change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59)
+ else
+ change(:month => 12, :day => 31)
+ end
end
alias :at_end_of_year :end_of_year
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 3262c254f7..81f969e786 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -5,12 +5,15 @@ require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
- :short => "%e %b",
- :long => "%B %e, %Y",
- :db => "%Y-%m-%d",
- :number => "%Y%m%d",
- :long_ordinal => lambda { |date| date.strftime("%B #{ActiveSupport::Inflector.ordinalize(date.day)}, %Y") }, # => "April 25th, 2007"
- :rfc822 => "%e %b %Y"
+ :short => '%e %b',
+ :long => '%B %e, %Y',
+ :db => '%Y-%m-%d',
+ :number => '%Y%m%d',
+ :long_ordinal => lambda { |date|
+ day_format = ActiveSupport::Inflector.ordinalize(date.day)
+ date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
+ },
+ :rfc822 => '%e %b %Y'
}
# Ruby 1.9 has Date#to_time which converts to localtime only.
@@ -23,7 +26,6 @@ class Date
#
# This method is aliased to <tt>to_s</tt>.
#
- # ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_formatted_s(:db) # => "2007-11-10"
@@ -40,7 +42,7 @@ class Date
# or Proc instance that takes a date argument as the value.
#
# # config/initializers/time_formats.rb
- # Date::DATE_FORMATS[:month_and_year] = "%B %Y"
+ # Date::DATE_FORMATS[:month_and_year] = '%B %Y'
# Date::DATE_FORMATS[:short_ordinal] = lambda { |date| date.strftime("%B #{date.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = DATE_FORMATS[format]
@@ -58,7 +60,7 @@ class Date
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
def readable_inspect
- strftime("%a, %d %b %Y")
+ strftime('%a, %d %b %Y')
end
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
@@ -66,7 +68,6 @@ class Date
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
# The timezone can be either :local or :utc (default :local).
#
- # ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_time # => Sat Nov 10 00:00:00 0800 2007
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 6f730e4b6f..fd78044b5d 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -4,8 +4,8 @@ class DateTime
class << self
# *DEPRECATED*: Use +DateTime.civil_from_format+ directly.
def local_offset
- ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. ' \
- 'Use DateTime.civil_from_format directly.', caller
+ ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. Use DateTime.civil_from_format directly.', caller
+
::Time.local(2012).utc_offset.to_r / 86400
end
@@ -35,14 +35,14 @@ class DateTime
# minute is passed, then sec is set to 0.
def change(options)
::DateTime.civil(
- options[:year] || year,
- options[:month] || month,
- options[:day] || day,
- options[:hour] || hour,
- options[:min] || (options[:hour] ? 0 : min),
- options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
- options[:offset] || offset,
- options[:start] || start
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day),
+ options.fetch(:hour, hour),
+ options.fetch(:min, options[:hour] ? 0 : min),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
+ options.fetch(:offset, offset),
+ options.fetch(:start, start)
)
end
@@ -53,8 +53,16 @@ class DateTime
def advance(options)
d = to_date.advance(options)
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
- seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
- seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance)
+ seconds_to_advance = \
+ options.fetch(:seconds, 0) +
+ options.fetch(:minutes, 0) * 60 +
+ options.fetch(:hours, 0) * 3600
+
+ if seconds_to_advance.zero?
+ datetime_advanced_by_date
+ else
+ datetime_advanced_by_date.since seconds_to_advance
+ end
end
# Returns a new DateTime representing the time a number of seconds ago
@@ -83,10 +91,19 @@ class DateTime
change(:hour => 23, :min => 59, :sec => 59)
end
+ # Returns a new DateTime representing the start of the hour (hh:00:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new DateTime representing the end of the hour (hh:59:59)
+ def end_of_hour
+ change(:min => 59, :sec => 59)
+ end
+
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0
#
- # Example:
- #
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
def utc
@@ -108,4 +125,5 @@ class DateTime
def <=>(other)
super other.to_datetime
end
+
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index dc55e9c33c..19925198c0 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -30,7 +30,7 @@ class DateTime
# datetime argument as the value.
#
# # config/initializers/time_formats.rb
- # Time::DATE_FORMATS[:month_and_year] = "%B %Y"
+ # Time::DATE_FORMATS[:month_and_year] = '%B %Y'
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = ::Time::DATE_FORMATS[format]
@@ -42,7 +42,6 @@ class DateTime
alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty?
alias_method :to_s, :to_formatted_s
- # Returns the +utc_offset+ as an +HH:MM formatted string. Examples:
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
@@ -61,7 +60,11 @@ class DateTime
# Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class.
# If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time.
def to_time
- self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) : self
+ if offset == 0
+ ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000)
+ else
+ self
+ end
end
# Returns DateTime with local offset for given year if format is local else offset is zero
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 6fa55a9255..823735d3e2 100644
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb
@@ -14,8 +14,10 @@ class DateTime
#
# DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
- return self unless zone
-
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ if zone
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ else
+ self
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 77a5087981..02d5a7080f 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -1,5 +1,5 @@
module Enumerable
- # Calculates a sum from the elements. Examples:
+ # Calculates a sum from the elements.
#
# payments.sum { |p| p.price * p.tax_rate }
# payments.sum(&:price)
@@ -11,7 +11,7 @@ module Enumerable
# It can also calculate the sum without the use of a block.
#
# [5, 15, 10].sum # => 30
- # ["foo", "bar"].sum # => "foobar"
+ # ['foo', 'bar'].sum # => "foobar"
# [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
#
# The default sum of an empty list is zero. You can override this default:
@@ -26,7 +26,7 @@ module Enumerable
end
end
- # Convert an enumerable to a hash. Examples:
+ # Convert an enumerable to a hash.
#
# people.index_by(&:login)
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
@@ -34,8 +34,11 @@ module Enumerable
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
#
def index_by
- return to_enum :index_by unless block_given?
- Hash[map { |elem| [yield(elem), elem] }]
+ if block_given?
+ Hash[map { |elem| [yield(elem), elem] }]
+ else
+ to_enum :index_by
+ end
end
# Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1.
@@ -48,7 +51,7 @@ module Enumerable
cnt > 1
end
else
- any?{ (cnt += 1) > 1 }
+ any? { (cnt += 1) > 1 }
end
end
@@ -62,8 +65,11 @@ class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
def sum(identity = 0)
- return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
- actual_last = exclude_end? ? (last - 1) : last
- (actual_last - first + 1) * (actual_last + first) / 2
+ if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
+ super
+ else
+ actual_last = exclude_end? ? (last - 1) : last
+ (actual_last - first + 1) * (actual_last + first) / 2
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index fc3277f4d2..9e504851e7 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -2,15 +2,15 @@ class File
# Write to a file atomically. Useful for situations where you don't
# want other processes or threads to see half-written files.
#
- # File.atomic_write("important.file") do |file|
- # file.write("hello")
+ # File.atomic_write('important.file') do |file|
+ # file.write('hello')
# end
#
# If your temp directory is not on the same filesystem as the file you're
# trying to write, you can provide a different temporary directory.
#
- # File.atomic_write("/data/something.important", "/data/tmp") do |file|
- # file.write("hello")
+ # File.atomic_write('/data/something.important', '/data/tmp') do |file|
+ # file.write('hello')
# end
def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
require 'tempfile' unless defined?(Tempfile)
@@ -26,8 +26,14 @@ class File
old_stat = stat(file_name)
rescue Errno::ENOENT
# No old permissions, write a temp file to determine the defaults
- check_name = join(dirname(file_name), ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}")
- open(check_name, "w") { }
+ temp_file_name = [
+ '.permissions_check',
+ Thread.current.object_id,
+ Process.pid,
+ rand(1000000)
+ ].join('.')
+ check_name = join(dirname(file_name), temp_file_name)
+ open(check_name, 'w') { }
old_stat = stat(check_name)
unlink(check_name)
end
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index fd1cda991e..501483498d 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/deep_merge'
-require 'active_support/core_ext/hash/deep_dup'
require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/indifferent_access'
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 5f07bb4f5a..469dc41f2d 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -8,7 +8,7 @@ require 'active_support/core_ext/string/inflections'
class Hash
# Returns a string containing an XML representation of its receiver:
#
- # {"foo" => 1, "bar" => 2}.to_xml
+ # {'foo' => 1, 'bar' => 2}.to_xml
# # =>
# # <?xml version="1.0" encoding="UTF-8"?>
# # <hash>
@@ -26,20 +26,20 @@ class Hash
#
# * If +value+ is a callable object it must expect one or two arguments. Depending
# on the arity, the callable is invoked with the +options+ hash as first argument
- # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
+ # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
# callable can add nodes by using <tt>options[:builder]</tt>.
#
- # "foo".to_xml(lambda { |options, key| options[:builder].b(key) })
+ # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
# # => "<b>foo</b>"
#
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
- #
+ #
# class Foo
# def to_xml(options)
- # options[:builder].bar "fooing!"
+ # options[:builder].bar 'fooing!'
# end
# end
- #
+ #
# {:foo => Foo.new}.to_xml(:skip_instruct => true)
# # => "<hash><bar>fooing!</bar></hash>"
#
@@ -71,7 +71,7 @@ class Hash
options = options.dup
options[:indent] ||= 2
- options[:root] ||= "hash"
+ options[:root] ||= 'hash'
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
builder = options[:builder]
@@ -100,24 +100,24 @@ class Hash
[]
else
case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
- when "Array"
+ when 'Array'
entries.collect { |v| typecast_xml_value(v) }
- when "Hash"
+ when 'Hash'
[typecast_xml_value(entries)]
else
raise "can't typecast #{entries.inspect}"
end
end
- elsif value['type'] == 'file' ||
- (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
- content = value["__content__"]
- if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
+ elsif value['type'] == 'file' ||
+ (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
+ content = value['__content__']
+ if parser = ActiveSupport::XmlMini::PARSING[value['type']]
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
else
content
end
elsif value['type'] == 'string' && value['nil'] != 'true'
- ""
+ ''
# blank or nil parsed values are represented by nil
elsif value.blank? || value['nil'] == 'true'
nil
@@ -131,7 +131,7 @@ class Hash
# Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
# how multipart uploaded files from HTML appear
- xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
+ xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
when 'Array'
value.map! { |i| typecast_xml_value(i) }
@@ -145,9 +145,9 @@ class Hash
def unrename_keys(params)
case params.class.to_s
- when "Hash"
- Hash[params.map { |k,v| [k.to_s.tr("-", "_"), unrename_keys(v)] } ]
- when "Array"
+ when 'Hash'
+ Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ]
+ when 'Array'
params.map { |v| unrename_keys(v) }
else
params
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb b/activesupport/lib/active_support/core_ext/hash/deep_dup.rb
deleted file mode 100644
index 9ab179c566..0000000000
--- a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class Hash
- # Returns a deep copy of hash.
- def deep_dup
- duplicate = self.dup
- duplicate.each_pair do |k,v|
- duplicate[k] = v.is_a?(Hash) ? v.deep_dup : v
- end
- duplicate
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
index af771c86ff..023bf68a87 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,11 +1,16 @@
class Hash
# Returns a new hash with +self+ and +other_hash+ merged recursively.
+ #
+ # h1 = {x: {y: [4,5,6]}, z: [7,8,9]}
+ # h2 = {x: {y: [7,8,9]}, z: "xyz"}
+ #
+ # h1.deep_merge(h2) #=> {:x => {:y => [7, 8, 9]}, :z => "xyz"}
+ # h2.deep_merge(h1) #=> {:x => {:y => [4, 5, 6]}, :z => [7, 8, 9]}
def deep_merge(other_hash)
dup.deep_merge!(other_hash)
end
- # Returns a new hash with +self+ and +other_hash+ merged recursively.
- # Modifies the receiver in place.
+ # Same as +deep_merge+, but modifies +self+.
def deep_merge!(other_hash)
other_hash.each_pair do |k,v|
tv = self[k]
diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb
index b904f49fa8..831dee8ecb 100644
--- a/activesupport/lib/active_support/core_ext/hash/diff.rb
+++ b/activesupport/lib/active_support/core_ext/hash/diff.rb
@@ -1,13 +1,13 @@
class Hash
# Returns a hash that represents the difference between two hashes.
#
- # Examples:
- #
# {1 => 2}.diff(1 => 2) # => {}
# {1 => 2}.diff(1 => 3) # => {1 => 2}
# {}.diff(1 => 2) # => {1 => 2}
# {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
- def diff(h2)
- dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) })
+ def diff(other)
+ dup.
+ delete_if { |k, v| other[k] == v }.
+ merge!(other.dup.delete_if { |k, v| has_key?(k) })
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 89729df258..5a61906222 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -9,7 +9,7 @@ class Hash
# for instance:
#
# {:a => 1}.with_indifferent_access.except(:a) # => {}
- # {:a => 1}.with_indifferent_access.except("a") # => {}
+ # {:a => 1}.with_indifferent_access.except('a') # => {}
#
def except(*keys)
dup.except!(*keys)
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index d8748b1138..be4d611ce7 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,10 +1,18 @@
class Hash
# Return a new hash with all keys converted to strings.
+ #
+ # { :name => 'Rob', :years => '28' }.stringify_keys
+ # #=> { "name" => "Rob", "years" => "28" }
def stringify_keys
- dup.stringify_keys!
+ result = {}
+ keys.each do |key|
+ result[key.to_s] = self[key]
+ end
+ result
end
- # Destructively convert all keys to strings.
+ # Destructively convert all keys to strings. Same as
+ # +stringify_keys+, but modifies +self+.
def stringify_keys!
keys.each do |key|
self[key.to_s] = delete(key)
@@ -14,34 +22,39 @@ class Hash
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
+ #
+ # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys
+ # #=> { :name => "Rob", :years => "28" }
def symbolize_keys
- dup.symbolize_keys!
+ result = {}
+ keys.each do |key|
+ result[(key.to_sym rescue key)] = self[key]
+ end
+ result
end
+ alias_method :to_options, :symbolize_keys
# Destructively convert all keys to symbols, as long as they respond
- # to +to_sym+.
+ # to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
def symbolize_keys!
keys.each do |key|
- self[(key.to_sym rescue key) || key] = delete(key)
+ self[(key.to_sym rescue key)] = delete(key)
end
self
end
-
- alias_method :to_options, :symbolize_keys
alias_method :to_options!, :symbolize_keys!
# Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
# Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
# as keys, this will fail.
#
- # ==== Examples
- # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
- # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key: name"
- # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
+ # { :name => 'Rob', :years => '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
+ # { :name => 'Rob', :age => '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
+ # { :name => 'Rob', :age => '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
def assert_valid_keys(*valid_keys)
valid_keys.flatten!
each_key do |k|
- raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(k)
+ raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
index 01863a162b..6074103484 100644
--- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -18,6 +18,5 @@ class Hash
# right wins if there is no left
merge!( other_hash ){|key,left,right| left }
end
-
alias_method :reverse_update, :reverse_merge!
end
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 45181f0e16..b862b5ae2a 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -18,7 +18,7 @@ class Hash
end
# Replaces the hash with only the given keys.
- # Returns a hash contained the removed key/value pairs
+ # Returns a hash containing the removed key/value pairs.
# {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d => 4}
def slice!(*keys)
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
@@ -31,6 +31,6 @@ class Hash
# Removes and returns the key/value pairs matching the given keys.
# {:a => 1, :b => 2, :c => 3, :d => 4}.extract!(:a, :b) # => {:a => 1, :b => 2}
def extract!(*keys)
- keys.each_with_object({}) {|key, result| result[key] = delete(key) }
+ keys.each_with_object({}) { |key, result| result[key] = delete(key) }
end
end
diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb
index 8dff217ddc..7c6c2f1ca7 100644
--- a/activesupport/lib/active_support/core_ext/integer/multiple.rb
+++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb
@@ -1,5 +1,9 @@
class Integer
# Check whether the integer is evenly divisible by the argument.
+ #
+ # 0.multiple_of?(0) #=> true
+ # 6.multiple_of?(5) #=> false
+ # 10.multiple_of?(2) #=> true
def multiple_of?(number)
number != 0 ? self % number == 0 : zero?
end
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index c677400396..894b5d0696 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -24,9 +24,9 @@ class Integer
# 1.year.to_f.from_now
#
# In such cases, Ruby's core
- # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
- # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
- # date and time arithmetic
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
def months
ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
index d5b590e9f0..2073cac98d 100644
--- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
@@ -1,8 +1,8 @@
module Kernel
unless respond_to?(:debugger)
- # Starts a debugging session if ruby-debug has been loaded (call rails server --debugger to do load it).
+ # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it).
def debugger
- message = "\n***** Debugger requested, but was not available (ensure ruby-debug19 is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
+ message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
end
alias breakpoint debugger unless respond_to?(:breakpoint)
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 526b8378a5..ad3f9ebec9 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,5 @@
require 'rbconfig'
+
module Kernel
# Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards.
#
@@ -49,10 +50,10 @@ module Kernel
#
# suppress(ZeroDivisionError) do
# 1/0
- # puts "This code is NOT reached"
+ # puts 'This code is NOT reached'
# end
#
- # puts "This code gets executed and nothing related to ZeroDivisionError was seen"
+ # puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
def suppress(*exception_classes)
begin yield
rescue Exception => e
@@ -62,7 +63,7 @@ module Kernel
# Captures the given stream and returns it:
#
- # stream = capture(:stdout) { puts "Cool" }
+ # stream = capture(:stdout) { puts 'Cool' }
# stream # => "Cool\n"
#
def capture(stream)
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index a51818d2b2..16fce81445 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -56,8 +56,8 @@ class Logger
alias :old_datetime_format= :datetime_format=
# Logging date-time format (string passed to +strftime+). Ignored if the formatter
# does not respond to datetime_format=.
- def datetime_format=(datetime_format)
- formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=)
+ def datetime_format=(format)
+ formatter.datetime_format = format if formatter.respond_to?(:datetime_format=)
end
alias :old_datetime_format :datetime_format
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index ce481f0e84..580cb80413 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -26,26 +26,25 @@ class Module
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
yield(aliased_target, punctuation) if block_given?
- with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
+ with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
alias_method without_method, target
alias_method target, with_method
case
- when public_method_defined?(without_method)
- public target
- when protected_method_defined?(without_method)
- protected target
- when private_method_defined?(without_method)
- private target
+ when public_method_defined?(without_method)
+ public target
+ when protected_method_defined?(without_method)
+ protected target
+ when private_method_defined?(without_method)
+ private target
end
end
# Allows you to make aliases for attributes, which includes
# getter, setter, and query methods.
#
- # Example:
- #
# class Content < ActiveRecord::Base
# # has a title attribute
# end
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index 00db75bfec..db07d549b0 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -15,7 +15,6 @@ class Module
attr_internal_reader(*attrs)
attr_internal_writer(*attrs)
end
-
alias_method :attr_internal, :attr_internal_accessor
class << self; attr_accessor :attr_internal_naming_format end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 84acb629ad..f914425827 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -4,7 +4,7 @@ class Module
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -26,7 +26,7 @@ class Module
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def self.#{sym}=(obj)
@@#{sym} = obj
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index af92b869fd..fbef27c76a 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,5 +1,5 @@
class Module
- # Provides a delegate class method to easily expose contained objects' methods
+ # Provides a delegate class method to easily expose contained objects' public methods
# as your own. Pass one or more methods (specified as symbols or strings)
# and the name of the target object via the <tt>:to</tt> option (also a symbol
# or string). At least one method and the <tt>:to</tt> option are required.
@@ -8,11 +8,11 @@ class Module
#
# class Greeter < ActiveRecord::Base
# def hello
- # "hello"
+ # 'hello'
# end
#
# def goodbye
- # "goodbye"
+ # 'goodbye'
# end
# end
#
@@ -62,7 +62,7 @@ class Module
# delegate :name, :address, :to => :client, :prefix => true
# end
#
- # john_doe = Person.new("John Doe", "Vimmersvej 13")
+ # john_doe = Person.new('John Doe', 'Vimmersvej 13')
# invoice = Invoice.new(john_doe)
# invoice.client_name # => "John Doe"
# invoice.client_address # => "Vimmersvej 13"
@@ -74,8 +74,8 @@ class Module
# end
#
# invoice = Invoice.new(john_doe)
- # invoice.customer_name # => "John Doe"
- # invoice.customer_address # => "Vimmersvej 13"
+ # invoice.customer_name # => 'John Doe'
+ # invoice.customer_address # => 'Vimmersvej 13'
#
# If the delegate object is +nil+ an exception is raised, and that happens
# no matter whether +nil+ responds to the delegated method. You can get a
@@ -104,17 +104,17 @@ class Module
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
- raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
+ raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter).'
end
to = to.to_s
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
if prefix == true && to =~ /^[^a-z_]/
- raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
+ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
- method_prefix =
+ method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
@@ -124,23 +124,27 @@ class Module
file, line = caller.first.split(':', 2)
line = line.to_i
- if allow_nil
- methods.each do |method|
+ methods.each do |method|
+ method = method.to_s
+
+ # Attribute writer methods only accept one argument. Makes sure []=
+ # methods still accept two arguments.
+ definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
+
+ if allow_nil
module_eval(<<-EOS, file, line - 2)
- def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
- #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
+ #{to}.#{method}(#{definition}) # client.name(*args, &block)
end # end
end # end
EOS
- end
- else
- methods.each do |method|
+ else
exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
module_eval(<<-EOS, file, line - 1)
- def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
+ #{to}.#{method}(#{definition}) # client.name(*args, &block)
rescue NoMethodError # rescue NoMethodError
if #{to}.nil? # if client.nil?
#{exception} # # add helpful message to the exception
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 5a5b4e3f80..9e77ac3c45 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/method_wrappers'
+
class Module
# Declare that a method has been deprecated.
# deprecate :foo
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 743db47bac..3c8e811fa4 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -5,10 +5,11 @@ class Module
#
# M::N.parent_name # => "M"
def parent_name
- unless defined? @parent_name
+ if defined? @parent_name
+ @parent_name
+ else
@parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
end
- @parent_name
end
# Returns the module which contains this one according to its name.
@@ -73,7 +74,7 @@ class Module
# This method is useful for forward compatibility, since Ruby 1.8 returns
# constant names as strings, whereas 1.9 returns them as symbols.
def local_constant_names
- ActiveSupport::Deprecation.warn('Module#local_constant_names is deprecated, use Module#local_constants instead', caller)
+ ActiveSupport::Deprecation.warn 'Module#local_constant_names is deprecated, use Module#local_constants instead', caller
local_constants.map { |c| c.to_s }
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
index 8adf050b6b..65525013db 100644
--- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb
+++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
@@ -5,7 +5,7 @@ require 'active_support/core_ext/string/inflections'
#++
module QualifiedConstUtils
def self.raise_if_absolute(path)
- raise NameError, "wrong constant name #$&" if path =~ /\A::[^:]+/
+ raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
end
def self.names(path)
@@ -20,7 +20,7 @@ end
#--
# Qualified names are required to be relative because we are extending existing
# methods that expect constant names, ie, relative paths of length 1. For example,
-# Object.const_get("::String") raises NameError and so does qualified_const_get.
+# Object.const_get('::String') raises NameError and so does qualified_const_get.
#++
class Module
def qualified_const_defined?(path, search_parents=true)
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index b76bc16ee1..719071d1c2 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,12 +1,8 @@
class Module
def remove_possible_method(method)
if method_defined?(method) || private_method_defined?(method)
- remove_method(method)
+ undef_method(method)
end
- rescue NameError
- # If the requested method is defined on a superclass or included module,
- # method_defined? returns true but remove_method throws a NameError.
- # Ignore this.
end
def redefine_method(method, &block)
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 58a03d508e..2bf3d1f278 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -8,13 +8,13 @@ class Numeric
# These methods use Time#advance for precise date calculations when using from_now, ago, etc.
# as well as adding or subtracting their results from a Time object. For example:
#
- # # equivalent to Time.now.advance(:months => 1)
+ # # equivalent to Time.current.advance(:months => 1)
# 1.month.from_now
#
- # # equivalent to Time.now.advance(:years => 2)
+ # # equivalent to Time.current.advance(:years => 2)
# 2.years.from_now
#
- # # equivalent to Time.now.advance(:months => 4, :years => 5)
+ # # equivalent to Time.current.advance(:months => 4, :years => 5)
# (4.months + 5.years).from_now
#
# While these methods provide precise calculation when used as in the examples above, care
@@ -28,9 +28,9 @@ class Numeric
# 1.year.to_f.from_now
#
# In such cases, Ruby's core
- # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
- # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
- # date and time arithmetic
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
def seconds
ActiveSupport::Duration.new(self, [[:seconds, self]])
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 9ad1e12699..ec2157221f 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -1,6 +1,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/deep_dup'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index d67711f3b8..e238fef5a2 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -2,7 +2,7 @@
class Object
# An object is blank if it's false, empty, or a whitespace string.
- # For example, "", " ", +nil+, [], and {} are all blank.
+ # For example, '', ' ', +nil+, [], and {} are all blank.
#
# This simplifies:
#
@@ -90,10 +90,10 @@ end
class String
# A string is blank if it's empty or contains whitespaces only:
#
- # "".blank? # => true
- # " ".blank? # => true
- # " ".blank? # => true
- # " something here ".blank? # => false
+ # ''.blank? # => true
+ # ' '.blank? # => true
+ # ' '.blank? # => true
+ # ' something here '.blank? # => false
#
def blank?
self !~ /[^[:space:]]/
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
new file mode 100644
index 0000000000..883f5f556c
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -0,0 +1,44 @@
+class Object
+ # Returns a deep copy of object if it's duplicable. If it's
+ # not duplicable, returns +self+.
+ #
+ # object = Object.new
+ # dup = object.deep_dup
+ # dup.instance_variable_set(:@a, 1)
+ #
+ # object.instance_variable_defined?(:@a) #=> false
+ # dup.instance_variable_defined?(:@a) #=> true
+ def deep_dup
+ duplicable? ? dup : self
+ end
+end
+
+class Array
+ # Returns a deep copy of array.
+ #
+ # array = [1, [2, 3]]
+ # dup = array.deep_dup
+ # dup[1][2] = 4
+ #
+ # array[1][2] #=> nil
+ # dup[1][2] #=> 4
+ def deep_dup
+ map { |it| it.deep_dup }
+ end
+end
+
+class Hash
+ # Returns a deep copy of hash.
+ #
+ # hash = { a: { b: 'b' } }
+ # dup = hash.deep_dup
+ # dup[:a][:c] = 'c'
+ #
+ # hash[:a][:c] #=> nil
+ # dup[:a][:c] #=> "c"
+ def deep_dup
+ each_with_object(dup) do |(key, value), hash|
+ hash[key.deep_dup] = value.deep_dup
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 9d1630bb7c..f1b755c2c4 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbols, numbers, class and module objects;
+ # False for +nil+, +false+, +true+, symbol, and number objects;
# true otherwise.
def duplicable?
true
@@ -81,30 +81,6 @@ class Numeric
end
end
-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
- # Modules are not duplicable:
- #
- # m = Module.new # => #<Module:0x10328b6e0>
- # m.dup # => #<Module:0x10328b6e0>
- #
- # Note +dup+ returned the same module object.
- def duplicable?
- false
- end
-end
-
require 'bigdecimal'
class BigDecimal
begin
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index f611cdd606..3fec465ec0 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -2,11 +2,11 @@ class Object
# Returns true if this object is included in the argument(s). Argument must be
# any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage:
#
- # characters = ["Konata", "Kagami", "Tsukasa"]
- # "Konata".in?(characters) # => true
- #
- # character = "Konata"
- # character.in?("Konata", "Kagami", "Tsukasa") # => true
+ # characters = ['Konata', 'Kagami', 'Tsukasa']
+ # 'Konata'.in?(characters) # => true
+ #
+ # character = 'Konata'
+ # character.in?('Konata', 'Kagami', 'Tsukasa') # => true
#
# This will throw an ArgumentError if a single argument is passed in and it doesn't respond
# to +#include?+.
@@ -18,7 +18,7 @@ class Object
if another_object.respond_to? :include?
another_object.include? self
else
- raise ArgumentError.new("The single parameter passed to #in? must respond to #include?")
+ raise ArgumentError.new 'The single parameter passed to #in? must respond to #include?'
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
index 91fdf93eb2..40821fd619 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -1,6 +1,6 @@
class Object
- # Returns a hash that maps instance variable names without "@" to their
- # corresponding values. Keys are strings both in Ruby 1.8 and 1.9.
+ # Returns a hash with string keys that maps instance variable names without "@" to their
+ # corresponding values.
#
# class C
# def initialize(x, y)
@@ -9,12 +9,11 @@ class Object
# end
#
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
- def instance_values #:nodoc:
+ def instance_values
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
end
- # Returns an array of instance variable names including "@". They are strings
- # both in Ruby 1.8 and 1.9.
+ # Returns an array of instance variable names including "@".
#
# class C
# def initialize(x, y)
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index e77a9da0ec..48eb546a7d 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -1,6 +1,6 @@
class Object
- # Invokes the method identified by the symbol +method+, passing it any arguments
- # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
+ # Invokes the public method identified by the symbol +method+, passing it any arguments
+ # and/or the block specified, just like the regular Ruby <tt>Object#public_send</tt> does.
#
# *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
# and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
@@ -24,12 +24,12 @@ class Object
# Without a method argument try will yield to the block unless the receiver is nil.
# @person.try { |p| "#{p.first_name} #{p.last_name}" }
#--
- # +try+ behaves like +Object#send+, unless called on +NilClass+.
+ # +try+ behaves like +Object#public_send+, unless called on +NilClass+.
def try(*a, &b)
if a.empty? && block_given?
yield self
else
- __send__(*a, &b)
+ public_send(*a, &b)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index 1397142c04..e058367111 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -29,7 +29,7 @@ class Object
#
# It can also be used with an explicit receiver:
#
- # I18n.with_options :locale => user.locale, :scope => "newsletter" do |i18n|
+ # I18n.with_options :locale => user.locale, :scope => 'newsletter' do |i18n|
# subject i18n.t :subject
# body i18n.t :body, :user_name => user.name
# end
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index c0736f3a44..1d8b1ede5a 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/range/blockless_step'
require 'active_support/core_ext/range/conversions'
require 'active_support/core_ext/range/include_range'
require 'active_support/core_ext/range/overlaps'
diff --git a/activesupport/lib/active_support/core_ext/range/blockless_step.rb b/activesupport/lib/active_support/core_ext/range/blockless_step.rb
deleted file mode 100644
index f687287f0d..0000000000
--- a/activesupport/lib/active_support/core_ext/range/blockless_step.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'active_support/core_ext/module/aliasing'
-
-class Range
- def step_with_blockless(*args, &block) #:nodoc:
- if block_given?
- step_without_blockless(*args, &block)
- else
- step_without_blockless(*args).to_a
- end
- end
-
- alias_method_chain :step, :blockless
-end
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 43134b4314..b1a12781f3 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -5,8 +5,6 @@ class Range
# Gives a human readable format of the range.
#
- # ==== Example
- #
# (1..100).to_formatted_s # => "1..100"
def to_formatted_s(format = :default)
if formatter = RANGE_FORMATS[format]
diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb
index 684b7cbc4a..3af66aaf2f 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -5,7 +5,7 @@ class Range
# (1..5).include?(2..6) # => false
#
# The native Range#include? behavior is untouched.
- # ("a".."f").include?("c") # => true
+ # ('a'..'f').include?('c') # => true
# (5..9).include?(11) # => false
def include_with_range?(value)
if value.is_a?(::Range)
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index ab49af55bf..fb36262528 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -6,7 +6,6 @@ require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/xchar'
require 'active_support/core_ext/string/behavior'
-require 'active_support/core_ext/string/interpolation'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/exclude'
require 'active_support/core_ext/string/strip'
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 9b5266c58c..5c32a2453d 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,18 +1,79 @@
-require "active_support/multibyte"
+require 'active_support/multibyte'
class String
+ # If you pass a single Fixnum, returns a substring of one character at that
+ # position. The first character of the string is at position 0, the next at
+ # position 1, and so on. If a range is supplied, a substring containing
+ # characters at offsets given by the range is returned. In both cases, if an
+ # offset is negative, it is counted from the end of the string. Returns nil
+ # if the initial offset falls outside the string. Returns an empty string if
+ # the beginning of the range is greater than the end of the string.
+ #
+ # str = "hello"
+ # str.at(0) #=> "h"
+ # str.at(1..3) #=> "ell"
+ # str.at(-2) #=> "l"
+ # str.at(-2..-1) #=> "lo"
+ # str.at(5) #=> nil
+ # str.at(5..-1) #=> ""
+ #
+ # If a Regexp is given, the matching portion of the string is returned.
+ # If a String is given, that given string is returned if it occurs in
+ # the string. In both cases, nil is returned if there is no match.
+ #
+ # str = "hello"
+ # str.at(/lo/) #=> "lo"
+ # str.at(/ol/) #=> nil
+ # str.at("lo") #=> "lo"
+ # str.at("ol") #=> nil
def at(position)
self[position]
end
+ # Returns a substring from the given position to the end of the string.
+ # If the position is negative, it is counted from the end of the string.
+ #
+ # str = "hello"
+ # str.from(0) #=> "hello"
+ # str.from(3) #=> "lo"
+ # str.from(-2) #=> "lo"
+ #
+ # You can mix it with +to+ method and do fun things like:
+ #
+ # str = "hello"
+ # str.from(0).to(-1) #=> "hello"
+ # str.from(1).to(-2) #=> "ell"
def from(position)
self[position..-1]
end
+ # Returns a substring from the beginning of the string to the given position.
+ # If the position is negative, it is counted from the end of the string.
+ #
+ # str = "hello"
+ # str.to(0) #=> "h"
+ # str.to(3) #=> "hell"
+ # str.to(-2) #=> "hell"
+ #
+ # You can mix it with +from+ method and do fun things like:
+ #
+ # str = "hello"
+ # str.from(0).to(-1) #=> "hello"
+ # str.from(1).to(-2) #=> "ell"
def to(position)
self[0..position]
end
+ # Returns the first character. If a limit is supplied, returns a substring
+ # from the beginning of the string until it reaches the limit value. If the
+ # given limit is greater than or equal to the string length, returns self.
+ #
+ # str = "hello"
+ # str.first #=> "h"
+ # str.first(1) #=> "h"
+ # str.first(2) #=> "he"
+ # str.first(0) #=> ""
+ # str.first(6) #=> "hello"
def first(limit = 1)
if limit == 0
''
@@ -23,6 +84,16 @@ class String
end
end
+ # Returns the last character of the string. If a limit is supplied, returns a substring
+ # from the end of the string until it reaches the limit value (counting backwards). If
+ # the given limit is greater than or equal to the string length, returns self.
+ #
+ # str = "hello"
+ # str.last #=> "o"
+ # str.last(1) #=> "o"
+ # str.last(2) #=> "lo"
+ # str.last(0) #=> ""
+ # str.last(6) #=> "hello"
def last(limit = 1)
if limit == 0
''
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 541f969faa..022b376aec 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -4,21 +4,45 @@ require 'active_support/core_ext/time/calculations'
class String
# Form can be either :utc (default) or :local.
def to_time(form = :utc)
- return nil if self.blank?
- d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).map { |arg| arg || 0 }
- d[6] *= 1000000
- ::Time.send("#{form}_time", *d[0..6]) - d[7]
+ unless blank?
+ date_values = ::Date._parse(self, false).
+ values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).
+ map! { |arg| arg || 0 }
+ date_values[6] *= 1000000
+ offset = date_values.pop
+
+ ::Time.send("#{form}_time", *date_values) - offset
+ end
end
+ # Converts a string to a Date value.
+ #
+ # "1-1-2012".to_date #=> Sun, 01 Jan 2012
+ # "01/01/2012".to_date #=> Sun, 01 Jan 2012
+ # "2012-12-13".to_date #=> Thu, 13 Dec 2012
+ # "12/13/2012".to_date #=> ArgumentError: invalid date
def to_date
- return nil if self.blank?
- ::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday))
+ unless blank?
+ date_values = ::Date._parse(self, false).values_at(:year, :mon, :mday)
+
+ ::Date.new(*date_values)
+ end
end
+ # Converts a string to a DateTime value.
+ #
+ # "1-1-2012".to_datetime #=> Sun, 01 Jan 2012 00:00:00 +0000
+ # "01/01/2012 23:59:59".to_datetime #=> Sun, 01 Jan 2012 23:59:59 +0000
+ # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000
+ # "12/13/2012".to_datetime #=> ArgumentError: invalid date
def to_datetime
- return nil if self.blank?
- d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).map { |arg| arg || 0 }
- d[5] += d.pop
- ::DateTime.civil(*d)
+ unless blank?
+ date_values = ::Date._parse(self, false).
+ values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).
+ map! { |arg| arg || 0 }
+ date_values[5] += date_values.pop
+
+ ::DateTime.civil(*date_values)
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/exclude.rb b/activesupport/lib/active_support/core_ext/string/exclude.rb
index 5e184ec1b3..114bcb87f0 100644
--- a/activesupport/lib/active_support/core_ext/string/exclude.rb
+++ b/activesupport/lib/active_support/core_ext/string/exclude.rb
@@ -1,5 +1,10 @@
class String
- # The inverse of <tt>String#include?</tt>. Returns true if the string does not include the other string.
+ # The inverse of <tt>String#include?</tt>. Returns true if the string
+ # does not include the other string.
+ #
+ # "hello".exclude? "lo" #=> false
+ # "hello".exclude? "ol" #=> true
+ # "hello".exclude? ?h #=> false
def exclude?(string)
!include?(string)
end
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 1a34e88a87..2478f42290 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -5,7 +5,6 @@ class String
# the string, and then changing remaining consecutive whitespace
# groups into one space each.
#
- # Examples:
# %{ Multi-line
# string }.squish # => "Multi-line string"
# " foo bar \n \t boo".squish # => "foo bar boo"
@@ -22,26 +21,33 @@ class String
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
#
- # "Once upon a time in a world far far away".truncate(27)
+ # 'Once upon a time in a world far far away'.truncate(27)
# # => "Once upon a time in a wo..."
#
- # Pass a <tt>:separator</tt> to truncate +text+ at a natural break:
+ # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
#
- # "Once upon a time in a world far far away".truncate(27, :separator => ' ')
+ # 'Once upon a time in a world far far away'.truncate(27, :separator => ' ')
+ # # => "Once upon a time in a..."
+ #
+ # 'Once upon a time in a world far far away'.truncate(27, :separator => /\s/)
# # => "Once upon a time in a..."
#
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
# for a total length not exceeding <tt>:length</tt>:
#
- # "And they found that many people were sleeping better.".truncate(25, :omission => "... (continued)")
+ # 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)')
# # => "And they f... (continued)"
- def truncate(length, options = {})
- return self.dup unless self.length > length
+ def truncate(truncate_at, options = {})
+ return dup unless length > truncate_at
- options[:omission] ||= "..."
- length_with_room_for_omission = length - options[:omission].length
- stop = options[:separator] ?
- (rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission
+ options[:omission] ||= '...'
+ length_with_room_for_omission = truncate_at - options[:omission].length
+ stop = \
+ if options[:separator]
+ rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
+ else
+ length_with_room_for_omission
+ end
self[0...stop] + options[:omission]
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 2194dafe4d..070bfd7af6 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -4,7 +4,7 @@ require 'active_support/inflector/transliterate'
# String inflections define new methods on the String class to transform names for different purposes.
# For instance, you can figure out the name of a table from the name of a class.
#
-# "ScaleScore".tableize # => "scale_scores"
+# 'ScaleScore'.tableize # => "scale_scores"
#
class String
# Returns the plural form of the word in the string.
@@ -13,15 +13,14 @@ class String
# the singular form will be returned if <tt>count == 1</tt>.
# For any other value of +count+ the plural will be returned.
#
- # ==== Examples
- # "post".pluralize # => "posts"
- # "octopus".pluralize # => "octopi"
- # "sheep".pluralize # => "sheep"
- # "words".pluralize # => "words"
- # "the blue mailman".pluralize # => "the blue mailmen"
- # "CamelOctopus".pluralize # => "CamelOctopi"
- # "apple".pluralize(1) # => "apple"
- # "apple".pluralize(2) # => "apples"
+ # 'post'.pluralize # => "posts"
+ # 'octopus'.pluralize # => "octopi"
+ # 'sheep'.pluralize # => "sheep"
+ # 'words'.pluralize # => "words"
+ # 'the blue mailman'.pluralize # => "the blue mailmen"
+ # 'CamelOctopus'.pluralize # => "CamelOctopi"
+ # 'apple'.pluralize(1) # => "apple"
+ # 'apple'.pluralize(2) # => "apples"
def pluralize(count = nil)
if count == 1
self
@@ -32,12 +31,12 @@ class String
# The reverse of +pluralize+, returns the singular form of a word in a string.
#
- # "posts".singularize # => "post"
- # "octopi".singularize # => "octopus"
- # "sheep".singularize # => "sheep"
- # "word".singularize # => "word"
- # "the blue mailmen".singularize # => "the blue mailman"
- # "CamelOctopi".singularize # => "CamelOctopus"
+ # 'posts'.singularize # => "post"
+ # 'octopi'.singularize # => "octopus"
+ # 'sheep'.singularize # => "sheep"
+ # 'word'.singularize # => "word"
+ # 'the blue mailmen'.singularize # => "the blue mailman"
+ # 'CamelOctopi'.singularize # => "CamelOctopus"
def singularize
ActiveSupport::Inflector.singularize(self)
end
@@ -46,10 +45,9 @@ class String
# in the string. It raises a NameError when the name is not in CamelCase
# or is not initialized. See ActiveSupport::Inflector.constantize
#
- # Examples
- # "Module".constantize # => Module
- # "Class".constantize # => Class
- # "blargle".constantize # => NameError: wrong constant name blargle
+ # 'Module'.constantize # => Module
+ # 'Class'.constantize # => Class
+ # 'blargle'.constantize # => NameError: wrong constant name blargle
def constantize
ActiveSupport::Inflector.constantize(self)
end
@@ -58,10 +56,9 @@ class String
# in the string. It returns nil when the name is not in CamelCase
# or is not initialized. See ActiveSupport::Inflector.safe_constantize
#
- # Examples
- # "Module".safe_constantize # => Module
- # "Class".safe_constantize # => Class
- # "blargle".safe_constantize # => nil
+ # 'Module'.safe_constantize # => Module
+ # 'Class'.safe_constantize # => Class
+ # 'blargle'.safe_constantize # => nil
def safe_constantize
ActiveSupport::Inflector.safe_constantize(self)
end
@@ -71,14 +68,16 @@ class String
#
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
#
- # "active_record".camelize # => "ActiveRecord"
- # "active_record".camelize(:lower) # => "activeRecord"
- # "active_record/errors".camelize # => "ActiveRecord::Errors"
- # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+ # 'active_record'.camelize # => "ActiveRecord"
+ # 'active_record'.camelize(:lower) # => "activeRecord"
+ # 'active_record/errors'.camelize # => "ActiveRecord::Errors"
+ # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
def camelize(first_letter = :upper)
case first_letter
- when :upper then ActiveSupport::Inflector.camelize(self, true)
- when :lower then ActiveSupport::Inflector.camelize(self, false)
+ when :upper
+ ActiveSupport::Inflector.camelize(self, true)
+ when :lower
+ ActiveSupport::Inflector.camelize(self, false)
end
end
alias_method :camelcase, :camelize
@@ -89,8 +88,8 @@ class String
#
# +titleize+ is also aliased as +titlecase+.
#
- # "man from the boondocks".titleize # => "Man From The Boondocks"
- # "x-men: the last stand".titleize # => "X Men: The Last Stand"
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
def titleize
ActiveSupport::Inflector.titleize(self)
end
@@ -100,23 +99,23 @@ class String
#
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
#
- # "ActiveModel".underscore # => "active_model"
- # "ActiveModel::Errors".underscore # => "active_model/errors"
+ # 'ActiveModel'.underscore # => "active_model"
+ # 'ActiveModel::Errors'.underscore # => "active_model/errors"
def underscore
ActiveSupport::Inflector.underscore(self)
end
# Replaces underscores with dashes in the string.
#
- # "puni_puni" # => "puni-puni"
+ # 'puni_puni' # => "puni-puni"
def dasherize
ActiveSupport::Inflector.dasherize(self)
end
# Removes the module part from the constant expression in the string.
#
- # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
- # "Inflections".demodulize # => "Inflections"
+ # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
+ # 'Inflections'.demodulize # => "Inflections"
#
# See also +deconstantize+.
def demodulize
@@ -125,11 +124,11 @@ class String
# Removes the rightmost segment from the constant expression in the string.
#
- # "Net::HTTP".deconstantize # => "Net"
- # "::Net::HTTP".deconstantize # => "::Net"
- # "String".deconstantize # => ""
- # "::String".deconstantize # => ""
- # "".deconstantize # => ""
+ # 'Net::HTTP'.deconstantize # => "Net"
+ # '::Net::HTTP'.deconstantize # => "::Net"
+ # 'String'.deconstantize # => ""
+ # '::String'.deconstantize # => ""
+ # ''.deconstantize # => ""
#
# See also +demodulize+.
def deconstantize
@@ -138,8 +137,6 @@ class String
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
#
- # ==== Examples
- #
# class Person
# def to_param
# "#{id}-#{name.parameterize}"
@@ -158,9 +155,9 @@ class String
# Creates the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
- # "RawScaledScorer".tableize # => "raw_scaled_scorers"
- # "egg_and_ham".tableize # => "egg_and_hams"
- # "fancyCategory".tableize # => "fancy_categories"
+ # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
+ # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'fancyCategory'.tableize # => "fancy_categories"
def tableize
ActiveSupport::Inflector.tableize(self)
end
@@ -169,12 +166,12 @@ class String
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # "egg_and_hams".classify # => "EggAndHam"
- # "posts".classify # => "Post"
+ # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'posts'.classify # => "Post"
#
# Singular names are not handled correctly.
#
- # "business".classify # => "Busines"
+ # 'business'.classify # => "Busines"
def classify
ActiveSupport::Inflector.classify(self)
end
@@ -182,8 +179,8 @@ class String
# Capitalizes the first word, turns underscores into spaces, and strips '_id'.
# Like +titleize+, this is meant for creating pretty output.
#
- # "employee_salary" # => "Employee salary"
- # "author_id" # => "Author"
+ # 'employee_salary' # => "Employee salary"
+ # 'author_id' # => "Author"
def humanize
ActiveSupport::Inflector.humanize(self)
end
@@ -192,10 +189,9 @@ class String
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # Examples
- # "Message".foreign_key # => "message_id"
- # "Message".foreign_key(false) # => "messageid"
- # "Admin::Post".foreign_key # => "post_id"
+ # 'Message'.foreign_key # => "message_id"
+ # 'Message'.foreign_key(false) # => "messageid"
+ # 'Admin::Post'.foreign_key # => "post_id"
def foreign_key(separate_class_name_and_id_with_underscore = true)
ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
end
diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb
index 5f0a017de6..1dcd949536 100644
--- a/activesupport/lib/active_support/core_ext/string/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb
@@ -2,9 +2,9 @@ require 'active_support/string_inquirer'
class String
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
- # which gives you a prettier way to test for equality. Example:
+ # which gives you a prettier way to test for equality.
#
- # env = "production".inquiry
+ # env = 'production'.inquiry
# env.production? # => true
# env.development? # => false
def inquiry
diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb
deleted file mode 100644
index 7f764e9de1..0000000000
--- a/activesupport/lib/active_support/core_ext/string/interpolation.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'active_support/i18n'
-require 'i18n/core_ext/string/interpolate'
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 4903687b73..5226ff0cbe 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -14,8 +14,7 @@ class ERB
# In your ERB templates, use this method to escape any unsafe content. For example:
# <%=h @person.name %>
#
- # ==== Example:
- # puts html_escape("is a > 0 & a < 10?")
+ # puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
s = s.to_s
@@ -37,11 +36,10 @@ class ERB
# A utility method for escaping HTML without affecting existing escaped entities.
#
- # ==== Examples
- # html_escape_once("1 < 2 &amp; 3")
+ # html_escape_once('1 < 2 &amp; 3')
# # => "1 &lt; 2 &amp; 3"
#
- # html_escape_once("&lt;&lt; Accept & Checkout")
+ # html_escape_once('&lt;&lt; Accept & Checkout')
# # => "&lt;&lt; Accept &amp; Checkout"
def html_escape_once(s)
result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] }
@@ -53,7 +51,7 @@ class ERB
# A utility method for escaping HTML entities in JSON strings
# using \uXXXX JavaScript escape sequences for string literals:
#
- # json_escape("is a > 0 & a < 10?")
+ # json_escape('is a > 0 & a < 10?')
# # => is a \u003E 0 \u0026 a \u003C 10?
#
# Note that after this operation is performed the output is not
@@ -92,26 +90,31 @@ end
module ActiveSupport #:nodoc:
class SafeBuffer < String
- UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase", "prepend"].freeze
+ UNSAFE_STRING_METHODS = %w(
+ capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
+ slice squeeze strip sub succ swapcase tr tr_s upcase prepend
+ )
alias_method :original_concat, :concat
private :original_concat
class SafeConcatError < StandardError
def initialize
- super "Could not concatenate to the buffer because it is not html safe."
+ super 'Could not concatenate to the buffer because it is not html safe.'
end
end
def [](*args)
- return super if args.size < 2
-
- if html_safe?
- new_safe_buffer = super
- new_safe_buffer.instance_eval { @html_safe = true }
- new_safe_buffer
+ if args.size < 2
+ super
else
- to_str[*args]
+ if html_safe?
+ new_safe_buffer = super
+ new_safe_buffer.instance_eval { @html_safe = true }
+ new_safe_buffer
+ else
+ to_str[*args]
+ end
end
end
@@ -147,6 +150,18 @@ module ActiveSupport #:nodoc:
dup.concat(other)
end
+ def %(args)
+ args = Array(args).map do |arg|
+ if !html_safe? || arg.html_safe?
+ arg
+ else
+ ERB::Util.h(arg)
+ end
+ end
+
+ self.class.new(super(args))
+ end
+
def html_safe?
defined?(@html_safe) && @html_safe
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 5076697c04..92b8417150 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,10 +1,17 @@
require 'active_support/duration'
-require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/time/conversions'
class Time
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
+ DAYS_INTO_WEEK = {
+ :monday => 0,
+ :tuesday => 1,
+ :wednesday => 2,
+ :thursday => 3,
+ :friday => 4,
+ :saturday => 5,
+ :sunday => 6
+ }
class << self
# Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
@@ -15,8 +22,11 @@ class Time
# Return the number of days in the given month.
# If no year is specified, it will use the current year.
def days_in_month(month, year = now.year)
- return 29 if month == 2 && ::Date.gregorian_leap?(year)
- COMMON_YEAR_DAYS_IN_MONTH[month]
+ if month == 2 && ::Date.gregorian_leap?(year)
+ 29
+ else
+ COMMON_YEAR_DAYS_IN_MONTH[month]
+ end
end
# Returns a new Time if requested year can be accommodated by Ruby's Time class
@@ -24,8 +34,13 @@ class Time
# otherwise returns a DateTime.
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
+
# This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138.
- time.year == year ? time : ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
+ if time.year == year
+ time
+ else
+ ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
+ end
rescue
::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
end
@@ -72,13 +87,13 @@ class Time
def change(options)
::Time.send(
utc? ? :utc_time : :local_time,
- options[:year] || year,
- options[:month] || month,
- options[:day] || day,
- options[:hour] || hour,
- options[:min] || (options[:hour] ? 0 : min),
- options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
- options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day),
+ options.fetch(:hour, hour),
+ options.fetch(:min, options[:hour] ? 0 : min),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
+ options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
)
end
@@ -89,18 +104,26 @@ class Time
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
- options[:days] = (options[:days] || 0) + 7 * partial_weeks
+ options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
end
unless options[:days].nil?
options[:days], partial_days = options[:days].divmod(1)
- options[:hours] = (options[:hours] || 0) + 24 * partial_days
+ options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
end
d = to_date.advance(options)
time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
- seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
- seconds_to_advance == 0 ? time_advanced_by_date : time_advanced_by_date.since(seconds_to_advance)
+ seconds_to_advance = \
+ options.fetch(:seconds, 0) +
+ options.fetch(:minutes, 0) * 60 +
+ options.fetch(:hours, 0) * 3600
+
+ if seconds_to_advance.zero?
+ time_advanced_by_date
+ else
+ time_advanced_by_date.since(seconds_to_advance)
+ end
end
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
@@ -168,6 +191,7 @@ class Time
start_day_number = DAYS_INTO_WEEK[start_day]
current_day_number = wday != 0 ? wday - 1 : 6
days_span = current_day_number - start_day_number
+
days_span >= 0 ? days_span : 7 + days_span
end
@@ -199,13 +223,19 @@ class Time
# Returns a new Time representing the start of the given day in the previous week (default is :monday).
def prev_week(day = :monday)
- ago(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
+ ago(1.week).
+ beginning_of_week.
+ since(DAYS_INTO_WEEK[day].day).
+ change(:hour => 0)
end
alias_method :last_week, :prev_week
# Returns a new Time representing the start of the given day in next week (default is :monday).
def next_week(day = :monday)
- since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
+ since(1.week).
+ beginning_of_week.
+ since(DAYS_INTO_WEEK[day].day).
+ change(:hour => 0)
end
# Returns a new Time representing the start of the day (0:00)
@@ -219,7 +249,27 @@ class Time
# Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
def end_of_day
- change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
+ change(
+ :hour => 23,
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
+ end
+
+ # Returns a new Time representing the start of the hour (x:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
+ def end_of_hour
+ change(
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
# Returns a new Time representing the start of the month (1st of the month, 0:00)
@@ -233,19 +283,27 @@ class Time
def end_of_month
#self - ((self.mday-1).days + self.seconds_since_midnight)
last_day = ::Time.days_in_month(month, year)
- change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
+ change(
+ :day => last_day,
+ :hour => 23,
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
alias :at_end_of_month :end_of_month
# Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
def beginning_of_quarter
- beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= month })
+ first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
+ beginning_of_month.change(:month => first_quarter_month)
end
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december)
def end_of_quarter
- beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month
+ last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
+ beginning_of_month.change(:month => last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
@@ -257,7 +315,14 @@ class Time
# Returns a new Time representing the end of the year (end of the 31st of december)
def end_of_year
- change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
+ change(
+ :month => 12,
+ :day => 31,
+ :hour => 23,
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
alias :at_end_of_year :end_of_year
@@ -330,7 +395,11 @@ class Time
# can be chronologically compared with a Time
def compare_with_coercion(other)
# we're avoiding Time#to_datetime cause it's expensive
- other.is_a?(Time) ? compare_without_coercion(other.to_time) : to_datetime <=> other
+ if other.is_a?(Time)
+ compare_without_coercion(other.to_time)
+ else
+ to_datetime <=> other
+ end
end
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
@@ -344,4 +413,5 @@ class Time
end
alias_method :eql_without_coercion, :eql?
alias_method :eql?, :eql_with_coercion
+
end
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 0fdcd383f0..10ca26acf2 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -3,13 +3,20 @@ require 'active_support/values/time_zone'
class Time
DATE_FORMATS = {
- :db => "%Y-%m-%d %H:%M:%S",
- :number => "%Y%m%d%H%M%S",
- :time => "%H:%M",
- :short => "%d %b %H:%M",
- :long => "%B %d, %Y %H:%M",
- :long_ordinal => lambda { |time| time.strftime("%B #{ActiveSupport::Inflector.ordinalize(time.day)}, %Y %H:%M") },
- :rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") }
+ :db => '%Y-%m-%d %H:%M:%S',
+ :number => '%Y%m%d%H%M%S',
+ :nsec => '%Y%m%d%H%M%S%9N',
+ :time => '%H:%M',
+ :short => '%d %b %H:%M',
+ :long => '%B %d, %Y %H:%M',
+ :long_ordinal => lambda { |time|
+ day_format = ActiveSupport::Inflector.ordinalize(time.day)
+ time.strftime("%B #{day_format}, %Y %H:%M")
+ },
+ :rfc822 => lambda { |time|
+ offset_format = time.formatted_offset(false)
+ time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
+ }
}
# Converts to a formatted string. See DATE_FORMATS for builtin formats.
@@ -34,7 +41,7 @@ class Time
# or Proc instance that takes a time argument as the value.
#
# # config/initializers/time_formats.rb
- # Time::DATE_FORMATS[:month_and_year] = "%B %Y"
+ # Time::DATE_FORMATS[:month_and_year] = '%B %Y'
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = DATE_FORMATS[format]
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 0c5962858e..e48866abe3 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/time/calculations'
require 'active_support/time_with_zone'
class Time
@@ -50,13 +51,21 @@ class Time
# 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)
+ if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
+ time_zone
+ else
+ # 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
+ if time_zone.is_a?(ActiveSupport::TimeZone)
+ time_zone
+ else
+ ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
+ end
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
@@ -79,8 +88,10 @@ class Time
#
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
- return self unless zone
-
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ if zone
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ else
+ self
+ end
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 745a131524..66f3af7002 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -371,10 +371,6 @@ module ActiveSupport #:nodoc:
Object.qualified_const_defined?(path.sub(/^::/, ''), false)
end
- def local_const_defined?(mod, const) #:nodoc:
- mod.const_defined?(const, false)
- end
-
# Given +path+, a filesystem path to a ruby file, return an array of constant
# paths which would cause Dependencies to attempt to load this file.
def loadable_constants_for_path(path, bases = autoload_paths)
@@ -475,7 +471,7 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
- raise NameError, "#{from_mod} is not missing constant #{const_name}!" if local_const_defined?(from_mod, const_name)
+ raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false)
qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
@@ -484,12 +480,12 @@ module ActiveSupport #:nodoc:
if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
require_or_load file_path
- raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless local_const_defined?(from_mod, const_name)
+ raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name, false)
return from_mod.const_get(const_name)
elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
return mod
elsif (parent = from_mod.parent) && parent != from_mod &&
- ! from_mod.parents.any? { |p| local_const_defined?(p, const_name) }
+ ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
# If our parents do not have a constant named +const_name+ then we are free
# to attempt to load upwards. If they do have such a constant, then this
# const_missing must be due to from_mod::const_name, which should not
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 45b9dda5ca..176edefa42 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/module/deprecation'
require 'active_support/deprecation/behaviors'
require 'active_support/deprecation/reporting'
require 'active_support/deprecation/method_wrappers'
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 94f8d7133e..fc962dcb57 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -6,17 +6,31 @@ module ActiveSupport
# Whether to print a backtrace along with the warning.
attr_accessor :debug
- # Returns the set behavior or if one isn't set, defaults to +:stderr+
+ # Returns the current behavior or if one isn't set, defaults to +:stderr+
def behavior
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
end
- # Sets the behavior to the specified value. Can be a single value or an array.
+ # Sets the behavior to the specified value. Can be a single value, array, or
+ # an object that responds to +call+.
#
- # Examples
+ # Available behaviors:
+ #
+ # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
+ # [+log+] Log all deprecation warnings to +Rails.logger+.
+ # [+notify+] Use <tt>ActiveSupport::Notifications</tt> to notify +deprecation.rails+.
+ # [+silence+] Do nothing.
+ #
+ # Setting behaviors only affects deprecations that happen after boot time.
+ # Deprecation warnings raised by gems are not affected by this setting because
+ # they happen before Rails boots up.
#
# ActiveSupport::Deprecation.behavior = :stderr
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
+ # ActiveSupport::Deprecation.behavior = MyCustomHandler
+ # ActiveSupport::Deprecation.behavior = proc { |message, callstack|
+ # # custom stuff
+ # }
def behavior=(behavior)
@behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
end
@@ -41,8 +55,9 @@ module ActiveSupport
},
:notify => Proc.new { |message, callstack|
ActiveSupport::Notifications.instrument("deprecation.rails",
- :message => message, :callstack => callstack)
- }
+ :message => message, :callstack => callstack)
+ },
+ :silence => Proc.new { |message, callstack| }
}
end
end
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index d0d8b577b3..c5de5e6a95 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -1,11 +1,10 @@
-require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
- class << Deprecation
+ module Deprecation
# Declare that a method has been deprecated.
- def deprecate_methods(target_module, *method_names)
+ def self.deprecate_methods(target_module, *method_names)
options = method_names.extract_options!
method_names += options.keys
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 00c67a470d..2cdc991120 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -5,7 +5,6 @@ require 'active_support/core_ext/object/acts_like'
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
# Time#advance, respectively. It mainly supports the methods on Numeric.
- # Example:
#
# 1.month.ago # equivalent to Time.now.advance(:months => -1)
class Duration < BasicObject
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index fe22b9515b..8860636168 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -9,7 +9,7 @@ module ActiveSupport
# the filesystem or not;
#
# * +execute+ which executes the given block on initialization
- # and updates the counter to the latest timestamp;
+ # and updates the latest watched files and timestamp;
#
# * +execute_if_updated+ which just executes the block if it was updated;
#
@@ -39,13 +39,13 @@ module ActiveSupport
#
# == Implementation details
#
- # This particular implementation checks for added and updated files,
- # but not removed files. Directories lookup are compiled to a glob for
- # performance. Therefore, while someone can add new files to the +files+
- # array after initialization (and parts of Rails do depend on this feature),
- # adding new directories after initialization is not allowed.
+ # This particular implementation checks for added, updated, and removed
+ # files. Directories lookup are compiled to a glob for performance.
+ # Therefore, while someone can add new files to the +files+ array after
+ # initialization (and parts of Rails do depend on this feature), adding
+ # new directories after initialization is not supported.
#
- # Notice that other objects that implements FileUpdateChecker API may
+ # Notice that other objects that implement the FileUpdateChecker API may
# not even allow new files to be added after initialization. If this
# is the case, we recommend freezing the +files+ after initialization to
# avoid changes that won't make effect.
@@ -53,27 +53,41 @@ module ActiveSupport
@files = files
@glob = compile_glob(dirs)
@block = block
+
+ @watched = nil
@updated_at = nil
- @last_update_at = updated_at
+
+ @last_watched = watched
+ @last_update_at = updated_at(@last_watched)
end
- # Check if any of the entries were updated. If so, the updated_at
- # value is cached until the block is executed via +execute+ or +execute_if_updated+
+ # Check if any of the entries were updated. If so, the watched and/or
+ # updated_at values are cached until the block is executed via +execute+
+ # or +execute_if_updated+
def updated?
- current_updated_at = updated_at
- if @last_update_at < current_updated_at
- @updated_at = updated_at
+ current_watched = watched
+ if @last_watched.size != current_watched.size
+ @watched = current_watched
true
else
- false
+ current_updated_at = updated_at(current_watched)
+ if @last_update_at < current_updated_at
+ @watched = current_watched
+ @updated_at = current_updated_at
+ true
+ else
+ false
+ end
end
end
- # Executes the given block and updates the counter to latest timestamp.
+ # Executes the given block and updates the latest watched files and timestamp.
def execute
- @last_update_at = updated_at
+ @last_watched = watched
+ @last_update_at = updated_at(@last_watched)
@block.call
ensure
+ @watched = nil
@updated_at = nil
end
@@ -89,16 +103,19 @@ module ActiveSupport
private
- def updated_at #:nodoc:
- @updated_at || begin
+ def watched
+ @watched || begin
all = @files.select { |f| File.exists?(f) }
- all.concat Dir[@glob] if @glob
- all.map! { |path| File.mtime(path) }
- all.max || Time.at(0)
+ all.concat(Dir[@glob]) if @glob
+ all
end
end
- def compile_glob(hash) #:nodoc:
+ def updated_at(paths)
+ @updated_at || paths.map { |path| File.mtime(path) }.max || Time.at(0)
+ end
+
+ def compile_glob(hash)
hash.freeze # Freeze so changes aren't accidently pushed
return if hash.empty?
@@ -112,7 +129,7 @@ module ActiveSupport
key.gsub(',','\,')
end
- def compile_ext(array) #:nodoc:
+ def compile_ext(array)
array = Array(array)
return if array.empty?
".{#{array.join(",")}}"
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index f9c5e5e2f8..188653bd9b 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -6,4 +6,5 @@ rescue LoadError => e
raise e
end
+ActiveSupport.run_load_hooks(:i18n)
I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index b3eb1333ca..c04c2ed15b 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -16,8 +16,8 @@ module ActiveSupport
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
- inflect.plural(/(m|l)ouse$/i, '\1ice')
- inflect.plural(/(m|l)ice$/i, '\1ice')
+ inflect.plural(/^(m|l)ouse$/i, '\1ice')
+ inflect.plural(/^(m|l)ice$/i, '\1ice')
inflect.plural(/^(ox)$/i, '\1en')
inflect.plural(/^(oxen)$/i, '\1')
inflect.plural(/(quiz)$/i, '\1zes')
@@ -26,7 +26,7 @@ module ActiveSupport
inflect.singular(/(ss)$/i, '\1')
inflect.singular(/(n)ews$/i, '\1ews')
inflect.singular(/([ti])a$/i, '\1um')
- inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1\2sis')
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
inflect.singular(/([^f])ves$/i, '\1fe')
inflect.singular(/(hive)s$/i, '\1')
@@ -36,7 +36,7 @@ module ActiveSupport
inflect.singular(/(s)eries$/i, '\1eries')
inflect.singular(/(m)ovies$/i, '\1ovie')
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
- inflect.singular(/(m|l)ice$/i, '\1ouse')
+ inflect.singular(/^(m|l)ice$/i, '\1ouse')
inflect.singular(/(bus)(es)?$/i, '\1')
inflect.singular(/(o)es$/i, '\1')
inflect.singular(/(shoe)s$/i, '\1')
@@ -58,6 +58,6 @@ module ActiveSupport
inflect.irregular('cow', 'kine')
inflect.irregular('zombie', 'zombies')
- inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
end
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 13b23d627a..600e353812 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/array/prepend_and_append'
module ActiveSupport
module Inflector
# A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
- # inflection rules. Examples:
+ # inflection rules.
#
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1\2en'
@@ -40,7 +40,6 @@ module ActiveSupport
# A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
# convert the acronym into a non-delimited single lowercase word when passed to +underscore+.
#
- # Examples:
# acronym 'HTML'
# titleize 'html' #=> 'HTML'
# camelize 'html' #=> 'HTML'
@@ -70,7 +69,6 @@ module ActiveSupport
# `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
# capitalization. The only restriction is that the word must begin with a capital letter.
#
- # Examples:
# acronym 'RESTful'
# underscore 'RESTful' #=> 'restful'
# underscore 'RESTfulController' #=> 'restful_controller'
@@ -105,7 +103,6 @@ module ActiveSupport
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
#
- # Examples:
# irregular 'octopus', 'octopi'
# irregular 'person', 'people'
def irregular(singular, plural)
@@ -127,7 +124,6 @@ module ActiveSupport
# Add uncountable words that shouldn't be attempted inflected.
#
- # Examples:
# uncountable "money"
# uncountable "money", "information"
# uncountable %w( money information rice )
@@ -139,7 +135,6 @@ module ActiveSupport
# When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
# When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
#
- # Examples:
# human /_cnt$/i, '\1_count'
# human "legacy_col_person_name", "Name"
def human(rule, replacement)
@@ -150,7 +145,6 @@ module ActiveSupport
# Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
# <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
#
- # Examples:
# clear :all
# clear :plurals
def clear(scope = :all)
@@ -166,7 +160,6 @@ module ActiveSupport
# Yields a singleton instance of Inflector::Inflections so you can specify additional
# inflector rules.
#
- # Example:
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.uncountable "rails"
# end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 4cebad742f..48296841aa 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -16,7 +16,6 @@ module ActiveSupport
# Returns the plural form of the word in the string.
#
- # Examples:
# "post".pluralize # => "posts"
# "octopus".pluralize # => "octopi"
# "sheep".pluralize # => "sheep"
@@ -28,7 +27,6 @@ module ActiveSupport
# The reverse of +pluralize+, returns the singular form of a word in a string.
#
- # Examples:
# "posts".singularize # => "post"
# "octopi".singularize # => "octopus"
# "sheep".singularize # => "sheep"
@@ -43,7 +41,6 @@ module ActiveSupport
#
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
#
- # Examples:
# "active_model".camelize # => "ActiveModel"
# "active_model".camelize(:lower) # => "activeModel"
# "active_model/errors".camelize # => "ActiveModel::Errors"
@@ -67,7 +64,6 @@ module ActiveSupport
#
# Changes '::' to '/' to convert namespaces to paths.
#
- # Examples:
# "ActiveModel".underscore # => "active_model"
# "ActiveModel::Errors".underscore # => "active_model/errors"
#
@@ -77,7 +73,7 @@ module ActiveSupport
# "SSLError".underscore.camelize # => "SslError"
def underscore(camel_cased_word)
word = camel_cased_word.to_s.dup
- word.gsub!(/::/, '/')
+ word.gsub!('::', '/')
word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
@@ -89,7 +85,6 @@ module ActiveSupport
# Capitalizes the first word and turns underscores into spaces and strips a
# trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
#
- # Examples:
# "employee_salary" # => "Employee salary"
# "author_id" # => "Author"
def humanize(lower_case_and_underscored_word)
@@ -106,21 +101,19 @@ module ActiveSupport
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
# used in the Rails internals.
#
- # +titleize+ is also aliased as as +titlecase+.
+ # +titleize+ is also aliased as +titlecase+.
#
- # Examples:
# "man from the boondocks".titleize # => "Man From The Boondocks"
# "x-men: the last stand".titleize # => "X Men: The Last Stand"
# "TheManWithoutAPast".titleize # => "The Man Without A Past"
# "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark"
def titleize(word)
- humanize(underscore(word)).gsub(/\b(['’`]?[a-z])/) { $1.capitalize }
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
end
# Create the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
- # Examples
# "RawScaledScorer".tableize # => "raw_scaled_scorers"
# "egg_and_ham".tableize # => "egg_and_hams"
# "fancyCategory".tableize # => "fancy_categories"
@@ -132,7 +125,6 @@ module ActiveSupport
# Note that this returns a string and not a Class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # Examples:
# "egg_and_hams".classify # => "EggAndHam"
# "posts".classify # => "Post"
#
@@ -145,8 +137,7 @@ module ActiveSupport
# Replaces underscores with dashes in the string.
#
- # Example:
- # "puni_puni" # => "puni-puni"
+ # "puni_puni".dasherize # => "puni-puni"
def dasherize(underscored_word)
underscored_word.tr('_', '-')
end
@@ -183,7 +174,6 @@ module ActiveSupport
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # Examples:
# "Message".foreign_key # => "message_id"
# "Message".foreign_key(false) # => "messageid"
# "Admin::Post".foreign_key # => "post_id"
@@ -253,7 +243,6 @@ module ActiveSupport
# Returns the suffix that should be added to a number to denote the position
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
#
- # Examples:
# ordinal(1) # => "st"
# ordinal(2) # => "nd"
# ordinal(1002) # => "nd"
@@ -276,7 +265,6 @@ module ActiveSupport
# Turns a number into an ordinal string used to denote the position in an
# ordered sequence such as 1st, 2nd, 3rd, 4th.
#
- # Examples:
# ordinalize(1) # => "1st"
# ordinalize(2) # => "2nd"
# ordinalize(1002) # => "1002nd"
@@ -302,7 +290,6 @@ module ActiveSupport
# Applies inflection rules for +singularize+ and +pluralize+.
#
- # Examples:
# apply_inflections("post", inflections.plurals) # => "posts"
# apply_inflections("posts", inflections.singulars) # => "post"
def apply_inflections(word, rules)
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 40e7a0e389..a372b6d1f7 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -66,8 +66,6 @@ module ActiveSupport
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
#
- # ==== Examples
- #
# class Person
# def to_param
# "#{id}-#{name.parameterize}"
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index cbeb6c0a28..986a764479 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -9,7 +9,7 @@ module ActiveSupport
module JSON
class << self
def decode(json, options ={})
- data = MultiJson.decode(json, options)
+ data = MultiJson.load(json, options)
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
@@ -18,12 +18,12 @@ module ActiveSupport
end
def engine
- MultiJson.engine
+ MultiJson.adapter
end
alias :backend :engine
def engine=(name)
- MultiJson.engine = name
+ MultiJson.use(name)
end
alias :backend= :engine=
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index b2adfea273..a6e4e7ced2 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -17,6 +17,7 @@ module ActiveSupport
class << self
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
+ :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -104,6 +105,9 @@ module ActiveSupport
# If true, use ISO 8601 format for dates and times. Otherwise, fall back to the Active Support legacy format.
attr_accessor :use_standard_json_time_format
+ # If false, serializes BigDecimal objects as numeric instead of wrapping them in a string
+ attr_accessor :encode_big_decimal_as_string
+
attr_accessor :escape_regex
attr_reader :escape_html_entities_in_json
@@ -132,7 +136,8 @@ module ActiveSupport
end
self.use_standard_json_time_format = true
- self.escape_html_entities_in_json = false
+ self.escape_html_entities_in_json = true
+ self.encode_big_decimal_as_string = true
end
end
end
@@ -182,6 +187,12 @@ class Numeric
def encode_json(encoder) to_s end #:nodoc:
end
+class Float
+ # Encoding Infinity or NaN to JSON should return "null". The default returns
+ # "Infinity" or "NaN" what breaks parsing the JSON. E.g. JSON.parse('[NaN]').
+ def as_json(options = nil) finite? ? self : NilClass::AS_JSON end #:nodoc:
+end
+
class BigDecimal
# A BigDecimal would be naturally represented as a JSON number. Most libraries,
# however, parse non-integer JSON numbers directly as floats. Clients using
@@ -191,7 +202,15 @@ class BigDecimal
# That's why a JSON string is returned. The JSON literal is not numeric, but if
# the other end knows by contract that the data is supposed to be a BigDecimal,
# it still has the chance to post-process the string and get the real value.
- def as_json(options = nil) to_s end #:nodoc:
+ #
+ # Use ActiveSupport.use_standard_json_big_decimal_format = true to override this behaviour
+ def as_json(options = nil) #:nodoc:
+ if finite?
+ ActiveSupport.encode_big_decimal_as_string ? to_s : self
+ else
+ NilClass::AS_JSON
+ end
+ end
end
class Regexp
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 7b7fc81e6c..b65ea6208c 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -61,8 +61,12 @@ module ActiveSupport
@logged = Hash.new { |h,k| h[k] = [] }
end
- def method_missing(level, message)
- @logged[level] << message
+ def method_missing(level, message = nil)
+ if block_given?
+ @logged[level] << yield
+ else
+ @logged[level] << message
+ end
end
def logged(level)
diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb
index 5efe13c537..977fe95dbe 100644
--- a/activesupport/lib/active_support/multibyte.rb
+++ b/activesupport/lib/active_support/multibyte.rb
@@ -7,7 +7,6 @@ module ActiveSupport #:nodoc:
# class so you can support other encodings. See the ActiveSupport::Multibyte::Chars implementation for
# an example how to do this.
#
- # Example:
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
def self.proxy_class=(klass)
@proxy_class = klass
@@ -18,4 +17,4 @@ module ActiveSupport #:nodoc:
@proxy_class ||= ActiveSupport::Multibyte::Chars
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 9a748dfa60..4fe925f7f4 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -62,8 +62,8 @@ module ActiveSupport #:nodoc:
# Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
# only if the optional second parameter evaluates to +true+.
- def respond_to?(method, include_private=false)
- super || @wrapped_string.respond_to?(method, include_private)
+ def respond_to_missing?(method, include_private)
+ @wrapped_string.respond_to?(method, include_private)
end
# Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
@@ -74,7 +74,6 @@ module ActiveSupport #:nodoc:
# Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars
# instances instead of String. This makes chaining methods easier.
#
- # Example:
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
def split(*args)
@wrapped_string.split(*args).map { |i| i.mb_chars }
@@ -88,7 +87,6 @@ module ActiveSupport #:nodoc:
# Reverses all characters in the string.
#
- # Example:
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
def reverse
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
@@ -97,7 +95,6 @@ module ActiveSupport #:nodoc:
# Limits the byte size of the string to a number of bytes without breaking characters. Usable
# when the storage for a string is limited for some reason.
#
- # Example:
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
def limit(limit)
slice(0...translate_offset(limit))
@@ -105,7 +102,6 @@ module ActiveSupport #:nodoc:
# Converts characters in the string to uppercase.
#
- # Example:
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
def upcase
chars Unicode.upcase(@wrapped_string)
@@ -113,7 +109,6 @@ module ActiveSupport #:nodoc:
# Converts characters in the string to lowercase.
#
- # Example:
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
def downcase
chars Unicode.downcase(@wrapped_string)
@@ -121,7 +116,6 @@ module ActiveSupport #:nodoc:
# Converts characters in the string to the opposite case.
#
- # Example:
# 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
def swapcase
chars Unicode.swapcase(@wrapped_string)
@@ -129,7 +123,6 @@ module ActiveSupport #:nodoc:
# Converts the first character to uppercase and the remainder to lowercase.
#
- # Example:
# 'über'.mb_chars.capitalize.to_s # => "Über"
def capitalize
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
@@ -137,7 +130,6 @@ module ActiveSupport #:nodoc:
# Capitalizes the first letter of every word, when possible.
#
- # Example:
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
# "日本語".mb_chars.titleize # => "日本語"
def titleize
@@ -157,7 +149,6 @@ module ActiveSupport #:nodoc:
# Performs canonical decomposition on all the characters.
#
- # Example:
# 'é'.length # => 2
# 'é'.mb_chars.decompose.to_s.length # => 3
def decompose
@@ -166,7 +157,6 @@ module ActiveSupport #:nodoc:
# Performs composition on all the characters.
#
- # Example:
# 'é'.length # => 3
# 'é'.mb_chars.compose.to_s.length # => 2
def compose
@@ -175,7 +165,6 @@ module ActiveSupport #:nodoc:
# Returns the number of grapheme clusters in the string.
#
- # Example:
# 'क्षि'.mb_chars.length # => 4
# 'क्षि'.mb_chars.grapheme_length # => 3
def grapheme_length
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index cb89d45c92..678f551193 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -15,7 +15,6 @@ module ActiveSupport
# The default normalization used for operations that require normalization. It can be set to any of the
# normalizations in NORMALIZATION_FORMS.
#
- # Example:
# ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
attr_accessor :default_normalization_form
@default_normalization_form = :kc
@@ -72,7 +71,6 @@ module ActiveSupport
# Unpack the string at grapheme boundaries. Returns a list of character lists.
#
- # Example:
# Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
# Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
def unpack_graphemes(string)
@@ -107,7 +105,6 @@ module ActiveSupport
# Reverse operation of unpack_graphemes.
#
- # Example:
# Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
def pack_graphemes(unpacked)
unpacked.flatten.pack('U*')
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 8cf7bdafda..6735c561d3 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -4,7 +4,7 @@ require 'active_support/notifications/fanout'
module ActiveSupport
# = Notifications
#
- # +ActiveSupport::Notifications+ provides an instrumentation API for Ruby.
+ # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for Ruby.
#
# == Instrumenters
#
@@ -44,26 +44,53 @@ module ActiveSupport
# event.duration # => 10 (in milliseconds)
# event.payload # => { :extra => :information }
#
- # The block in the +subscribe+ call gets the name of the event, start
+ # The block in the <tt>subscribe</tt> call gets the name of the event, start
# timestamp, end timestamp, a string with a unique identifier for that event
# (something like "535801666f04d0298cd6"), and a hash with the payload, in
# that order.
#
# If an exception happens during that particular instrumentation the payload will
- # have a key +:exception+ with an array of two elements as value: a string with
+ # have a key <tt>:exception</tt> with an array of two elements as value: a string with
# the name of the exception class, and the exception message.
#
- # As the previous example depicts, the class +ActiveSupport::Notifications::Event+
+ # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
# is able to take the arguments as they come and provide an object-oriented
# interface to that data.
#
+ # It is also possible to pass an object as the second parameter passed to the
+ # <tt>subscribe</tt> method instead of a block:
+ #
+ # module ActionController
+ # class PageRequest
+ # def call(name, started, finished, unique_id, payload)
+ # Rails.logger.debug ["notification:", name, started, finished, unique_id, payload].join(" ")
+ # end
+ # end
+ # end
+ #
+ # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
+ #
+ # resulting in the following output within the logs including a hash with the payload:
+ #
+ # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
+ # :controller=>"Devise::SessionsController",
+ # :action=>"new",
+ # :params=>{"action"=>"new", "controller"=>"devise/sessions"},
+ # :format=>:html,
+ # :method=>"GET",
+ # :path=>"/login/sign_in",
+ # :status=>200,
+ # :view_runtime=>279.3080806732178,
+ # :db_runtime=>40.053
+ # }
+ #
# You can also subscribe to all events whose name matches a certain regexp:
#
# ActiveSupport::Notifications.subscribe(/render/) do |*args|
# ...
# end
#
- # and even pass no argument to +subscribe+, in which case you are subscribing
+ # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
# to all events.
#
# == Temporary Subscriptions
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 8edd3960c7..1a3693f766 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -5,16 +5,20 @@ YAML.add_builtin_type("omap") do |type, val|
end
module ActiveSupport
- # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
- # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
- # implements a hash that preserves insertion order, as in Ruby 1.9:
+ # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
+ # insertion order.
#
# oh = ActiveSupport::OrderedHash.new
# oh[:a] = 1
# oh[:b] = 2
# oh.keys # => [:a, :b], this order is guaranteed
#
- # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
+ # Also, maps the +omap+ feature for YAML files
+ # (See http://yaml.org/type/omap.html) to support ordered items
+ # when loading from yaml.
+ #
+ # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
+ # with other implementations.
class OrderedHash < ::Hash
def to_yaml_type
"!tag:yaml.org,2002:omap"
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index 538e41e0eb..60e6cd55ad 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -36,7 +36,7 @@ module ActiveSupport #:nodoc:
end
end
- def respond_to?(name)
+ def respond_to_missing?(name, include_private)
true
end
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index d1c62c5087..30ac881090 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -24,5 +24,12 @@ module ActiveSupport
Time.zone_default = zone_default
end
+
+ initializer "active_support.set_configs" do |app|
+ app.config.active_support.each do |k, v|
+ k = "#{k}="
+ ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 85e84bc203..7aecdd11d3 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -48,6 +48,7 @@ module ActiveSupport
# end
# end
#
+ # Exceptions raised inside exception handlers are not propagated up.
def rescue_from(*klasses, &block)
options = klasses.extract_options!
diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb
deleted file mode 100644
index 13e96b3596..0000000000
--- a/activesupport/lib/active_support/ruby/shim.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# Backported Ruby builtins so you can code with the latest & greatest
-# but still run on any Ruby 1.8.x.
-#
-# Date next_year, next_month
-# DateTime to_date, to_datetime, xmlschema
-# Enumerable group_by, none?
-# String ord
-# Time to_date, to_time, to_datetime
-require 'active_support'
-require 'active_support/core_ext/date/calculations'
-require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/enumerable'
-require 'active_support/core_ext/string/conversions'
-require 'active_support/core_ext/string/interpolation'
-require 'active_support/core_ext/time/conversions'
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index 538a36f6d9..5e080df518 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -3,7 +3,7 @@ require 'logger'
require 'active_support/logger'
module ActiveSupport
- # Wraps any standard Logger object to provide tagging capabilities. Examples:
+ # Wraps any standard Logger object to provide tagging capabilities.
#
# logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
# logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff"
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 244ee1a224..2bea0f991a 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -61,7 +61,7 @@ module ActiveSupport
ensure
begin
teardown
- run_callbacks :teardown, :enumerator => :reverse_each
+ run_callbacks :teardown
rescue Exception => e
result = @runner.puke(self.class, method_name, e)
end
@@ -126,7 +126,7 @@ module ActiveSupport
def record; end
end
- class Benchmarker < Performer
+ class Benchmarker < Performer
def initialize(*args)
super
@supported = @metric.respond_to?('measure')
@@ -196,6 +196,7 @@ module ActiveSupport
class Base
include ActionView::Helpers::NumberHelper
+ include ActionView::Helpers::OutputSafetyHelper
attr_reader :total
@@ -207,7 +208,7 @@ module ActiveSupport
@name ||= self.class.name.demodulize.underscore
end
- def benchmark
+ def benchmark
with_gc_stats do
before = measure
yield
diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb
index b347539f13..34e3f9f45f 100644
--- a/activesupport/lib/active_support/testing/performance/jruby.rb
+++ b/activesupport/lib/active_support/testing/performance/jruby.rb
@@ -42,7 +42,7 @@ module ActiveSupport
klasses.each do |klass|
fname = output_filename(klass)
FileUtils.mkdir_p(File.dirname(fname))
- file = File.open(fname, 'wb') do |file|
+ File.open(fname, 'wb') do |file|
klass.new(@data).printProfile(file)
end
end
diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb
index b7a34ea279..1104fc0a03 100644
--- a/activesupport/lib/active_support/testing/performance/ruby.rb
+++ b/activesupport/lib/active_support/testing/performance/ruby.rb
@@ -36,7 +36,7 @@ module ActiveSupport
RubyProf.pause
full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
@data = RubyProf.stop
- @total = @data.threads.values.sum(0) { |method_infos| method_infos.max.total_time }
+ @total = @data.threads.sum(0) { |thread| thread.methods.max.total_time }
end
def record
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 9634b52ecf..bcd5d78b54 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -4,10 +4,6 @@ module ActiveSupport
autoload :Duration, 'active_support/duration'
autoload :TimeWithZone, 'active_support/time_with_zone'
autoload :TimeZone, 'active_support/values/time_zone'
-
- on_load_all do
- [Duration, TimeWithZone, TimeZone]
- end
end
require 'date'
diff --git a/activesupport/lib/active_support/time/autoload.rb b/activesupport/lib/active_support/time/autoload.rb
deleted file mode 100644
index c9a7731b39..0000000000
--- a/activesupport/lib/active_support/time/autoload.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module ActiveSupport
- autoload :Duration, 'active_support/duration'
- autoload :TimeWithZone, 'active_support/time_with_zone'
- autoload :TimeZone, 'active_support/values/time_zone'
-end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 1cb71012ef..451520ac5c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -8,7 +8,6 @@ module ActiveSupport
#
# You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> . Instead use methods
# +local+, +parse+, +at+ and +now+ on TimeZone instances, and +in_time_zone+ on Time and DateTime instances.
- # Examples:
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
@@ -20,7 +19,6 @@ module ActiveSupport
# See Time and TimeZone for further documentation of these methods.
#
# TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable.
- # Examples:
#
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00
# t.hour # => 13
@@ -35,8 +33,10 @@ module ActiveSupport
# t.is_a?(ActiveSupport::TimeWithZone) # => true
#
class TimeWithZone
+
+ # Report class name as 'Time' to thwart type checking
def self.name
- 'Time' # Report class name as 'Time' to thwart type checking
+ 'Time'
end
include Comparable
@@ -120,8 +120,6 @@ module ActiveSupport
# %Y/%m/%d %H:%M:%S +offset style by setting <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
# to false.
#
- # ==== Examples
- #
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
# Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
# # => "2005-02-01T15:15:10Z"
@@ -311,16 +309,15 @@ module ActiveSupport
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
- def respond_to?(sym, include_priv = false)
+ def respond_to_missing?(sym, include_priv)
# consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
- return false if sym.to_s == 'acts_like_date?'
- super || time.respond_to?(sym, include_priv)
+ return false if sym.to_sym == :acts_like_date?
+ time.respond_to?(sym, include_priv)
end
# Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+.
def method_missing(sym, *args, &block)
- result = time.__send__(sym, *args, &block)
- result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result
+ wrap_with_time_zone time.__send__(sym, *args, &block)
end
private
@@ -338,11 +335,21 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0)
+ ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0)
end
def duration_of_variable_length?(obj)
ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) }
end
+
+ def wrap_with_time_zone(time)
+ if time.acts_like?(:time)
+ self.class.new(nil, time_zone, time)
+ elsif time.is_a?(Range)
+ wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end)
+ else
+ time
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index ce46c46092..28bc06f103 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -28,7 +28,7 @@ module ActiveSupport
MAPPING = {
"International Date Line West" => "Pacific/Midway",
"Midway Island" => "Pacific/Midway",
- "Samoa" => "Pacific/Pago_Pago",
+ "American Samoa" => "Pacific/Pago_Pago",
"Hawaii" => "Pacific/Honolulu",
"Alaska" => "America/Juneau",
"Pacific Time (US & Canada)" => "America/Los_Angeles",
@@ -167,14 +167,16 @@ module ActiveSupport
"Marshall Is." => "Pacific/Majuro",
"Auckland" => "Pacific/Auckland",
"Wellington" => "Pacific/Auckland",
- "Nuku'alofa" => "Pacific/Tongatapu"
+ "Nuku'alofa" => "Pacific/Tongatapu",
+ "Tokelau Is." => "Pacific/Fakaofo",
+ "Samoa" => "Pacific/Apia"
}
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
# Assumes self represents an offset from UTC in seconds (as returned from Time#utc_offset)
- # and turns this into an +HH:MM formatted string. Example:
+ # and turns this into an +HH:MM formatted string.
#
# TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
def self.seconds_to_utc_offset(seconds, colon = true)
@@ -202,6 +204,7 @@ module ActiveSupport
@current_period = nil
end
+ # Returns the offset of this time zone from UTC in seconds.
def utc_offset
if @utc_offset
@utc_offset
@@ -236,7 +239,7 @@ module ActiveSupport
"(GMT#{formatted_offset}) #{name}"
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values.
#
# Time.zone = "Hawaii" # => "Hawaii"
# Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
@@ -245,7 +248,7 @@ module ActiveSupport
ActiveSupport::TimeWithZone.new(nil, self, time)
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch.
#
# Time.zone = "Hawaii" # => "Hawaii"
# Time.utc(2000).to_f # => 946684800.0
@@ -255,7 +258,7 @@ module ActiveSupport
utc.in_time_zone(self)
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string.
#
# Time.zone = "Hawaii" # => "Hawaii"
# Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
@@ -268,7 +271,12 @@ module ActiveSupport
date_parts = Date._parse(str)
return if date_parts.empty?
time = Time.parse(str, now) rescue DateTime.parse(str)
+
if date_parts[:offset].nil?
+ if date_parts[:hour] && time.hour != date_parts[:hour]
+ time = DateTime.parse(str)
+ end
+
ActiveSupport::TimeWithZone.new(nil, self, time)
else
time.in_time_zone(self)
@@ -276,7 +284,7 @@ module ActiveSupport
end
# Returns an ActiveSupport::TimeWithZone instance representing the current time
- # in the time zone represented by +self+. Example:
+ # in the time zone represented by +self+.
#
# Time.zone = 'Hawaii' # => "Hawaii"
# Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 40e25ce0cd..57ed4a6b60 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -7,9 +7,6 @@ ensure
$VERBOSE = old
end
-lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/string/encoding'
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 4371a02934..d62b782e2d 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -568,6 +568,13 @@ class FileStoreTest < ActiveSupport::TestCase
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
+ def test_clear
+ filepath = File.join(cache_dir, ".gitkeep")
+ FileUtils.touch(filepath)
+ @cache.clear
+ assert File.exist?(filepath)
+ end
+
def test_key_transformation
key = @cache.send(:key_file_path, "views/index?id=1")
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
@@ -585,7 +592,7 @@ class FileStoreTest < ActiveSupport::TestCase
key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
path = @cache.send(:key_file_path, key)
Dir::Tmpname.create(path) do |tmpname, n, opts|
- assert (File.basename(tmpname+'.lock').length <= 255), "Temp filename too long: #{File.basename(tmpname+'.lock').length}"
+ assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}"
end
end
@@ -677,6 +684,13 @@ class MemoryStoreTest < ActiveSupport::TestCase
assert @cache.exist?(2)
assert !@cache.exist?(1)
end
+
+ def test_write_with_unless_exist
+ assert_equal true, @cache.write(1, "aaaaaaaaaa")
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true)
+ @cache.write(1, nil)
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true)
+ end
end
uses_memcached 'memcached backed store' do
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index e5ac9511df..6be8ea8b84 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -29,7 +29,7 @@ class GrandParent
end
def dispatch
- run_callbacks(:dispatch, action_name) do
+ run_callbacks :dispatch do
@log << action_name
end
self
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 25688a9da5..b7c3b130c3 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -112,7 +112,7 @@ module CallbacksTest
end
def dispatch
- run_callbacks :dispatch, action_name do
+ run_callbacks :dispatch do
@logger << "Done"
end
self
@@ -153,7 +153,7 @@ module CallbacksTest
end
def save
- run_callbacks :save, :action
+ run_callbacks :save
end
end
@@ -338,7 +338,7 @@ module CallbacksTest
end
def save
- run_callbacks :save, "hyphen-ated" do
+ run_callbacks :save do
@stuff
end
end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 760d138623..e14a137f84 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -374,14 +374,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day
+ assert_equal Time.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
end
def test_end_of_day_when_zone_is_set
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'UTC' do
with_tz_default zone do
- assert_equal zone.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day
+ assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone
end
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index cd8cb5d18b..3da0825489 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -91,6 +91,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day
end
+ def test_beginning_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,0,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_hour
+ end
+
+ def test_end_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour
+ end
+
def test_beginning_of_month
assert_equal DateTime.civil(2005,2,1,0,0,0), DateTime.civil(2005,2,22,10,10,10).beginning_of_month
end
diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/deep_dup_test.rb
new file mode 100644
index 0000000000..91d558dbb5
--- /dev/null
+++ b/activesupport/test/core_ext/deep_dup_test.rb
@@ -0,0 +1,53 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class DeepDupTest < ActiveSupport::TestCase
+
+ def test_array_deep_dup
+ array = [1, [2, 3]]
+ dup = array.deep_dup
+ dup[1][2] = 4
+ assert_equal nil, array[1][2]
+ assert_equal 4, dup[1][2]
+ end
+
+ def test_hash_deep_dup
+ hash = { :a => { :b => 'b' } }
+ dup = hash.deep_dup
+ dup[:a][:c] = 'c'
+ assert_equal nil, hash[:a][:c]
+ assert_equal 'c', dup[:a][:c]
+ end
+
+ def test_array_deep_dup_with_hash_inside
+ array = [1, { :a => 2, :b => 3 } ]
+ dup = array.deep_dup
+ dup[1][:c] = 4
+ assert_equal nil, array[1][:c]
+ assert_equal 4, dup[1][:c]
+ end
+
+ def test_hash_deep_dup_with_array_inside
+ hash = { :a => [1, 2] }
+ dup = hash.deep_dup
+ dup[:a][2] = 'c'
+ assert_equal nil, hash[:a][2]
+ assert_equal 'c', dup[:a][2]
+ end
+
+ def test_deep_dup_initialize
+ zero_hash = Hash.new 0
+ hash = { :a => zero_hash }
+ dup = hash.deep_dup
+ assert_equal 0, dup[:a][44]
+ end
+
+ def test_object_deep_dup
+ object = Object.new
+ dup = object.deep_dup
+ dup.instance_variable_set(:@a, 1)
+ assert !object.instance_variable_defined?(:@a)
+ assert dup.instance_variable_defined?(:@a)
+ end
+
+end
diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb
index 1105353e45..e0566e012c 100644
--- a/activesupport/test/core_ext/duplicable_test.rb
+++ b/activesupport/test/core_ext/duplicable_test.rb
@@ -5,8 +5,8 @@ require 'active_support/core_ext/numeric/time'
class DuplicableTest < ActiveSupport::TestCase
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds]
- YES = ['1', Object.new, /foo/, [], {}, Time.now]
- NO = [Class.new, Module.new]
+ YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
+ NO = []
begin
bd = BigDecimal.new('4.56')
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 80b3c16328..8239054117 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -363,21 +363,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, hash_1
end
- def test_deep_dup
- hash = { :a => { :b => 'b' } }
- dup = hash.deep_dup
- dup[:a][:c] = 'c'
- assert_equal nil, hash[:a][:c]
- assert_equal 'c', dup[:a][:c]
- end
-
- def test_deep_dup_initialize
- zero_hash = Hash.new 0
- hash = { :a => zero_hash }
- dup = hash.deep_dup
- assert_equal 0, dup[:a][44]
- end
-
def test_store_on_indifferent_access
hash = HashWithIndifferentAccess.new
hash.store(:test1, 1)
@@ -506,15 +491,21 @@ class HashExtTest < ActiveSupport::TestCase
original = { :a => 'x', :b => 'y', :c => 10 }
expected = { :a => 'x', :b => 'y' }
- # Should return a new hash with only the given keys.
+ # Should return a new hash without the given keys.
assert_equal expected, original.except(:c)
assert_not_equal expected, original
- # Should replace the hash with only the given keys.
+ # Should replace the hash without the given keys.
assert_equal expected, original.except!(:c)
assert_equal expected, original
end
+ def test_except_with_more_than_one_argument
+ original = { :a => 'x', :b => 'y', :c => 10 }
+ expected = { :a => 'x' }
+ assert_equal expected, original.except(:b, :c)
+ end
+
def test_except_with_original_frozen
original = { :a => 'x', :b => 'y' }
original.freeze
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 09ca4e7296..6e1b3ca010 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -60,6 +60,14 @@ Tester = Struct.new(:client) do
delegate :name, :to => :client, :prefix => false
end
+class ParameterSet
+ delegate :[], :[]=, :to => :@params
+
+ def initialize
+ @params = {:foo => "bar"}
+ end
+end
+
class Name
delegate :upcase, :to => :@full_name
@@ -83,6 +91,17 @@ class ModuleTest < ActiveSupport::TestCase
assert_equal "Fred", @david.place.name
end
+ def test_delegation_to_index_get_method
+ @params = ParameterSet.new
+ assert_equal "bar", @params[:foo]
+ end
+
+ def test_delegation_to_index_set_method
+ @params = ParameterSet.new
+ @params[:foo] = "baz"
+ assert_equal "baz", @params[:foo]
+ end
+
def test_delegation_down_hierarchy
assert_equal "CHICAGO", @david.upcase
end
diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb
index b027fccab3..98ab82609e 100644
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ b/activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -101,7 +101,7 @@ class ObjectTryTest < ActiveSupport::TestCase
assert !@string.respond_to?(method)
assert_raise(NoMethodError) { @string.try(method) }
end
-
+
def test_nonexisting_method_with_arguments
method = :undefined_method
assert !@string.respond_to?(method)
@@ -138,4 +138,16 @@ class ObjectTryTest < ActiveSupport::TestCase
nil.try { ran = true }
assert_equal false, ran
end
+
+ def test_try_with_private_method
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_raise(NoMethodError) { klass.new.try(:private_method) }
+ end
end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index cf1ec448c2..f0cdc0bfd4 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -41,6 +41,18 @@ class RangeTest < ActiveSupport::TestCase
assert((1..10).include?(1...10))
end
+ def test_should_compare_identical_inclusive
+ assert((1..10) === (1..10))
+ end
+
+ def test_should_compare_identical_exclusive
+ assert((1...10) === (1...10))
+ end
+
+ def test_should_compare_other_with_exlusive_end
+ assert((1..10) === (1...10))
+ end
+
def test_exclusive_end_should_not_include_identical_with_inclusive_end
assert !(1...10).include?(1..10)
end
@@ -57,16 +69,6 @@ class RangeTest < ActiveSupport::TestCase
assert((1.0...10.0).include?(1.0...10.0))
end
- def test_blockless_step
- assert_equal [1,3,5,7,9], (1..10).step(2)
- end
-
- def test_original_step
- array = []
- (1..10).step(2) {|i| array << i }
- assert_equal [1,3,5,7,9], array
- end
-
def test_cover_is_not_override
range = (1..3)
assert range.method(:include?) != range.method(:cover?)
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 6c2828b74e..8437ef1347 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -279,6 +279,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ')
end
+ def test_truncate_with_omission_and_regexp_seperator
+ assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/)
+ assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/)
+ assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/)
+ end
+
def test_truncate_multibyte
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
"\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10)
@@ -433,6 +439,37 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert @other_string.html_safe?
end
+ test "Concatting safe onto unsafe with % yields unsafe" do
+ @other_string = "other%s"
+ string = @string.html_safe
+
+ @other_string = @other_string % string
+ assert !@other_string.html_safe?
+ end
+
+ test "Concatting unsafe onto safe with % yields escaped safe" do
+ @other_string = "other%s".html_safe
+ string = @other_string % "<foo>"
+
+ assert_equal "other&lt;foo&gt;", string
+ assert string.html_safe?
+ end
+
+ test "Concatting safe onto safe with % yields safe" do
+ @other_string = "other%s".html_safe
+ string = @string.html_safe
+
+ @other_string = @other_string % string
+ assert @other_string.html_safe?
+ end
+
+ test "Concatting with % doesn't modify a string" do
+ @other_string = ["<p>", "<b>", "<h1>"]
+ _ = "%s %s %s".html_safe % @other_string
+
+ assert_equal ["<p>", "<b>", "<h1>"], @other_string
+ end
+
test "Concatting a fixnum to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index c542acca68..15c04bedf7 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -93,6 +93,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_beginning_of_hour
+ assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour
+ end
+
def test_beginning_of_month
assert_equal Time.local(2005,2,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_month
end
@@ -105,45 +109,49 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day
+ assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day
with_env_tz 'US/Eastern' do
- assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
- assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
end
with_env_tz 'NZ' do
- assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
- assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
end
end
def test_end_of_week
- assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday
+ assert_equal Time.local(2008,1,6,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_week
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,27,0,0,0).end_of_week #monday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,30,0,0,0).end_of_week #thursday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,31,0,0,0).end_of_week #friday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,01,0,0,0).end_of_week #saturday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,02,0,0,0).end_of_week #sunday
+ end
+
+ def test_end_of_hour
+ assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour
end
def test_end_of_month
- assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month
- assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month
- assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2005,3,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,2,28,23,59,59,Rational(999999999, 1000)), Time.local(2005,2,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,4,30,23,59,59,Rational(999999999, 1000)), Time.local(2005,4,20,10,10,10).end_of_month
end
def test_end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter
- assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter
- assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,15,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,3,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,21,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,1,0,0,0).end_of_quarter
+ assert_equal Time.local(2008,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2008,5,31,0,0,0).end_of_quarter
end
def test_end_of_year
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,22,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_year
end
def test_beginning_of_year
@@ -509,7 +517,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).prev_week(:wednesday)
end
end
-
+
def test_last_week
with_env_tz 'US/Eastern' do
assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week
@@ -549,12 +557,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_to_s
- time = Time.utc(2005, 2, 21, 17, 44, 30)
+ time = Time.utc(2005, 2, 21, 17, 44, 30.12345678901)
assert_equal time.to_default_s, time.to_s
assert_equal time.to_default_s, time.to_s(:doesnt_exist)
assert_equal "2005-02-21 17:44:30", time.to_s(:db)
assert_equal "21 Feb 17:44", time.to_s(:short)
assert_equal "17:44", time.to_s(:time)
+ assert_equal "20050221174430", time.to_s(:number)
+ assert_equal "20050221174430123456789", time.to_s(:nsec)
assert_equal "February 21, 2005 17:44", time.to_s(:long)
assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal)
with_env_tz "UTC" do
@@ -820,24 +830,32 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_all_day
- assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day
+ assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_day
+ end
+
+ def test_all_day_with_timezone
+ beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,0,0,0))
+ end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)))
+
+ assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin
+ assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.end
end
def test_all_week
- assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week
- assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week(:sunday)
+ assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week
+ assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week(:sunday)
end
def test_all_month
- assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month
+ assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_month
end
def test_all_quarter
- assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter
+ assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_quarter
end
def test_all_year
- assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year
+ assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_year
end
protected
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 7cf3842a16..1293f104e5 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -80,6 +80,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase
ActiveSupport.use_standard_json_time_format = old
end
+ def test_nsec
+ local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local)
+
+ assert_equal local.nsec, with_zone.nsec
+ assert_equal with_zone.nsec, 999999999
+ end
+
def test_strftime
assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z')
end
@@ -191,7 +199,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
end
- def future_with_time_current_as_time_with_zone
+ def test_future_with_time_current_as_time_with_zone
twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
Time.stubs(:current).returns(twz)
assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
@@ -450,6 +458,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_ruby_19_weekday_name_query_methods
%w(sunday? monday? tuesday? wednesday? thursday? friday? saturday?).each do |name|
assert_respond_to @twz, name
+ assert_equal @twz.send(name), @twz.method(name).call
end
end
@@ -482,36 +491,50 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect
end
- def beginning_of_year
+ def test_beginning_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 01 Jan 1999 00:00:00 EST -05:00", @twz.beginning_of_year.inspect
end
- def end_of_year
+ def test_end_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_year.inspect
end
- def beginning_of_month
+ def test_beginning_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
- assert_equal "Fri, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
+ assert_equal "Wed, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
end
- def end_of_month
+ def test_end_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_month.inspect
end
- def beginning_of_day
+ def test_beginning_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_day.inspect
end
- def end_of_day
+ def test_end_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_day.inspect
end
+ def test_beginning_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect
+ end
+
+ def test_end_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect
+ end
+
def test_since
assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index e821a285d7..e21f3efe36 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -93,6 +93,26 @@ class DeprecationTest < ActiveSupport::TestCase
assert_match(/foo=nil/, @b)
end
+ def test_default_stderr_behavior
+ ActiveSupport::Deprecation.behavior = :stderr
+ behavior = ActiveSupport::Deprecation.behavior.first
+
+ content = capture(:stderr) {
+ assert_nil behavior.call('Some error!', ['call stack!'])
+ }
+ assert_match(/Some error!/, content)
+ assert_match(/call stack!/, content)
+ end
+
+ def test_default_silence_behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ behavior = ActiveSupport::Deprecation.behavior.first
+
+ assert_blank capture(:stderr) {
+ assert_nil behavior.call('Some error!', ['call stack!'])
+ }
+ end
+
def test_deprecated_instance_variable_proxy
assert_not_deprecated { @dtc.request.size }
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index 066db7c0f9..8adff5de8d 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -44,8 +44,8 @@ class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
i = 0
checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
FileUtils.rm(FILES)
- assert !checker.execute_if_updated
- assert_equal 0, i
+ assert checker.execute_if_updated
+ assert_equal 1, i
end
def test_should_cache_updated_result_until_execute
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 879c3c1125..9fa1f417e4 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -47,6 +47,7 @@ module InflectorTestCases
"medium" => "media",
"stadium" => "stadia",
"analysis" => "analyses",
+ "my_analysis" => "my_analyses",
"node_child" => "node_children",
"child" => "children",
@@ -109,7 +110,9 @@ module InflectorTestCases
# regression tests against improper inflection regexes
"|ice" => "|ices",
- "|ouse" => "|ouses"
+ "|ouse" => "|ouses",
+ "slice" => "slices",
+ "police" => "police"
}
CamelToUnderscore = {
@@ -211,18 +214,22 @@ module InflectorTestCases
}
MixtureToTitleCase = {
- 'active_record' => 'Active Record',
- 'ActiveRecord' => 'Active Record',
- 'action web service' => 'Action Web Service',
- 'Action Web Service' => 'Action Web Service',
- 'Action web service' => 'Action Web Service',
- 'actionwebservice' => 'Actionwebservice',
- 'Actionwebservice' => 'Actionwebservice',
- "david's code" => "David's Code",
- "David's code" => "David's Code",
- "david's Code" => "David's Code",
- "Fred’s" => "Fred’s",
- "Fred`s" => "Fred`s"
+ 'active_record' => 'Active Record',
+ 'ActiveRecord' => 'Active Record',
+ 'action web service' => 'Action Web Service',
+ 'Action Web Service' => 'Action Web Service',
+ 'Action web service' => 'Action Web Service',
+ 'actionwebservice' => 'Actionwebservice',
+ 'Actionwebservice' => 'Actionwebservice',
+ "david's code" => "David's Code",
+ "David's code" => "David's Code",
+ "david's Code" => "David's Code",
+ "sgt. pepper's" => "Sgt. Pepper's",
+ "i've just seen a face" => "I've Just Seen A Face",
+ "maybe you'll be there" => "Maybe You'll Be There",
+ "¿por qué?" => '¿Por Qué?',
+ "Fred’s" => "Fred’s",
+ "Fred`s" => "Fred`s"
}
OrdinalNumbers = {
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index a2e61d88d5..0566ebf291 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -27,6 +27,10 @@ class TestJSONEncoding < ActiveSupport::TestCase
NilTests = [[ nil, %(null) ]]
NumericTests = [[ 1, %(1) ],
[ 2.5, %(2.5) ],
+ [ 0.0/0.0, %(null) ],
+ [ 1.0/0.0, %(null) ],
+ [ -1.0/0.0, %(null) ],
+ [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
[ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
@@ -270,6 +274,17 @@ class TestJSONEncoding < ActiveSupport::TestCase
JSON.parse(json_string_and_date))
end
+ def test_opt_out_big_decimal_string_serialization
+ big_decimal = BigDecimal('2.5')
+
+ begin
+ ActiveSupport.encode_big_decimal_as_string = false
+ assert_equal big_decimal.to_s, big_decimal.to_json
+ ensure
+ ActiveSupport.encode_big_decimal_as_string = true
+ end
+ end
+
protected
def object_keys(json_object)
diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index 8e160714b1..2a0e8d20ed 100644
--- a/activesupport/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -11,7 +11,7 @@ class MyLogSubscriber < ActiveSupport::LogSubscriber
def foo(event)
debug "debug"
- info "info"
+ info { "info" }
warn "warn"
end
diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/logger_test.rb
index 615635607c..eedeca30a8 100644
--- a/activesupport/test/buffered_logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -3,11 +3,9 @@ require 'multibyte_test_helpers'
require 'stringio'
require 'fileutils'
require 'tempfile'
-require 'active_support/testing/deprecation'
-class BufferedLoggerTest < ActiveSupport::TestCase
+class LoggerTest < ActiveSupport::TestCase
include MultibyteTestHelpers
- include ActiveSupport::Testing::Deprecation
Logger = ActiveSupport::Logger
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 90aa13b3e6..a8d69d0ec3 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -458,6 +458,15 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
assert !''.mb_chars.respond_to?(:undefined_method) # Not defined
end
+ def test_method_works_for_proxyed_methods
+ assert_equal 'll', 'hello'.mb_chars.method(:slice).call(2..3) # Defined on Chars
+ chars = 'hello'.mb_chars
+ assert_equal 'Hello', chars.method(:capitalize!).call # Defined on Chars
+ assert_equal 'Hello', chars
+ assert_equal 'jello', 'hello'.mb_chars.method(:gsub).call(/h/, 'j') # Defined on String
+ assert_raise(NameError){ ''.mb_chars.method(:undefined_method) } # Not defined
+ end
+
def test_acts_like_string
assert 'Bambi'.mb_chars.acts_like_string?
end
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index 3526c7a366..f60f9a58e3 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -77,4 +77,12 @@ class OrderedOptionsTest < ActiveSupport::TestCase
assert copy.kind_of?(original.class)
assert_not_equal copy.object_id, original.object_id
end
+
+ def test_introspection
+ a = ActiveSupport::OrderedOptions.new
+ assert a.respond_to?(:blah)
+ assert a.respond_to?(:blah=)
+ assert_equal 42, a.method(:blah=).call(42)
+ assert_equal 42, a.method(:blah).call
+ end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index d14d01dc30..b9434489bb 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -203,6 +203,24 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Time.utc(1999,12,31,19), twz.time
end
+ def test_parse_should_not_black_out_system_timezone_dst_jump
+ zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
+ zone.stubs(:now).returns(zone.now)
+ Time.stubs(:parse).with('2012-03-25 03:29', zone.now).
+ returns(Time.local(0,29,4,25,3,2012,nil,nil,true,"+03:00"))
+ twz = zone.parse('2012-03-25 03:29')
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6]
+ end
+
+ def test_parse_should_black_out_app_timezone_dst_jump
+ zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
+ zone.stubs(:now).returns(zone.now)
+ Time.stubs(:parse).with('2012-03-11 02:29', zone.now).
+ returns(Time.local(0,29,2,11,3,2012,nil,nil,false,"+02:00"))
+ twz = zone.parse('2012-03-11 02:29')
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6]
+ end
+
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
tzinfo = TZInfo::Timezone.get('America/New_York')
zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo)
diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb
index 1d96c20bb6..938bb4ee99 100644
--- a/activesupport/test/ts_isolated.rb
+++ b/activesupport/test/ts_isolated.rb
@@ -1,5 +1,3 @@
-$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
-
require 'minitest/autorun'
require 'active_support/test_case'
require 'rbconfig'
diff --git a/guides/assets/images/favicon.ico b/guides/assets/images/favicon.ico
new file mode 100644
index 0000000000..e0e80cf8f1
--- /dev/null
+++ b/guides/assets/images/favicon.ico
Binary files differ
diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png
new file mode 100644
index 0000000000..a26c09ef2d
--- /dev/null
+++ b/guides/assets/images/getting_started/confirm_dialog.png
Binary files differ
diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png
new file mode 100644
index 0000000000..badefe6ea6
--- /dev/null
+++ b/guides/assets/images/getting_started/form_with_errors.png
Binary files differ
diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png
new file mode 100644
index 0000000000..6e58a13756
--- /dev/null
+++ b/guides/assets/images/getting_started/index_action_with_edit_link.png
Binary files differ
diff --git a/guides/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png
new file mode 100644
index 0000000000..bd9b2e10f5
--- /dev/null
+++ b/guides/assets/images/getting_started/post_with_comments.png
Binary files differ
diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png
new file mode 100644
index 0000000000..5c8c4d8e5e
--- /dev/null
+++ b/guides/assets/images/getting_started/show_action_for_posts.png
Binary files differ
diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png
new file mode 100644
index 0000000000..f568bf315c
--- /dev/null
+++ b/guides/assets/images/getting_started/undefined_method_post_path.png
Binary files differ
diff --git a/guides/assets/images/posts_index.png b/guides/assets/images/posts_index.png
deleted file mode 100644
index f6cd2f9b80..0000000000
--- a/guides/assets/images/posts_index.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 90723cc8e1..42b85fefa3 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -80,7 +80,7 @@ body {
font-family: Helvetica, Arial, sans-serif;
font-size: 87.5%;
line-height: 1.5em;
- background: #222;
+ background: #fff;
min-width: 69em;
color: #999;
}
@@ -94,6 +94,7 @@ body {
#topNav {
padding: 1em 0;
color: #565656;
+ background: #222;
}
#header {
@@ -111,7 +112,6 @@ body {
}
#container {
- background: #FFF;
color: #333;
padding: 0.5em 0 1.5em 0;
}
@@ -137,7 +137,7 @@ body {
#footer {
padding: 2em 0;
- background: url(../images/footer_tile.gif) repeat-x;
+ background: #222 url(../images/footer_tile.gif) repeat-x;
}
#footer .wrapper {
padding-left: 2em;
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index 768985070c..670a8523b0 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -1,6 +1,6 @@
source 'https://rubygems.org'
-gem 'rails', '3.2.0'
+gem 'rails', '3.2.3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
@@ -15,7 +15,7 @@ group :assets do
gem 'coffee-rails', '~> 3.2.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
- # gem 'therubyracer'
+ # gem 'therubyracer', :platform => :ruby
gem 'uglifier', '>= 1.0.3'
end
@@ -28,11 +28,11 @@ gem 'jquery-rails'
# To use Jbuilder templates for JSON
# gem 'jbuilder'
-# Use unicorn as the web server
+# Use unicorn as the app server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
# To use debugger
-# gem 'ruby-debug19', :require => 'ruby-debug'
+# gem 'debugger'
diff --git a/guides/code/getting_started/README.rdoc b/guides/code/getting_started/README.rdoc
index d2014bd35f..b5d7b6436b 100644
--- a/guides/code/getting_started/README.rdoc
+++ b/guides/code/getting_started/README.rdoc
@@ -86,8 +86,8 @@ programming in general.
Debugger support is available through the debugger command when you start your
Mongrel or WEBrick server with --debugger. This means that you can break out of
execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug19 to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug19</tt>. Example:
+resume execution! You need to install the 'debugger' gem to run the server in debugging
+mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example:
class WeblogController < ActionController::Base
def index
diff --git a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee b/guides/code/getting_started/app/assets/javascripts/comments.js.coffee
deleted file mode 100644
index 761567942f..0000000000
--- a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# 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/guides/code/getting_started/app/assets/javascripts/home.js.coffee b/guides/code/getting_started/app/assets/javascripts/home.js.coffee
deleted file mode 100644
index 761567942f..0000000000
--- a/guides/code/getting_started/app/assets/javascripts/home.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# 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/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
deleted file mode 100644
index 761567942f..0000000000
--- a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# 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/guides/code/getting_started/app/assets/stylesheets/comments.css.scss b/guides/code/getting_started/app/assets/stylesheets/comments.css.scss
deleted file mode 100644
index e730912783..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the Comments controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/guides/code/getting_started/app/assets/stylesheets/home.css.scss b/guides/code/getting_started/app/assets/stylesheets/home.css.scss
deleted file mode 100644
index f0ddc6846a..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/home.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the home controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
deleted file mode 100644
index ed4dfd10f2..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the Posts controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss b/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss
deleted file mode 100644
index 05188f08ed..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss
+++ /dev/null
@@ -1,56 +0,0 @@
-body {
- background-color: #fff;
- color: #333;
- font-family: verdana, arial, helvetica, sans-serif;
- font-size: 13px;
- line-height: 18px; }
-
-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, &.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; } }
diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb
index 7447fd078b..cf3d1be42e 100644
--- a/guides/code/getting_started/app/controllers/comments_controller.rb
+++ b/guides/code/getting_started/app/controllers/comments_controller.rb
@@ -1,16 +1,17 @@
class CommentsController < ApplicationController
http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy
+
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
redirect_to post_path(@post)
end
-
+
def destroy
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_path(@post)
end
-
+
end
diff --git a/guides/code/getting_started/app/controllers/home_controller.rb b/guides/code/getting_started/app/controllers/home_controller.rb
index 6cc31c1ca3..309b70441e 100644
--- a/guides/code/getting_started/app/controllers/home_controller.rb
+++ b/guides/code/getting_started/app/controllers/home_controller.rb
@@ -1,4 +1,4 @@
-class HomeController < ApplicationController
+class WelcomeController < ApplicationController
def index
end
diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb
index 1581d4eb16..a8ac9aba5a 100644
--- a/guides/code/getting_started/app/controllers/posts_controller.rb
+++ b/guides/code/getting_started/app/controllers/posts_controller.rb
@@ -1,84 +1,47 @@
class PostsController < ApplicationController
- http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
- # GET /posts
- # GET /posts.json
+
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
+
def index
@posts = Post.all
-
- respond_to do |format|
- format.html # index.html.erb
- format.json { render json: @posts }
- end
end
- # GET /posts/1
- # GET /posts/1.json
def show
@post = Post.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.json { render json: @post }
- end
end
- # GET /posts/new
- # GET /posts/new.json
def new
@post = Post.new
-
- respond_to do |format|
- format.html # new.html.erb
- format.json { render json: @post }
- end
end
- # GET /posts/1/edit
- def edit
- @post = Post.find(params[:id])
- end
-
- # POST /posts
- # POST /posts.json
def create
@post = Post.new(params[:post])
- respond_to do |format|
- if @post.save
- format.html { redirect_to @post, notice: 'Post was successfully created.' }
- format.json { render json: @post, status: :created, location: @post }
- else
- format.html { render action: "new" }
- format.json { render json: @post.errors, status: :unprocessable_entity }
- end
+ if @post.save
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'new'
end
end
- # PUT /posts/1
- # PUT /posts/1.json
+ def edit
+ @post = Post.find(params[:id])
+ end
+
def update
@post = Post.find(params[:id])
- respond_to do |format|
- if @post.update_attributes(params[:post])
- format.html { redirect_to @post, notice: 'Post was successfully updated.' }
- format.json { head :no_content }
- else
- format.html { render action: "edit" }
- format.json { render json: @post.errors, status: :unprocessable_entity }
- end
+ if @post.update_attributes(params[:post])
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'edit'
end
end
- # DELETE /posts/1
- # DELETE /posts/1.json
def destroy
@post = Post.find(params[:id])
@post.destroy
- respond_to do |format|
- format.html { redirect_to posts_url }
- format.json { head :no_content }
- end
+ redirect_to :action => :index
end
end
diff --git a/guides/code/getting_started/app/helpers/home_helper.rb b/guides/code/getting_started/app/helpers/home_helper.rb
deleted file mode 100644
index 23de56ac60..0000000000
--- a/guides/code/getting_started/app/helpers/home_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module HomeHelper
-end
diff --git a/guides/code/getting_started/app/helpers/posts_helper.rb b/guides/code/getting_started/app/helpers/posts_helper.rb
index b6e8e67894..a7b8cec898 100644
--- a/guides/code/getting_started/app/helpers/posts_helper.rb
+++ b/guides/code/getting_started/app/helpers/posts_helper.rb
@@ -1,5 +1,2 @@
module PostsHelper
- def join_tags(post)
- post.tags.map { |t| t.name }.join(", ")
- end
end
diff --git a/guides/code/getting_started/app/helpers/welcome_helper.rb b/guides/code/getting_started/app/helpers/welcome_helper.rb
new file mode 100644
index 0000000000..eeead45fc9
--- /dev/null
+++ b/guides/code/getting_started/app/helpers/welcome_helper.rb
@@ -0,0 +1,2 @@
+module WelcomeHelper
+end
diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/post.rb
index 61c2b5ae44..21387340b0 100644
--- a/guides/code/getting_started/app/models/post.rb
+++ b/guides/code/getting_started/app/models/post.rb
@@ -1,11 +1,6 @@
class Post < ActiveRecord::Base
- validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
-
+
has_many :comments, :dependent => :destroy
- has_many :tags
-
- accepts_nested_attributes_for :tags, :allow_destroy => :true,
- :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
end
diff --git a/guides/code/getting_started/app/models/tag.rb b/guides/code/getting_started/app/models/tag.rb
deleted file mode 100644
index 30992e8ba9..0000000000
--- a/guides/code/getting_started/app/models/tag.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Tag < ActiveRecord::Base
- belongs_to :post
-end
diff --git a/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb
index 4c3fbf26cd..0cebe0bd96 100644
--- a/guides/code/getting_started/app/views/comments/_comment.html.erb
+++ b/guides/code/getting_started/app/views/comments/_comment.html.erb
@@ -1,13 +1,13 @@
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
-
+
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
-
+
<p>
<%= link_to 'Destroy Comment', [comment.post, comment],
:confirm => 'Are you sure?',
diff --git a/guides/code/getting_started/app/views/comments/_form.html.erb b/guides/code/getting_started/app/views/comments/_form.html.erb
index d15bdd6b59..00cb3a08f0 100644
--- a/guides/code/getting_started/app/views/comments/_form.html.erb
+++ b/guides/code/getting_started/app/views/comments/_form.html.erb
@@ -1,13 +1,13 @@
<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
+ <p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
- </div>
- <div class="field">
+ </p>
+ <p>
<%= f.label :body %><br />
<%= f.text_area :body %>
- </div>
- <div class="actions">
+ </p>
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
diff --git a/guides/code/getting_started/app/views/home/index.html.erb b/guides/code/getting_started/app/views/home/index.html.erb
deleted file mode 100644
index bb4f3dcd1f..0000000000
--- a/guides/code/getting_started/app/views/home/index.html.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Hello, Rails!</h1>
-<%= link_to "My Blog", posts_path %>
diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb
index e27da7f413..f22139938c 100644
--- a/guides/code/getting_started/app/views/posts/_form.html.erb
+++ b/guides/code/getting_started/app/views/posts/_form.html.erb
@@ -1,32 +1,25 @@
-<% @post.tags.build %>
-<%= form_for(@post) do |post_form| %>
+<%= form_for @post do |f| %>
<% if @post.errors.any? %>
- <div id="errorExplanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+ <div id="errorExplanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
-
- <div class="field">
- <%= post_form.label :name %><br />
- <%= post_form.text_field :name %>
- </div>
- <div class="field">
- <%= post_form.label :title %><br />
- <%= post_form.text_field :title %>
- </div>
- <div class="field">
- <%= post_form.label :content %><br />
- <%= post_form.text_area :content %>
- </div>
- <h2>Tags</h2>
- <%= render :partial => 'tags/form',
- :locals => {:form => post_form} %>
- <div class="actions">
- <%= post_form.submit %>
- </div>
+ <p>
+ <%= f.label :title %><br />
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br />
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
+ <%= f.submit %>
+ </p>
<% end %>
diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/posts/edit.html.erb
index 720580236b..911a48569d 100644
--- a/guides/code/getting_started/app/views/posts/edit.html.erb
+++ b/guides/code/getting_started/app/views/posts/edit.html.erb
@@ -2,5 +2,4 @@
<%= render 'form' %>
-<%= link_to 'Show', @post %> |
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', :action => :index %>
diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb
index 45dee1b25f..7b72720d50 100644
--- a/guides/code/getting_started/app/views/posts/index.html.erb
+++ b/guides/code/getting_started/app/views/posts/index.html.erb
@@ -1,10 +1,11 @@
<h1>Listing posts</h1>
+<%= link_to 'New post', :action => :new %>
+
<table>
<tr>
- <th>Name</th>
<th>Title</th>
- <th>Content</th>
+ <th>Text</th>
<th></th>
<th></th>
<th></th>
@@ -12,16 +13,11 @@
<% @posts.each do |post| %>
<tr>
- <td><%= post.name %></td>
<td><%= post.title %></td>
- <td><%= post.content %></td>
- <td><%= link_to 'Show', post %></td>
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
- <td><%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete %></td>
+ <td><%= post.text %></td>
+ <td><%= link_to 'Show', :action => :show, :id => post.id %>
+ <td><%= link_to 'Edit', :action => :edit, :id => post.id %>
+ <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :confirm => 'Are you sure?' %>
</tr>
<% end %>
</table>
-
-<br />
-
-<%= link_to 'New Post', new_post_path %>
diff --git a/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/posts/new.html.erb
index 36ad7421f9..ce9523a721 100644
--- a/guides/code/getting_started/app/views/posts/new.html.erb
+++ b/guides/code/getting_started/app/views/posts/new.html.erb
@@ -2,4 +2,4 @@
<%= render 'form' %>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', :action => :index %>
diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb
index da78a9527b..65809033ed 100644
--- a/guides/code/getting_started/app/views/posts/show.html.erb
+++ b/guides/code/getting_started/app/views/posts/show.html.erb
@@ -1,31 +1,18 @@
-<p class="notice"><%= notice %></p>
-
<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
+ <strong>Title:</strong>
<%= @post.title %>
</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
+
<p>
- <b>Tags:</b>
- <%= join_tags(@post) %>
+ <strong>Text:</strong>
+ <%= @post.text %>
</p>
-
+
<h2>Comments</h2>
<%= render @post.comments %>
-
+
<h2>Add a comment:</h2>
<%= render "comments/form" %>
-
-
+
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
diff --git a/guides/code/getting_started/app/views/tags/_form.html.erb b/guides/code/getting_started/app/views/tags/_form.html.erb
deleted file mode 100644
index 7e424b0e20..0000000000
--- a/guides/code/getting_started/app/views/tags/_form.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-<%= form.fields_for :tags do |tag_form| %>
- <div class="field">
- <%= tag_form.label :name, 'Tag:' %>
- <%= tag_form.text_field :name %>
- </div>
- <% unless tag_form.object.nil? || tag_form.object.new_record? %>
- <div class="field">
- <%= tag_form.label :_destroy, 'Remove:' %>
- <%= tag_form.check_box :_destroy %>
- </div>
- <% end %>
-<% end %>
diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb
new file mode 100644
index 0000000000..e04680ea7e
--- /dev/null
+++ b/guides/code/getting_started/app/views/welcome/index.html.erb
@@ -0,0 +1,2 @@
+<h1>Hello, Rails!</h1>
+<%= link_to "My Blog", :controller => "posts" %>
diff --git a/guides/code/getting_started/config/boot.rb b/guides/code/getting_started/config/boot.rb
index 4489e58688..3596736667 100644
--- a/guides/code/getting_started/config/boot.rb
+++ b/guides/code/getting_started/config/boot.rb
@@ -1,5 +1,3 @@
-require 'rubygems'
-
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb
index cfb8c960d6..ecc35b030b 100644
--- a/guides/code/getting_started/config/environments/production.rb
+++ b/guides/code/getting_started/config/environments/production.rb
@@ -20,7 +20,7 @@ Blog::Application.configure do
# Generate digests for assets URLs.
config.assets.digest = true
- # Defaults to Rails.root.join("public/assets").
+ # Defaults to nil
# config.assets.manifest = YOUR_PATH
# Specifies the header that your server uses for sending files.
diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb
index b048ac68f1..04a6bd374e 100644
--- a/guides/code/getting_started/config/routes.rb
+++ b/guides/code/getting_started/config/routes.rb
@@ -1,10 +1,9 @@
Blog::Application.routes.draw do
+
resources :posts do
resources :comments
end
- get "home/index"
-
# The priority is based upon order of creation:
# first created -> highest priority.
@@ -54,8 +53,8 @@ Blog::Application.routes.draw do
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
- root :to => "home#index"
-
+ root :to => "welcome#index"
+
# See how all your routes lay out with "rake routes"
# This is a legacy wild controller route that's not recommended for RESTful applications.
diff --git a/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb b/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb
deleted file mode 100644
index cf95b1c3d0..0000000000
--- a/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateTags < ActiveRecord::Migration
- def change
- create_table :tags do |t|
- t.string :name
- t.references :post
-
- t.timestamps
- end
- add_index :tags, :post_id
- end
-end
diff --git a/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb b/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb
index d45a961523..602bef31ab 100644
--- a/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb
+++ b/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb
@@ -1,9 +1,8 @@
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
- t.string :name
t.string :title
- t.text :content
+ t.text :text
t.timestamps
end
diff --git a/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb
index 9db4fbe4b6..cfb56ca9b9 100644
--- a/guides/code/getting_started/db/schema.rb
+++ b/guides/code/getting_started/db/schema.rb
@@ -11,31 +11,30 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20110901013701) do
+ActiveRecord::Schema.define(:version => 20120420083127) do
create_table "comments", :force => true do |t|
t.string "commenter"
t.text "body"
t.integer "post_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "comments", ["post_id"], :name => "index_comments_on_post_id"
create_table "posts", :force => true do |t|
- t.string "name"
t.string "title"
- t.text "content"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.text "text"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "tags", :force => true do |t|
t.string "name"
t.integer "post_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "tags", ["post_id"], :name => "index_tags_on_post_id"
diff --git a/guides/code/getting_started/public/robots.txt b/guides/code/getting_started/public/robots.txt
index 085187fa58..1a3a5e4dd2 100644
--- a/guides/code/getting_started/public/robots.txt
+++ b/guides/code/getting_started/public/robots.txt
@@ -1,5 +1,5 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
-# User-Agent: *
+# User-agent: *
# Disallow: /
diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml
index 8b0f75a33d..e1edfd385e 100644
--- a/guides/code/getting_started/test/fixtures/posts.yml
+++ b/guides/code/getting_started/test/fixtures/posts.yml
@@ -1,11 +1,9 @@
# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
one:
- name: MyString
title: MyString
- content: MyText
+ text: MyText
two:
- name: MyString
title: MyString
- content: MyText
+ text: MyText
diff --git a/guides/code/getting_started/test/fixtures/tags.yml b/guides/code/getting_started/test/fixtures/tags.yml
deleted file mode 100644
index 8485668908..0000000000
--- a/guides/code/getting_started/test/fixtures/tags.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
-
-one:
- name: MyString
- post:
-
-two:
- name: MyString
- post:
diff --git a/guides/code/getting_started/test/functional/home_controller_test.rb b/guides/code/getting_started/test/functional/home_controller_test.rb
index 0d9bb47c3e..dff8e9d2c5 100644
--- a/guides/code/getting_started/test/functional/home_controller_test.rb
+++ b/guides/code/getting_started/test/functional/home_controller_test.rb
@@ -1,6 +1,6 @@
require 'test_helper'
-class HomeControllerTest < ActionController::TestCase
+class WelcomeControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
diff --git a/guides/code/getting_started/test/performance/browsing_test.rb b/guides/code/getting_started/test/performance/browsing_test.rb
index 3fea27b916..2a849b7f2b 100644
--- a/guides/code/getting_started/test/performance/browsing_test.rb
+++ b/guides/code/getting_started/test/performance/browsing_test.rb
@@ -3,7 +3,7 @@ require 'rails/performance_test_help'
class BrowsingTest < ActionDispatch::PerformanceTest
# Refer to the documentation for all available options
- # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory],
# :output => 'tmp/performance', :formats => [:flat] }
def test_homepage
diff --git a/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep b/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep
+++ /dev/null
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index e662ad2ed9..1955309865 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -8,9 +8,6 @@ def bundler?
File.exists?('Gemfile')
end
-# Loading Action Pack requires rack and erubis.
-require 'rubygems'
-
begin
# Guides generation in the Rails repo.
as_lib = File.join(pwd, "../activesupport/lib")
diff --git a/guides/rails_guides/textile_extensions.rb b/guides/rails_guides/textile_extensions.rb
index 4677fae504..0a002a785f 100644
--- a/guides/rails_guides/textile_extensions.rb
+++ b/guides/rails_guides/textile_extensions.rb
@@ -1,5 +1,11 @@
require 'active_support/core_ext/object/inclusion'
+module RedCloth::Formatters::HTML
+ def emdash(opts)
+ "--"
+ end
+end
+
module RailsGuides
module TextileExtensions
def notestuff(body)
diff --git a/guides/source/3_2_release_notes.textile b/guides/source/3_2_release_notes.textile
index 0f8fea2bf6..3524ea6595 100644
--- a/guides/source/3_2_release_notes.textile
+++ b/guides/source/3_2_release_notes.textile
@@ -299,7 +299,7 @@ end
h5(#actionview_deprecations). Deprecations
-* Passing formats or handlers to render :template and friends like <tt>render :template => "foo.html.erb"</tt> is deprecated. Instead, you can provide :handlers and :formats directly as an options: <tt> render :template => "foo", :formats => [:html, :js], :handlers => :erb</tt>.
+* Passing formats or handlers to render :template and friends like <tt>render :template => "foo.html.erb"</tt> is deprecated. Instead, you can provide :handlers and :formats directly as options: <tt> render :template => "foo", :formats => [:html, :js], :handlers => :erb</tt>.
h4. Sprockets
diff --git a/guides/source/action_controller_overview.textile b/guides/source/action_controller_overview.textile
index 52d134ace5..cc3350819b 100644
--- a/guides/source/action_controller_overview.textile
+++ b/guides/source/action_controller_overview.textile
@@ -148,18 +148,19 @@ In this case, when a user opens the URL +/clients/active+, +params[:status]+ wil
h4. +default_url_options+
-You can set global default parameters that will be used when generating URLs with +default_url_options+. To do this, define a method with that name in your controller:
+You can set global default parameters for URL generation by defining a method called +default_url_options+ in your controller. Such a method must return a hash with the desired defaults, whose keys must be symbols:
<ruby>
class ApplicationController < ActionController::Base
- # The options parameter is the hash passed in to 'url_for'
- def default_url_options(options)
+ def default_url_options
{:locale => I18n.locale}
end
end
</ruby>
-These options will be used as a starting-point when generating URLs, so it's possible they'll be overridden by +url_for+. Because this method is defined in the controller, you can define it on +ApplicationController+ so it would be used for all URL generation, or you could define it on only one controller for all URLs generated there.
+These options will be used as a starting point when generating URLs, so it's possible they'll be overridden by the options passed in +url_for+ calls.
+
+If you define +default_url_options+ in +ApplicationController+, as in the example above, it would be used for all URL generation. The method can also be defined in one specific controller, in which case it only affects URLs generated there.
h3. Session
diff --git a/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile
index c277f764e7..ebe774fbef 100644
--- a/guides/source/action_mailer_basics.textile
+++ b/guides/source/action_mailer_basics.textile
@@ -4,7 +4,7 @@ This guide should provide you with all you need to get started in sending and re
endprologue.
-WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
h3. Introduction
diff --git a/guides/source/action_view_overview.textile b/guides/source/action_view_overview.textile
index 42120e9bad..bde30ba21c 100644
--- a/guides/source/action_view_overview.textile
+++ b/guides/source/action_view_overview.textile
@@ -59,7 +59,6 @@ Now we'll create a simple "Hello World" application that uses the +titleize+ met
*hello_world.rb:*
<ruby>
-require 'rubygems'
require 'active_support/core_ext/string/inflections'
require 'rack'
@@ -94,7 +93,6 @@ Now we'll create the same "Hello World" application in Sinatra.
*hello_world.rb:*
<ruby>
-require 'rubygems'
require 'action_view'
require 'sinatra'
@@ -550,9 +548,9 @@ Register one or more JavaScript files to be included when symbol is passed to ja
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
javascript_include_tag :monkey # =>
- <script type="text/javascript" src="/javascripts/head.js"></script>
- <script type="text/javascript" src="/javascripts/body.js"></script>
- <script type="text/javascript" src="/javascripts/tail.js"></script>
+ <script src="/javascripts/head.js"></script>
+ <script src="/javascripts/body.js"></script>
+ <script src="/javascripts/tail.js"></script>
</ruby>
h5. register_stylesheet_expansion
@@ -563,9 +561,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" />
+ <link href="/stylesheets/body.css" media="screen" rel="stylesheet" />
+ <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" />
</ruby>
h5. auto_discovery_link_tag
@@ -607,7 +605,7 @@ Returns an html script tag for each of the sources provided. You can pass in the
<ruby>
javascript_include_tag "common" # =>
- <script type="text/javascript" src="/javascripts/common.js"></script>
+ <script src="/javascripts/common.js"></script>
</ruby>
If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well.
@@ -626,7 +624,7 @@ You can also cache multiple JavaScript files into one file, which requires less
<ruby>
javascript_include_tag :all, :cache => true # =>
- <script type="text/javascript" src="/javascripts/all.js"></script>
+ <script src="/javascripts/all.js"></script>
</ruby>
h5. javascript_path
@@ -651,7 +649,7 @@ Returns a stylesheet link tag for the sources specified as arguments. If you don
<ruby>
stylesheet_link_tag "application" # =>
- <link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/application.css" media="screen" rel="stylesheet" />
</ruby>
You can also include all styles in the stylesheet directory using :all as the source:
@@ -664,7 +662,7 @@ You can also cache multiple stylesheets into one file, which requires less HTTP
<ruby>
stylesheet_link_tag :all, :cache => true
- <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/all.css" media="screen" rel="stylesheet" />
</ruby>
h5. stylesheet_path
@@ -805,7 +803,7 @@ For example, let's say we have a standard application layout, but also a special
<p>This is a special page.</p>
<% content_for :special_script do %>
- <script type="text/javascript">alert('Hello!')</script>
+ <script>alert('Hello!')</script>
<% end %>
</ruby>
@@ -833,7 +831,7 @@ Reports the approximate distance in time between two Time or Date objects or int
<ruby>
distance_of_time_in_words(Time.now, Time.now + 15.seconds) # => less than a minute
-distance_of_time_in_words(Time.now, Time.now + 15.seconds, true) # => less than 20 seconds
+distance_of_time_in_words(Time.now, Time.now + 15.seconds, :include_seconds => true) # => less than 20 seconds
</ruby>
h5. select_date
@@ -1501,7 +1499,7 @@ javascript_tag "alert('All is good')"
</ruby>
<html>
-<script type="text/javascript">
+<script>
//<![CDATA[
alert('All is good')
//]]>
diff --git a/guides/source/active_model_basics.textile b/guides/source/active_model_basics.textile
index 98b3533000..d373f4ac85 100644
--- a/guides/source/active_model_basics.textile
+++ b/guides/source/active_model_basics.textile
@@ -20,7 +20,7 @@ class Person
attribute_method_prefix 'reset_'
attribute_method_suffix '_highest?'
- define_attribute_methods ['age']
+ define_attribute_methods 'age'
attr_accessor :age
@@ -95,12 +95,11 @@ h4. Dirty
An object becomes dirty when an object is gone through one or more changes to its attributes and not yet saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Lets consider a Person class with attributes first_name and last_name
<ruby>
-require 'rubygems'
require 'active_model'
class Person
include ActiveModel::Dirty
- define_attribute_methods [:first_name, :last_name]
+ define_attribute_methods :first_name, :last_name
def first_name
@first_name
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index 14d0ba9b28..294ef25b33 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -99,9 +99,28 @@ SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
<tt>Model.find(primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found.
+h5. +take+
+
+<tt>Model.take</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take
+# => #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take</tt> returns +nil+ if no record is found and no exception will be raised.
+
+TIP: The retrieved record may vary depending on the database engine.
+
h5. +first+
-<tt>Model.first</tt> finds the first record matched by the supplied options, if any. For example:
+<tt>Model.first</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first
@@ -111,14 +130,14 @@ client = Client.first
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.first</tt> returns +nil+ if no matching record is found and no exception will be raised.
h5. +last+
-<tt>Model.last</tt> finds the last record matched by the supplied options. For example:
+<tt>Model.last</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last
@@ -131,7 +150,7 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.last</tt> returns +nil+ if no matching record is found and no exception will be raised.
h5. +find_by+
@@ -148,12 +167,29 @@ Client.find_by first_name: 'Jon'
It is equivalent to writing:
<ruby>
-Client.where(first_name: 'Lifo').first
+Client.where(first_name: 'Lifo').take
+</ruby>
+
+h5(#take_1). +take!+
+
+<tt>Model.take!</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take!
+# => #<Client id: 1, first_name: "Lifo">
</ruby>
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
+
h5(#first_1). +first!+
-<tt>Model.first!</tt> finds the first record. For example:
+<tt>Model.first!</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first!
@@ -163,14 +199,14 @@ client = Client.first!
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.first!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#last_1). +last!+
-<tt>Model.last!</tt> finds the last record. For example:
+<tt>Model.last!</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last!
@@ -183,24 +219,24 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.last!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#find_by_1). +find_by!+
-<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +RecordNotFound+ if no matching record is found. For example:
+<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +ActiveRecord::RecordNotFound+ if no matching record is found. For example:
<ruby>
Client.find_by! first_name: 'Lifo'
# => #<Client id: 1, first_name: "Lifo">
Client.find_by! first_name: 'Jon'
-# => RecordNotFound
+# => ActiveRecord::RecordNotFound
</ruby>
It is equivalent to writing:
<ruby>
-Client.where(first_name: 'Lifo').first!
+Client.where(first_name: 'Lifo').take!
</ruby>
h4. Retrieving Multiple Objects
@@ -356,20 +392,6 @@ Client.where("created_at >= :start_date AND created_at <= :end_date",
This makes for clearer readability if you have a large number of variable conditions.
-h5(#array-range_conditions). Range Conditions
-
-If you're looking for a range inside of a table (for example, users created in a certain timeframe) you can use the conditions option coupled with the +IN+ SQL statement for this. If you had two dates coming in from a controller you could do something like this to look for a range:
-
-<ruby>
-Client.where(:created_at => (params[:start_date].to_date)..(params[:end_date].to_date))
-</ruby>
-
-This query will generate something similar to the following SQL:
-
-<sql>
- SELECT "clients".* FROM "clients" WHERE ("clients"."created_at" BETWEEN '2010-09-29' AND '2010-11-30')
-</sql>
-
h4. Hash Conditions
Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
@@ -388,9 +410,9 @@ The field name can also be a string:
Client.where('locked' => true)
</ruby>
-h5(#hash-range_conditions). Range Conditions
+NOTE: The values cannot be symbols. For example, you cannot do +Client.where(:status => :active)+.
-The good thing about this is that we can pass in a range for our fields without it generating a large query as shown in the preamble of this section.
+h5(#hash-range_conditions). Range Conditions
<ruby>
Client.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight)
@@ -539,7 +561,9 @@ And this will give you a single +Order+ object for each date where there are ord
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at)
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
+GROUP BY date(created_at)
</sql>
h3. Having
@@ -555,7 +579,10 @@ Order.select("date(created_at) as ordered_date, sum(price) as total_price").grou
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) HAVING sum(price) > 100
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
+GROUP BY date(created_at)
+HAVING sum(price) > 100
</sql>
This will return single order objects for each day, but only those that are ordered more than $100 in a day.
@@ -695,7 +722,7 @@ Optimistic locking allows multiple users to access the same record for edits, an
<strong>Optimistic locking column</strong>
-In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column. If an update request is made with a lower value in the +lock_version+ field than is currently in the +lock_version+ column in the database, the update request will fail with an +ActiveRecord::StaleObjectError+. Example:
+In order to use optimistic locking, the table needs to have a column called +lock_version+ of type integer. Each time the record is updated, Active Record increments the +lock_version+ column. If an update request is made with a lower value in the +lock_version+ field than is currently in the +lock_version+ column in the database, the update request will fail with an +ActiveRecord::StaleObjectError+. Example:
<ruby>
c1 = Client.find(1)
@@ -829,7 +856,7 @@ SELECT categories.* FROM categories
INNER JOIN posts ON posts.category_id = categories.id
</sql>
-Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:post).select("distinct(categories.id)").
+Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:posts).select("distinct(categories.id)").
h5. Joining Multiple Associations
@@ -919,7 +946,7 @@ This code looks fine at the first sight. But the problem lies within the total n
Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
-Revisiting the above case, we could rewrite +Client.all+ to use eager load addresses:
+Revisiting the above case, we could rewrite +Client.limit(10)+ to use eager load addresses:
<ruby>
clients = Client.includes(:address).limit(10)
@@ -1004,7 +1031,7 @@ Scopes are also chainable within scopes:
<ruby>
class Post < ActiveRecord::Base
scope :published, -> { where(:published => true) }
- scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
+ scope :published_and_commented, -> { published.where("comments_count > 0") }
end
</ruby>
@@ -1233,6 +1260,24 @@ with
Client.pluck(:id)
</ruby>
+h3. +ids+
+
++ids+ can be used to pluck all the IDs for the relation using the table's primary key.
+
+<ruby>
+Person.ids
+# SELECT id FROM people
+</ruby>
+
+<ruby>
+class Person < ActiveRecord::Base
+ self.primary_key = "person_id"
+end
+
+Person.ids
+# SELECT person_id FROM people
+</ruby>
+
h3. Existence of Objects
If you simply want to check for the existence of the object there's a method called +exists?+. This method will query the database using the same query as +find+, but instead of returning an object or collection of objects it will return either +true+ or +false+.
diff --git a/guides/source/active_record_validations_callbacks.textile b/guides/source/active_record_validations_callbacks.textile
index 88c4481e5e..f49d91fd3c 100644
--- a/guides/source/active_record_validations_callbacks.textile
+++ b/guides/source/active_record_validations_callbacks.textile
@@ -1064,6 +1064,7 @@ Additionally, the +after_find+ callback is triggered by the following finder met
* +find_all_by_<em>attribute</em>+
* +find_by_<em>attribute</em>+
* +find_by_<em>attribute</em>!+
+* +find_by_sql+
* +last+
The +after_initialize+ callback is triggered every time a new object of the class is initialized.
@@ -1076,7 +1077,6 @@ Just as with validations, it is also possible to skip callbacks. These methods s
* +decrement_counter+
* +delete+
* +delete_all+
-* +find_by_sql+
* +increment+
* +increment_counter+
* +toggle+
diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile
index 5d0a3f82e8..6443255f5d 100644
--- a/guides/source/active_support_core_extensions.textile
+++ b/guides/source/active_support_core_extensions.textile
@@ -154,6 +154,51 @@ WARNING. Any class can disallow duplication removing +dup+ and +clone+ or raisin
NOTE: Defined in +active_support/core_ext/object/duplicable.rb+.
+h4. +deep_dup+
+
+The +deep_dup+ method returns deep copy of given object. Normally, when you +dup+ an object that contains other objects, ruby does not +dup+ them. If you have array with a string, for example, it will look like this:
+
+<ruby>
+array = ['string']
+duplicate = array.dup
+
+duplicate.push 'another-string'
+
+# object was duplicated, element added only to duplicate
+array #=> ['string']
+duplicate #=> ['string', 'another-string']
+
+duplicate.first.gsub!('string', 'foo')
+
+# first element was not duplicated, it will be changed for both arrays
+array #=> ['foo']
+duplicate #=> ['foo', 'another-string']
+</ruby>
+
+As you can see, after duplicating +Array+ instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since +dup+ does not make deep copy, the string inside array is still the same object.
+
+If you need a deep copy of an object, you should use +deep_dup+ in such situation:
+
+<ruby>
+array = ['string']
+duplicate = array.deep_dup
+
+duplicate.first.gsub!('string', 'foo')
+
+array #=> ['string']
+duplicate #=> ['foo']
+</ruby>
+
+If object is not duplicable +deep_dup+ will just return this object:
+
+<ruby>
+number = 1
+dup = number.deep_dup
+number.object_id == dup.object_id # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/object/deep_dup.rb+.
+
h4. +try+
Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first. +try+ is like +Object#send+ except that it returns +nil+ if sent to +nil+.
@@ -184,7 +229,7 @@ You can evaluate code in the context of any object's singleton class using +clas
<ruby>
class Proc
def bind(object)
- block, time = self, Time.now
+ block, time = self, Time.current
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
define_method(method_name, &block)
@@ -1034,49 +1079,6 @@ A model may find it useful to set +:instance_accessor+ to +false+ as a way to pr
NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+.
-h4. Class Inheritable Attributes
-
-WARNING: Class Inheritable Attributes are deprecated. It's recommended that you use +Class#class_attribute+ instead.
-
-Class variables are shared down the inheritance tree. Class instance variables are not shared, but they are not inherited either. The macros +class_inheritable_reader+, +class_inheritable_writer+, and +class_inheritable_accessor+ provide accessors for class-level data which is inherited but not shared with children:
-
-<ruby>
-module ActionController
- class Base
- # FIXME: REVISE/SIMPLIFY THIS COMMENT.
- # The value of allow_forgery_protection is inherited,
- # but its value in a particular class does not affect
- # the value in the rest of the controllers hierarchy.
- class_inheritable_accessor :allow_forgery_protection
- end
-end
-</ruby>
-
-They accomplish this with class instance variables and cloning on subclassing, there are no class variables involved. Cloning is performed with +dup+ as long as the value is duplicable.
-
-There are some variants specialised in arrays and hashes:
-
-<ruby>
-class_inheritable_array
-class_inheritable_hash
-</ruby>
-
-Those writers take any inherited array or hash into account and extend them rather than overwrite them.
-
-As with vanilla class attribute accessors these macros create convenience instance methods for reading and writing. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+):
-
-<ruby>
-module ActiveRecord
- class Base
- class_inheritable_accessor :default_scoping, :instance_writer => false
- end
-end
-</ruby>
-
-Since values are copied when a subclass is defined, if the base class changes the attribute after that, the subclass does not see the new value. That's the point.
-
-NOTE: Defined in +active_support/core_ext/class/inheritable_attributes.rb+.
-
h4. Subclasses & Descendants
h5. +subclasses+
@@ -1131,7 +1133,7 @@ h4. Output Safety
h5. Motivation
-Inserting data into HTML templates needs extra care. For example you can't just interpolate +@review.title+ verbatim into an HTML page. On one hand if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;amp;". On the other hand, depending on the application that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks.
+Inserting data into HTML templates needs extra care. For example, you can't just interpolate +@review.title+ verbatim into an HTML page. For one thing, if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;amp;". What's more, depending on the application, that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks.
h5. Safe Strings
@@ -1255,9 +1257,14 @@ Pass a +:separator+ to truncate the string at a natural break:
# => "Oh dear! Oh..."
</ruby>
-In the above example "dear" gets cut first, but then +:separator+ prevents it.
+The option +:separator+ can be a regexp:
-WARNING: The option +:separator+ can't be a regexp.
+<ruby>
+"Oh dear! Oh dear! I shall be late!".truncate(18, :separator => /\s/)
+# => "Oh dear! Oh..."
+</ruby>
+
+In above examples "dear" gets cut first, but then +:separator+ prevents it.
NOTE: Defined in +active_support/core_ext/string/filters.rb+.
@@ -1270,20 +1277,6 @@ The <tt>inquiry</tt> method converts a string into a +StringInquirer+ object mak
"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:
-
-<ruby>
-"Total is %<total>.02f" % {:total => 43.1} # => Total is 43.10
-"I say %{foo}" % {:foo => "wadus"} # => "I say wadus"
-"I say %{woo}" % {:foo => "wadus"} # => KeyError
-</ruby>
-
-Active Support adds that functionality to <tt>%</tt> in previous versions of Ruby.
-
-NOTE: Defined in +active_support/core_ext/string/interpolation.rb+.
-
h4. +starts_with?+ and +ends_with?+
Active Support defines 3rd person aliases of +String#start_with?+ and +String#end_with?+:
@@ -1330,7 +1323,7 @@ Returns the character of the string at position +position+:
"hello".at(0) # => "h"
"hello".at(4) # => "o"
"hello".at(-1) # => "o"
-"hello".at(10) # => ERROR if < 1.9, nil in 1.9
+"hello".at(10) # => nil
</ruby>
NOTE: Defined in +active_support/core_ext/string/access.rb+.
@@ -1810,6 +1803,43 @@ Singular forms are aliased so you are able to say:
NOTE: Defined in +active_support/core_ext/numeric/bytes.rb+.
+h4. Time
+
+Enables the use of time calculations and declarations, like @45.minutes <plus> 2.hours <plus> 4.years@.
+
+These methods use Time#advance for precise date calculations when using from_now, ago, etc.
+as well as adding or subtracting their results from a Time object. For example:
+
+<ruby>
+# equivalent to Time.current.advance(:months => 1)
+1.month.from_now
+
+# equivalent to Time.current.advance(:years => 2)
+2.years.from_now
+
+# equivalent to Time.current.advance(:months => 4, :years => 5)
+(4.months + 5.years).from_now
+</ruby>
+
+While these methods provide precise calculation when used as in the examples above, care
+should be taken to note that this is not true if the result of `months', `years', etc is
+converted before use:
+
+<ruby>
+# equivalent to 30.days.to_i.from_now
+1.month.to_i.from_now
+
+# equivalent to 365.25.days.to_f.from_now
+1.year.to_f.from_now
+</ruby>
+
+In such cases, Ruby's core
+Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+date and time arithmetic.
+
+NOTE: Defined in +active_support/core_ext/numeric/time.rb+.
+
h3. Extensions to +Integer+
h4. +multiple_of?+
@@ -2103,20 +2133,20 @@ To do so it sends +to_xml+ to every item in turn, and collects the results under
By default, the name of the root element is the underscorized and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with <tt>is_a?</tt>) and they are not hashes. In the example above that's "contributors".
-If there's any element that does not belong to the type of the first one the root node becomes "records":
+If there's any element that does not belong to the type of the first one the root node becomes "objects":
<ruby>
[Contributor.first, Commit.first].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
-# <records type="array">
-# <record>
+# <objects type="array">
+# <object>
# <id type="integer">4583</id>
# <name>Aaron Batalion</name>
# <rank type="integer">53</rank>
# <url-id>aaron-batalion</url-id>
-# </record>
-# <record>
+# </object>
+# <object>
# <author>Joshua Peek</author>
# <authored-timestamp type="datetime">2009-09-02T16:44:36Z</authored-timestamp>
# <branch>origin/master</branch>
@@ -2127,30 +2157,30 @@ If there's any element that does not belong to the type of the first one the roo
# <imported-from-svn type="boolean">false</imported-from-svn>
# <message>Kill AMo observing wrap_with_notifications since ARes was only using it</message>
# <sha1>723a47bfb3708f968821bc969a9a3fc873a3ed58</sha1>
-# </record>
-# </records>
+# </object>
+# </objects>
</ruby>
-If the receiver is an array of hashes the root element is by default also "records":
+If the receiver is an array of hashes the root element is by default also "objects":
<ruby>
[{:a => 1, :b => 2}, {:c => 3}].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
-# <records type="array">
-# <record>
+# <objects type="array">
+# <object>
# <b type="integer">2</b>
# <a type="integer">1</a>
-# </record>
-# <record>
+# </object>
+# <object>
# <c type="integer">3</c>
-# </record>
-# </records>
+# </object>
+# </objects>
</ruby>
WARNING. If the collection is empty the root element is by default "nil-classes". That's a gotcha, for example the root element of the list of contributors above would not be "contributors" if the collection was empty, but "nil-classes". You may use the <tt>:root</tt> option to ensure a consistent root element.
-The name of children nodes is by default the name of the root node singularized. In the examples above we've seen "contributor" and "record". The option <tt>:children</tt> allows you to set these node names.
+The name of children nodes is by default the name of the root node singularized. In the examples above we've seen "contributor" and "object". The option <tt>:children</tt> allows you to set these node names.
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:
@@ -2217,6 +2247,19 @@ Thus, in this case the behavior is different for +nil+, and the differences with
NOTE: Defined in +active_support/core_ext/array/wrap.rb+.
+h4. Duplicating
+
+The method +Array.deep_dup+ duplicates itself and all objects inside recursively with ActiveSupport method +Object#deep_dup+. It works like +Array#map+ with sending +deep_dup+ method to each object inside.
+
+<ruby>
+array = [1, [2, 3]]
+dup = array.deep_dup
+dup[1][2] = 4
+array[1][2] == nil # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/array/deep_dup.rb+.
+
h4. Grouping
h5. +in_groups_of(number, fill_with = nil)+
@@ -2423,6 +2466,23 @@ The method +deep_merge!+ performs a deep merge in place.
NOTE: Defined in +active_support/core_ext/hash/deep_merge.rb+.
+h4. Deep duplicating
+
+The method +Hash.deep_dup+ duplicates itself and all keys and values inside recursively with ActiveSupport method +Object#deep_dup+. It works like +Enumerator#each_with_object+ with sending +deep_dup+ method to each pair inside.
+
+<ruby>
+hash = { :a => 1, :b => { :c => 2, :d => [3, 4] } }
+
+dup = hash.deep_dup
+dup[:b][:e] = 5
+dup[:b][:d] << 5
+
+hash[:b][:e] == nil # => true
+hash[:b][:d] == [3, 4] # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/hash/deep_dup.rb+.
+
h4. Diffing
The method +diff+ returns a hash that represents a diff of the receiver and the argument with the following logic:
@@ -2693,36 +2753,27 @@ As the example depicts, the +:db+ format generates a +BETWEEN+ SQL clause. That
NOTE: Defined in +active_support/core_ext/range/conversions.rb+.
-h4. +step+
-
-Active Support extends the method +Range#step+ so that it can be invoked without a block:
-
-<ruby>
-(1..10).step(2) # => [1, 3, 5, 7, 9]
-</ruby>
-
-As the example shows, in that case the method returns an array with the corresponding elements.
-
-NOTE: Defined in +active_support/core_ext/range/blockless_step.rb+.
-
h4. +include?+
-The method +Range#include?+ says whether some value falls between the ends of a given instance:
+The methods +Range#include?+ and +Range#===+ say whether some value falls between the ends of a given instance:
<ruby>
(2..3).include?(Math::E) # => true
</ruby>
-Active Support extends this method so that the argument may be another range in turn. In that case we test whether the ends of the argument range belong to the receiver themselves:
+Active Support extends these methods so that the argument may be another range in turn. In that case we test whether the ends of the argument range belong to the receiver themselves:
<ruby>
(1..10).include?(3..7) # => true
(1..10).include?(0..7) # => false
(1..10).include?(3..11) # => false
(1...9).include?(3..9) # => false
-</ruby>
-WARNING: The original +Range#include?+ is still the one aliased to +Range#===+.
+(1..10) === (3..7) # => true
+(1..10) === (0..7) # => false
+(1..10) === (3..11) # => false
+(1...9) === (3..9) # => false
+</ruby>
NOTE: Defined in +active_support/core_ext/range/include_range.rb+.
@@ -3052,18 +3103,38 @@ The method +beginning_of_day+ returns a timestamp at the beginning of the day (0
<ruby>
date = Date.new(2010, 6, 7)
-date.beginning_of_day # => Sun Jun 07 00:00:00 +0200 2010
+date.beginning_of_day # => Mon Jun 07 00:00:00 +0200 2010
</ruby>
The method +end_of_day+ returns a timestamp at the end of the day (23:59:59):
<ruby>
date = Date.new(2010, 6, 7)
-date.end_of_day # => Sun Jun 06 23:59:59 +0200 2010
+date.end_of_day # => Mon Jun 07 23:59:59 +0200 2010
</ruby>
+beginning_of_day+ is aliased to +at_beginning_of_day+, +midnight+, +at_midnight+.
+h6. +beginning_of_hour+, +end_of_hour+
+
+The method +beginning_of_hour+ returns a timestamp at the beginning of the hour (hh:00:00):
+
+<ruby>
+date = DateTime.new(2010, 6, 7, 19, 55, 25)
+date.beginning_of_hour # => Mon Jun 07 19:00:00 +0200 2010
+</ruby>
+
+The method +end_of_hour+ returns a timestamp at the end of the hour (hh:59:59):
+
+<ruby>
+date = DateTime.new(2010, 6, 7, 19, 55, 25)
+date.end_of_hour # => Mon Jun 07 19:59:59 +0200 2010
+</ruby>
+
++beginning_of_hour+ is aliased to +at_beginning_of_hour+.
+
+INFO: +beginning_of_hour+ and +end_of_hour+ are implemented for +Time+ and +DateTime+ but *not* +Date+ as it does not make sense to request the beginning or end of an hour on a +Date+ instance.
+
h6. +ago+, +since+
The method +ago+ receives a number of seconds as argument and returns a timestamp those many seconds ago from midnight:
@@ -3131,6 +3202,13 @@ since (in)
On the other hand, +advance+ and +change+ are also defined and support more options, they are documented below.
+The following methods are only implemented in +active_support/core_ext/date_time/calculations.rb+ as they only make sense when used with a +DateTime+ instance:
+
+<ruby>
+beginning_of_hour (at_beginning_of_hour)
+end_of_hour
+</ruby>
+
h5. Named Datetimes
h6. +DateTime.current+
@@ -3273,6 +3351,8 @@ ago
since (in)
beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
+beginning_of_hour (at_beginning_of_hour)
+end_of_hour
beginning_of_week (at_beginning_of_week)
end_of_week (at_end_of_week)
monday
diff --git a/guides/source/ajax_on_rails.textile b/guides/source/ajax_on_rails.textile
index cda9c64460..bfd007490a 100644
--- a/guides/source/ajax_on_rails.textile
+++ b/guides/source/ajax_on_rails.textile
@@ -78,7 +78,7 @@ will produce
<ruby>
button_to('Destroy', 'http://www.example.com', :confirm => 'Are you sure?',
- :method => "delete", :remote => true, :disable_with => 'loading...')
+ :method => "delete", :remote => true, 'data-disable-with' => 'loading...')
</ruby>
will produce
@@ -87,7 +87,7 @@ will produce
<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
<div>
<input name='_method' value='delete' type='hidden' />
- <input value='Destroy' type='submit' disable_with='loading...' data-confirm='Are you sure?' />
+ <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
</div>
</form>
</html>
diff --git a/guides/source/asset_pipeline.textile b/guides/source/asset_pipeline.textile
index a1b7a42d66..105efe229e 100644
--- a/guides/source/asset_pipeline.textile
+++ b/guides/source/asset_pipeline.textile
@@ -204,6 +204,8 @@ Images can also be organized into subdirectories if required, and they can be ac
<%= image_tag "icons/rails.png" %>
</erb>
+WARNING: If you're precompiling your assets (see "In Production":#in-production below), linking to an asset that does not exist will raise an exception in the calling page. This includes linking to a blank string. As such, be careful using <tt>image_tag</tt> and the other helpers with user-supplied data.
+
h5. CSS and ERB
The asset pipeline automatically evaluates ERB. This means that if you add an +erb+ extension to a CSS asset (for example, +application.css.erb+), then helpers like +asset_path+ are available in your CSS rules:
@@ -328,9 +330,9 @@ This manifest +app/assets/javascripts/application.js+:
would generate this HTML:
<html>
-<script src="/assets/core.js?body=1" type="text/javascript"></script>
-<script src="/assets/projects.js?body=1" type="text/javascript"></script>
-<script src="/assets/tickets.js?body=1" type="text/javascript"></script>
+<script src="/assets/core.js?body=1"></script>
+<script src="/assets/projects.js?body=1"></script>
+<script src="/assets/tickets.js?body=1"></script>
</html>
The +body+ param is required by Sprockets.
@@ -346,7 +348,7 @@ config.assets.debug = false
When debug mode is off, Sprockets concatenates and runs the necessary preprocessors on all files. With debug mode turned off the manifest above would generate instead:
<html>
-<script src="/assets/application.js" type="text/javascript"></script>
+<script src="/assets/application.js"></script>
</html>
Assets are compiled and cached on the first request after the server is started. Sprockets sets a +must-revalidate+ Cache-Control HTTP header to reduce request overhead on subsequent requests -- on these the browser gets a 304 (Not Modified) response.
@@ -380,8 +382,8 @@ For example this:
generates something like this:
<html>
-<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js" type="text/javascript"></script>
-<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" rel="stylesheet" type="text/css" />
+<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>
+<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" rel="stylesheet" />
</html>
The fingerprinting behavior is controlled by the setting of +config.assets.digest+ setting in Rails (which defaults to +true+ for production and +false+ for everything else).
@@ -394,7 +396,7 @@ Rails comes bundled with a rake task to compile the asset manifests and other fi
Compiled assets are written to the location specified in +config.assets.prefix+. By default, this is the +public/assets+ directory.
-You can call this task on the server during deployment to create compiled versions of your assets directly on the server. If you do not have write access to your production file system, you can call this task locally and then deploy the compiled assets.
+You can call this task on the server during deployment to create compiled versions of your assets directly on the server. See the next section for information on compiling locally.
The rake task is:
@@ -409,9 +411,9 @@ cannot see application objects or methods. *Heroku requires this to be false.*
WARNING: If you set +config.assets.initialize_on_precompile+ to false, be sure to
test +rake assets:precompile+ locally before deploying. It may expose bugs where
your assets reference application objects or methods, since those are still
-in scope in development mode regardless of the value of this flag. Changing this flag also effects
+in scope in development mode regardless of the value of this flag. Changing this flag also affects
engines. Engines can define assets for precompilation as well. Since the complete environment is not loaded,
-engines (or other gems) will not be loaded which can cause missing assets.
+engines (or other gems) will not be loaded, which can cause missing assets.
Capistrano (v2.8.0 and above) includes a recipe to handle this in deployment. Add the following line to +Capfile+:
@@ -514,6 +516,41 @@ If you're compiling nginx with Phusion Passenger you'll need to pass that option
A robust configuration for Apache is possible but tricky; please Google around. (Or help update this Guide if you have a good example configuration for Apache.)
+h4. Local Precompilation
+
+There are several reasons why you might want to precompile your assets locally. Among them are:
+
+* You may not have write access to your production file system.
+* You may be deploying to more than one server, and want to avoid the duplication of work.
+* You may be doing frequent deploys that do not include asset changes.
+
+Local compilation allows you to commit the compiled files into source control, and deploy as normal.
+
+There are two caveats:
+
+* You must not run the Capistrano deployment task that precompiles assets.
+* You must change the following two application configuration settings.
+
+In <tt>config/environments/development.rb</tt>, place the following line:
+
+<erb>
+config.assets.prefix = "/dev-assets"
+</erb>
+
+You will also need this in application.rb:
+
+<erb>
+config.assets.initialize_on_precompile = false
+</erb>
+
+The +prefix+ change makes Rails use a different URL for serving assets in development mode, and pass all requests to Sprockets. The prefix is still set to +/assets+ in the production environment. Without this change, the application would serve the precompiled assets from +public/assets+ in development, and you would not see any local changes until you compile assets again.
+
+The +initialize_on_precompile+ change tells the precompile task to run without invoking Rails. This is because the precompile task runs in production mode by default, and will attempt to connect to your specified production database. Please note that you cannot have code in pipeline files that relies on Rails resources (such as the database) when compiling locally with this option.
+
+You will also need to ensure that any compressors or minifiers are available on your development system.
+
+In practice, this will allow you to precompile locally, have those files in your working tree, and commit those files to source control when needed. Development mode will work as expected.
+
h4. Live Compilation
In some circumstances you may wish to use live compilation. In this mode all requests for assets in the pipeline are handled by Sprockets directly.
@@ -673,7 +710,7 @@ config.assets.compile = false
# Generate digests for assets URLs.
config.assets.digest = true
-# Defaults to Rails.root.join("public/assets")
+# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
diff --git a/guides/source/caching_with_rails.textile b/guides/source/caching_with_rails.textile
index 0e811a2527..34a100cd3a 100644
--- a/guides/source/caching_with_rails.textile
+++ b/guides/source/caching_with_rails.textile
@@ -92,7 +92,7 @@ INFO: Page caching runs in an after filter. Thus, invalid requests won't generat
h4. Action Caching
-One of the issues with Page Caching is that you cannot use it for pages that require to restrict access somehow. This is where Action Caching comes in. Action Caching works like Page Caching except for the fact that the incoming web request does go from the webserver to the Rails stack and Action Pack so that before filters can be run on it before the cache is served. This allows authentication and other restriction to be run while still serving the result of the output from a cached copy.
+Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy.
Clearing the cache works in a similar way to Page Caching, except you use +expire_action+ instead of +expire_page+.
@@ -157,7 +157,7 @@ and you can expire it using the +expire_fragment+ method, like so:
expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products')
</ruby>
-If you don't want the cache block to bind to the action that called it, You can also use globally keyed fragments by calling the +cache+ method with a key, like so:
+If you don't want the cache block to bind to the action that called it, you can also use globally keyed fragments by calling the +cache+ method with a key:
<ruby>
<% cache('all_available_products') do %>
@@ -229,6 +229,42 @@ class ProductsController < ActionController
end
</ruby>
+Sometimes it is necessary to disambiguate the controller when you call +expire_action+, such as when there are two identically named controllers in separate namespaces:
+
+<ruby>
+class ProductsController < ActionController
+ caches_action :index
+
+ def index
+ @products = Product.all
+ end
+end
+
+module Admin
+ class ProductsController < ActionController
+ cache_sweeper :product_sweeper
+
+ def new
+ @product = Product.new
+ end
+
+ def create
+ @product = Product.create(params[:product])
+ end
+ end
+end
+
+class ProductSweeper < ActionController::Caching::Sweeper
+ observe Product
+
+ def after_create(product)
+ expire_action(:controller => '/products', :action => 'index')
+ end
+end
+</ruby>
+
+Note the use of '/products' here rather than 'products'. If you wanted to expire an action cache for the +Admin::ProductsController+, you would use 'admin/products' instead.
+
h4. SQL Caching
Query caching is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.
diff --git a/guides/source/command_line.textile b/guides/source/command_line.textile
index 858ce47db1..b656a0857a 100644
--- a/guides/source/command_line.textile
+++ b/guides/source/command_line.textile
@@ -12,7 +12,7 @@ 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.
+WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
h3. Command Line Basics
@@ -31,7 +31,7 @@ h4. +rails new+
The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails.
-WARNING: You can install the rails gem by typing +gem install rails+, if you don't have it already. Follow the instructions in the "Rails 3 Release Notes":/3_0_release_notes.html
+TIP: You can install the rails gem by typing +gem install rails+, if you don't have it already.
<shell>
$ rails new commandsapp
@@ -185,8 +185,6 @@ $ rails server
=> Booting WEBrick...
</shell>
-WARNING: Make sure that you do not have any "tilde backup" files in +app/views/(controller)+, or else WEBrick will _not_ show the expected output. This seems to be a *bug* in Rails 2.3.0.
-
The URL will be "http://localhost:3000/greetings/hello":http://localhost:3000/greetings/hello.
INFO: With a normal, plain-old Rails application, your URLs will generally follow the pattern of http://(host)/(controller)/(action), and a URL like http://(host)/(controller) will hit the *index* action of that controller.
@@ -446,6 +444,18 @@ app/model/post.rb:
NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines.
+By default, +rake notes+ will look in the +app+, +config+, +lib+, +script+ and +test+ directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable +SOURCE_ANNOTATION_DIRECTORIES+.
+
+<shell>
+$ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor'
+$ rake notes
+(in /home/foobar/commandsapp)
+app/model/user.rb:
+ * [ 35] [FIXME] User should have a subscription at this point
+rspec/model/user_spec.rb:
+ * [122] [TODO] Verify the user that has a subscription works
+</shell>
+
h4. +routes+
+rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with.
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index 717654d5d8..f114075cae 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -70,12 +70,23 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is
* +config.action_view.cache_template_loading+ controls whether or not templates should be reloaded on each request. Defaults to whatever is set for +config.cache_classes+.
-* +config.cache_store+ configures which cache store to use for Rails caching. Options include one of the symbols +:memory_store+, +:file_store+, +:mem_cache_store+, or an object that implements the cache API. Defaults to +:file_store+ if the directory +tmp/cache+ exists, and to +:memory_store+ otherwise.
+* +config.cache_store+ configures which cache store to use for Rails caching. Options include one of the symbols +:memory_store+, +:file_store+, +:mem_cache_store+, +:null_store+, or an object that implements the cache API. Defaults to +:file_store+ if the directory +tmp/cache+ exists, and to +:memory_store+ otherwise.
* +config.colorize_logging+ specifies whether or not to use ANSI color codes when logging information. Defaults to true.
* +config.consider_all_requests_local+ is a flag. If true then any error will cause detailed debugging information to be dumped in the HTTP response, and the +Rails::Info+ controller will show the application runtime context in +/rails/info/properties+. True by default in development and test environments, and false in production mode. For finer-grained control, set this to false and implement +local_request?+ in controllers to specify which requests should provide debugging information on errors.
+* +config.console+ allows you to set class that will be used as console you run +rails console+. It's best to run it in +console+ block:
+
+<ruby>
+console do
+ # this block is called only when running console,
+ # so we can safely require pry here
+ require "pry"
+ config.console = Pry
+end
+</ruby>
+
* +config.dependency_loading+ is a flag that allows you to disable constant autoloading setting it to false. It only has effect if +config.cache_classes+ is true, which it is by default in production mode. This flag is set to false by +config.threadsafe!+.
* +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the +app+ directory of the application.
@@ -100,6 +111,10 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is
* +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled.
+* +config.queue+ configures a different queue implementation for the application. Defaults to +Rails::Queueing::Queue+. Note that, if the default queue is changed, the default +queue_consumer+ is not going to be initialized, it is up to the new queue implementation to handle starting and shutting down its own consumer(s).
+
+* +config.queue_consumer+ configures a different consumer implementation for the default queue. Defaults to +Rails::Queueing::ThreadedConsumer+.
+
* +config.reload_classes_only_on_change+ enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If +config.cache_classes+ is true, this option is ignored.
* +config.secret_token+ used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get +config.secret_token+ initialized to a random key in +config/initializers/secret_token.rb+.
@@ -122,17 +137,6 @@ WARNING: Threadsafe operation is incompatible with the normal workings of develo
* +config.whiny_nils+ enables or disables warnings when a certain set of methods are invoked on +nil+ and it does not respond to them. Defaults to true in development and test environments.
-* +config.console+ allows you to set class that will be used as console you run +rails console+. It's best to run it in +console+ block:
-
-<ruby>
-console do
- # this block is called only when running console,
- # so we can safely require pry here
- require "pry"
- config.console = Pry
-end
-</ruby>
-
h4. Configuring Assets
Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful.
@@ -182,13 +186,13 @@ The full set of methods that can be used in this block are as follows:
* +force_plural+ allows pluralized model names. Defaults to +false+.
* +helper+ defines whether or not to generate helpers. Defaults to +true+.
* +integration_tool+ defines which integration tool to use. Defaults to +nil+.
-* +javascripts+ turns on the hook for javascripts in generators. Used in Rails for when the +scaffold+ generator is ran. Defaults to +true+.
+* +javascripts+ turns on the hook for javascripts in generators. Used in Rails for when the +scaffold+ generator is run. Defaults to +true+.
* +javascript_engine+ configures the engine to be used (for eg. coffee) when generating assets. Defaults to +nil+.
* +orm+ defines which orm to use. Defaults to +false+ and will use Active Record by default.
* +performance_tool+ defines which performance tool to use. Defaults to +nil+.
* +resource_controller+ defines which generator to use for generating a controller when using +rails generate resource+. Defaults to +:controller+.
* +scaffold_controller+ different from +resource_controller+, defines which generator to use for generating a _scaffolded_ controller when using +rails generate scaffold+. Defaults to +:scaffold_controller+.
-* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is ran, but this hook can be used in other generates as well. Defaults to +true+.
+* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is run, but this hook can be used in other generates as well. Defaults to +true+.
* +stylesheet_engine+ configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to +:css+.
* +test_framework+ defines which test framework to use. Defaults to +false+ and will use Test::Unit by default.
* +template_engine+ defines which template engine to use, such as ERB or Haml. Defaults to +:erb+.
@@ -248,14 +252,6 @@ They can also be removed from the stack completely:
config.middleware.delete ActionDispatch::BestStandardsSupport
</ruby>
-In addition to these methods to handle the stack, if your application is going to be used as an API endpoint only, the middleware stack can be configured like this:
-
-<ruby>
-config.middleware.http_only!
-</ruby>
-
-By doing this, Rails will create a smaller middleware stack, by not adding some middlewares that are usually useful for browser access only, such as Cookies, Session and Flash, BestStandardsSupport, and MethodOverride. You can always add any of them later manually if you want. Refer to the "API App docs":api_app.html for more info on how to setup your application for API only apps.
-
h4. Configuring i18n
* +config.i18n.default_locale+ sets the default locale of an application used for i18n. Defaults to +:en+.
@@ -362,7 +358,7 @@ h4. Configuring Action View
Proc.new { |html_tag, instance| %Q(<div class="field_with_errors">#{html_tag}</div>).html_safe }
</ruby>
-* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+.
+* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. If you want your form builder class to be loaded after initialization (so it's reloaded on each request in development), you can pass it as a +String+
* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to +nil+ to disable logging.
@@ -452,9 +448,9 @@ There are a few configuration options available in Active Support:
* +config.active_support.bare+ enables or disables the loading of +active_support/all+ when booting Rails. Defaults to +nil+, which means +active_support/all+ is loaded.
-* +config.active_support.escape_html_entities_in_json+ enables or disables the escaping of HTML entities in JSON serialization. Defaults to +true+.
+* +config.active_support.escape_html_entities_in_json+ enables or disables the escaping of HTML entities in JSON serialization. Defaults to +false+.
-* +config.active_support.use_standard_json_time_format+ enables or disables serializing dates to ISO 8601 format. Defaults to +false+.
+* +config.active_support.use_standard_json_time_format+ enables or disables serializing dates to ISO 8601 format. Defaults to +true+.
* +ActiveSupport::BufferedLogger.silencer+ is set to +false+ to disable the ability to silence logging in a block. The default is +true+.
@@ -525,6 +521,14 @@ development:
password:
</yaml>
+Prepared Statements can be disabled thus:
+
+<yaml>
+production:
+ adapter: postgresql
+ prepared_statements: false
+</yaml>
+
h5. Configuring an SQLite3 Database for JRuby Platform
If you choose to use SQLite3 and are using JRuby, your +config/database.yml+ will look a little different. Here's the development section:
@@ -585,15 +589,15 @@ TIP: If you have any ordering dependency in your initializers, you can control t
h3. Initialization events
-Rails has 5 initialization events which can be hooked into (listed in the order that they are ran):
+Rails has 5 initialization events which can be hooked into (listed in the order that they are run):
* +before_configuration+: This is run as soon as the application constant inherits from +Rails::Application+. The +config+ calls are evaluated before this happens.
* +before_initialize+: This is run directly before the initialization process of the application occurs with the +:bootstrap_hook+ initializer near the beginning of the Rails initialization process.
-* +to_prepare+: Run after the initializers are ran for all Railties (including the application itself), but before eager loading and the middleware stack is built. More importantly, will run upon every request in +development+, but only once (during boot-up) in +production+ and +test+.
+* +to_prepare+: Run after the initializers are run for all Railties (including the application itself), but before eager loading and the middleware stack is built. More importantly, will run upon every request in +development+, but only once (during boot-up) in +production+ and +test+.
-* +before_eager_load+: This is run directly before eager loading occurs, which is the default behaviour for the _production_ environment and not for the +development+ environment.
+* +before_eager_load+: This is run directly before eager loading occurs, which is the default behaviour for the +production+ environment and not for the +development+ environment.
* +after_initialize+: Run directly after the initialization of the application, but before the application initializers are run.
@@ -732,7 +736,7 @@ The error occurred while evaluating nil.each
*+load_config_initializers+* Loads all Ruby files from +config/initializers+ in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks are loaded.
-*+engines_blank_point+* Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are ran.
+*+engines_blank_point+* Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run.
*+add_generator_templates+* Finds templates for generators at +lib/templates+ for the application, railities and engines and adds these to the +config.generators.templates+ setting, which will make the templates available for all generators to reference.
diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index d0dbb1555a..df475a2359 100644
--- a/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -42,7 +42,7 @@ h4. Install Git
Ruby on Rails uses git for source code control. The "git homepage":http://git-scm.com/ has installation instructions. There are a variety of resources on the net that will help you get familiar with git:
-* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
+* "Everyday Git":http://schacon.github.com/git/everyday.html will teach you just enough about git to get by.
* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
* "GitHub":http://help.github.com offers links to a variety of git resources.
* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license.
@@ -105,6 +105,13 @@ $ cd railties
$ TEST_DIR=generators bundle exec rake test
</shell>
+You can run any single test separately too:
+
+<shell>
+$ cd actionpack
+$ ruby -Itest test/template/form_helper_test.rb
+</shell>
+
h4. Warnings
The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
@@ -201,6 +208,12 @@ $ bundle exec rake test
will now run the four of them in turn.
+You can also run any single test separately:
+
+<shell>
+$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
+</shell>
+
You can invoke +test_jdbcmysql+, +test_jdbcsqlite3+ or +test_jdbcpostgresql+ also. See the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/travis.rb+ for the test suite run by the continuous integration server.
h4. Older Versions of Ruby on Rails
diff --git a/guides/source/debugging_rails_applications.textile b/guides/source/debugging_rails_applications.textile
index 57c7786636..45fa4ada78 100644
--- a/guides/source/debugging_rails_applications.textile
+++ b/guides/source/debugging_rails_applications.textile
@@ -124,7 +124,7 @@ h4. Log Levels
When something is logged it's printed into the corresponding log if the log level of the message is equal or higher than the configured log level. If you want to know the current log level you can call the +Rails.logger.level+ method.
-The available log levels are: +:debug+, +:info+, +:warn+, +:error+, and +:fatal+, corresponding to the log level numbers from 0 up to 4 respectively. To change the default log level, use
+The available log levels are: +:debug+, +:info+, +:warn+, +:error+, +:fatal+, and +:unknown+, corresponding to the log level numbers from 0 up to 5 respectively. To change the default log level, use
<ruby>
config.log_level = :warn # In any environment initializer, or
@@ -191,7 +191,7 @@ Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localh
Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels, to avoid filling your production logs with useless trivia.
-h3. Debugging with +ruby-debug+
+h3. Debugging with the +debugger+ gem
When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion.
@@ -199,17 +199,13 @@ The debugger can also help you if you want to learn about the Rails source code
h4. Setup
-The debugger used by Rails, +ruby-debug+, comes as a gem. To install it, just run:
+Rails uses the +debugger+ gem to set breakpoints and step through live code. To install it, just run:
<shell>
-$ sudo gem install ruby-debug
+$ gem install debugger
</shell>
-TIP: If you are using Ruby 1.9, you can install a compatible version of +ruby-debug+ by running +sudo gem install ruby-debug19+
-
-In case you want to download a particular version or get the source code, refer to the "project's page on rubyforge":http://rubyforge.org/projects/ruby-debug/.
-
-Rails has had built-in support for ruby-debug since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the +debugger+ method.
+Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the +debugger+ method.
Here's an example:
@@ -238,11 +234,11 @@ $ rails server --debugger
...
</shell>
-TIP: In development mode, you can dynamically +require \'ruby-debug\'+ instead of restarting the server, if it was started without +--debugger+.
+TIP: In development mode, you can dynamically +require \'debugger\'+ instead of restarting the server, if it was started without +--debugger+.
h4. The Shell
-As soon as your application calls the +debugger+ method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at ruby-debug's prompt +(rdb:n)+. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run.
+As soon as your application calls the +debugger+ method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt +(rdb:n)+. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run.
If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request.
@@ -270,7 +266,7 @@ continue edit frame method putl set tmate where
TIP: To view the help menu for any command use +help &lt;command-name&gt;+ in active debug mode. For example: _+help var+_
-The next command to learn is one of the most useful: +list+. You can also abbreviate ruby-debug commands by supplying just enough letters to distinguish them from other commands, so you can also use +l+ for the +list+ command.
+The next command to learn is one of the most useful: +list+. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use +l+ for the +list+ command.
This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by +=>+.
@@ -347,7 +343,7 @@ 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.
-ruby-debug creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped.
+The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped.
At any time you can call the +backtrace+ command (or its alias +where+) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then +backtrace+ will supply the answer.
@@ -463,7 +459,7 @@ h4. Step by Step
Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution.
-Use +step+ (abbreviated +s+) to continue running your program until the next logical stopping point and return control to ruby-debug.
+Use +step+ (abbreviated +s+) to continue running your program until the next logical stopping point and return control to the debugger.
TIP: You can also use <tt>step<plus> n</tt> and <tt>step- n</tt> to move forward or backward +n+ steps respectively.
@@ -485,12 +481,12 @@ class Author < ActiveRecord::Base
end
</ruby>
-TIP: You can use ruby-debug while using +rails console+. Just remember to +require "ruby-debug"+ before calling the +debugger+ method.
+TIP: You can use the debugger while using +rails console+. Just remember to +require "debugger"+ before calling the +debugger+ method.
<shell>
$ rails console
Loading development environment (Rails 3.1.0)
->> require "ruby-debug"
+>> require "debugger"
=> []
>> author = Author.first
=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10">
@@ -603,7 +599,7 @@ A simple quit tries to terminate all threads in effect. Therefore your server wi
h4. Settings
-There are some settings that can be configured in ruby-debug to make it easier to debug your code. Here are a few of the available options:
+The +debugger+ gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options:
* +set reload+: Reload source code when changed.
* +set autolist+: Execute +list+ command on every breakpoint.
@@ -612,7 +608,7 @@ There are some settings that can be configured in ruby-debug to make it easier t
You can see the full list by using +help set+. Use +help set _subcommand_+ to learn about a particular +set+ command.
-TIP: You can include any number of these configuration lines inside a +.rdebugrc+ file in your HOME directory. ruby-debug will read this file every time it is loaded and configure itself accordingly.
+TIP: You can save these settings in an +.rdebugrc+ file in your home directory. The debugger reads these global settings when it starts.
Here's a good start for an +.rdebugrc+:
@@ -637,7 +633,7 @@ If a Ruby object does not go out of scope, the Ruby Garbage Collector won't swee
To install it run:
<shell>
-$ sudo gem install bleak_house
+$ gem install bleak_house
</shell>
Then setup your application for profiling. Then add the following at the bottom of config/environment.rb:
@@ -703,11 +699,12 @@ There are some Rails plugins to help you to find errors and debug your applicati
h3. References
* "ruby-debug Homepage":http://www.datanoise.com/ruby-debug
+* "debugger Homepage":http://github.com/cldwalker/debugger
* "Article: Debugging a Rails application with ruby-debug":http://www.sitepoint.com/article/debug-rails-app-ruby-debug/
* "ruby-debug Basics screencast":http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/
-* "Ryan Bate's ruby-debug screencast":http://railscasts.com/episodes/54-debugging-with-ruby-debug
-* "Ryan Bate's stack trace screencast":http://railscasts.com/episodes/24-the-stack-trace
-* "Ryan Bate's logger screencast":http://railscasts.com/episodes/56-the-logger
+* "Ryan Bates' debugging ruby (revised) screencast":http://railscasts.com/episodes/54-debugging-ruby-revised
+* "Ryan Bates' stack trace screencast":http://railscasts.com/episodes/24-the-stack-trace
+* "Ryan Bates' logger screencast":http://railscasts.com/episodes/56-the-logger
* "Debugging with ruby-debug":http://bashdb.sourceforge.net/ruby-debug.html
* "ruby-debug cheat sheet":http://cheat.errtheblog.com/s/rdebug/
* "Ruby on Rails Wiki: How to Configure Logging":http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging
diff --git a/guides/source/engines.textile b/guides/source/engines.textile
index 047f9afd76..880be57fb5 100644
--- a/guides/source/engines.textile
+++ b/guides/source/engines.textile
@@ -16,7 +16,7 @@ Engines can be considered miniature applications that provide functionality to t
Therefore, engines and applications can be thought of almost the same thing, just with very minor differences, as you'll see throughout this guide. Engines and applications also share a common structure.
-Engines are also closely related to plugins where the two share a common +lib+ directory structure and are both generated using the +rails plugin new+ generator. The difference being that an engine is considered a "full plugin" by Rails -- as indicated by the +--full+ option that's passed to the generator command -- but this guide will refer to them simply as "engines" throughout. An engine *can* be a plugin, and a plugin *can* be an engine.
+Engines are also closely related to plugins where the two share a common +lib+ directory structure and are both generated using the +rails plugin new+ generator. The difference being that an engine is considered a "full plugin" by Rails as indicated by the +--full+ option that's passed to the generator command, but this guide will refer to them simply as "engines" throughout. An engine *can* be a plugin, and a plugin *can* be an engine.
The engine that will be created in this guide will be called "blorgh". The engine will provide blogging functionality to its host applications, allowing for new posts and comments to be created. At the beginning of this guide, you will be working solely within the engine itself, but in later sections you'll see how to hook it into an application.
@@ -51,7 +51,7 @@ h5. Critical files
At the root of this brand new engine's directory, lives a +blorgh.gemspec+ file. When you include the engine into the application later on, you will do so with this line in a Rails application's +Gemfile+:
<ruby>
- gem 'blorgh', :path => "vendor/engines/blorgh"
+gem 'blorgh', :path => "vendor/engines/blorgh"
</ruby>
By specifying it as a gem within the +Gemfile+, Bundler will load it as such, parsing this +blorgh.gemspec+ file and requiring a file within the +lib+ directory called +lib/blorgh.rb+. This file requires the +blorgh/engine.rb+ file (located at +lib/blorgh/engine.rb+) and defines a base module called +Blorgh+.
@@ -77,7 +77,7 @@ end
By inheriting from the +Rails::Engine+ class, this gem notifies Rails that there's an engine at the specified path, and will correctly mount the engine inside the application, performing tasks such as adding the +app+ directory of the engine to the load path for models, mailers, controllers and views.
-The +isolate_namespace+ method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace, away from similar components inside hte application. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption, or that important engine components could be overriden by similarly named things within the application. One of the examples of such conflicts are helpers. Without calling +isolate_namespace+, engine's helpers would be included in application's controllers.
+The +isolate_namespace+ method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace, away from similar components inside the application. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption, or that important engine components could be overridden by similarly named things within the application. One of the examples of such conflicts are helpers. Without calling +isolate_namespace+, engine's helpers would be included in application's controllers.
NOTE: It is *highly* recommended that the +isolate_namespace+ line be left within the +Engine+ class definition. Without it, classes generated in an engine *may* conflict with an application.
@@ -115,7 +115,6 @@ The +test+ directory is where tests for the engine will go. To test the engine,
<ruby>
Rails.application.routes.draw do
-
mount Blorgh::Engine => "/blorgh"
end
</ruby>
@@ -126,7 +125,7 @@ Also in the test directory is the +test/integration+ directory, where integratio
h3. Providing engine functionality
-The engine that this guide covers will provide posting and commenting functionality and follows a similar thread to the "Getting Started Guide":getting-started.html, with some new twists.
+The engine that this guide covers will provide posting and commenting functionality and follows a similar thread to the "Getting Started Guide":getting_started.html, with some new twists.
h4. Generating a post resource
@@ -179,7 +178,6 @@ After that, a line for the resource is inserted into the +config/routes.rb+ file
<ruby>
Blorgh::Engine.routes.draw do
resources :posts
-
end
</ruby>
@@ -219,17 +217,13 @@ By default, the scaffold styling is not applied to the engine as the engine's la
<%= stylesheet_link_tag "scaffold" %>
</erb>
-You can see what the engine has so far by running +rake db:migrate+ at the root of our engine to run the migration generated by the scaffold generator, and then running +rails server+ in +test/dummy+. When you open +http://localhost:3000/blorgh/posts+ you will see the default scaffold that has been generated.
-
-!images/engines_scaffold.png(Blank engine scaffold)!
-
-Click around! You've just generated your first engine's first functions.
+You can see what the engine has so far by running +rake db:migrate+ at the root of our engine to run the migration generated by the scaffold generator, and then running +rails server+ in +test/dummy+. When you open +http://localhost:3000/blorgh/posts+ you will see the default scaffold that has been generated. Click around! You've just generated your first engine's first functions.
If you'd rather play around in the console, +rails console+ will also work just like a Rails application. Remember: the +Post+ model is namespaced, so to reference it you must call it as +Blorgh::Post+.
<ruby>
- >> Blorgh::Post.find(1)
- => #<Blorgh::Post id: 1 ...>
+>> Blorgh::Post.find(1)
+=> #<Blorgh::Post id: 1 ...>
</ruby>
One final thing is that the +posts+ resource for this engine should be the root of the engine. Whenever someone goes to the root path where the engine is mounted, they should be shown a list of posts. This can be made to happen if this line is inserted into the +config/routes.rb+ file inside the engine:
@@ -355,11 +349,11 @@ end
This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error:
-<text>
- Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in:
- * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"
- * "/Users/ryan/Sites/side_projects/blorgh/app/views"
-</text>
+<plain>
+Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in:
+ * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"
+ * "/Users/ryan/Sites/side_projects/blorgh/app/views"
+</plain>
The engine is unable to find the partial required for rendering the comments. Rails has looked firstly in the application's (+test/dummy+) +app/views+ directory and then in the engine's +app/views+ directory. When it can't find it, it will throw this error. The engine knows to look for +blorgh/comments/comment+ because the model object it is receiving is from the +Blorgh::Comment+ class.
@@ -454,6 +448,8 @@ rake db:migrate SCOPE=blorgh VERSION=0
h4. Using a class provided by the application
+h5. Using a model provided by the application
+
When an engine is created, it may want to use specific classes from an application to provide links between the pieces of the engine and the pieces of the application. In the case of the +blorgh+ engine, making posts and comments have authors would make a lot of sense.
Usually, an application would have a +User+ class that would provide the objects that would represent the posts' and comments' authors, but there could be a case where the application calls this class something different, such as +Person+. It's because of this reason that the engine should not hardcode the associations to be exactly for a +User+ class, but should allow for some flexibility around what the class is called.
@@ -511,11 +507,11 @@ $ rake blorgh:install:migrations
Notice here that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run.
-<shell>
- NOTE: Migration [timestamp]_create_blorgh_posts.rb from blorgh has been skipped. Migration with the same name already exists.
- NOTE: Migration [timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration with the same name already exists.
- Copied migration [timestamp]_add_author_id_to_blorgh_posts.rb from blorgh
-</shell>
+<plain>
+NOTE Migration [timestamp]_create_blorgh_posts.rb from blorgh has been skipped. Migration with the same name already exists.
+NOTE Migration [timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration with the same name already exists.
+Copied migration [timestamp]_add_author_id_to_blorgh_posts.rb from blorgh
+</plain>
Run this migration using this command:
@@ -536,9 +532,9 @@ Finally, the author's name should be displayed on the post's page. Add this code
By outputting +@post.author+ using the +<%=+ tag the +to_s+ method will be called on the object. By default, this will look quite ugly:
-<text>
+<plain>
#<User:0x00000100ccb3b0>
-</text>
+</plain>
This is undesirable and it would be much better to have the user's name there. To do this, add a +to_s+ method to the +User+ class within the application:
@@ -550,6 +546,19 @@ end
Now instead of the ugly Ruby object output the author's name will be displayed.
+h5. Using a controller provided by the application
+
+Because Rails controllers generally share code for things like authentication and accessing session variables, by default they inherit from <tt>ApplicationController</tt>. Rails engines, however are scoped to run independently from the main application, so each engine gets a scoped +ApplicationController+. This namespace prevents code collisions, but often engine controllers should access methods in the main application's +ApplicationController+. An easy way to provide this access is to change the engine's scoped +ApplicationController+ to inherit from the main application's +ApplicationController+. For our Blorgh engine this would be done by changing +app/controllers/blorgh/application_controller.rb+ to look like:
+
+<ruby>
+class Blorgh::ApplicationController < ApplicationController
+end
+</ruby>
+
+By default, the engine's controllers inherit from <tt>Blorgh::ApplicationController</tt>. So, after making this change they will have access to the main applications +ApplicationController+ as though they were part of the main application.
+
+This change does require that the engine is run from a Rails application that has an +ApplicationController+.
+
h4. Configuring an engine
This section covers firstly how you can make the +user_class+ setting of the Blorgh engine configurable, followed by general configuration tips for the engine.
@@ -581,9 +590,9 @@ self.author = Blorgh.user_class.constantize.find_or_create_by_name(author_name)
To save having to call +constantize+ on the +user_class+ result all the time, you could instead just override the +user_class+ getter method inside the +Blorgh+ module in the +lib/blorgh.rb+ file to always call +constantize+ on the saved value before returning the result:
<ruby>
- def self.user_class
- @@user_class.constantize
- end
+def self.user_class
+ @@user_class.constantize
+end
</ruby>
This would then turn the above code for +self.author=+ into this:
@@ -663,10 +672,6 @@ Try this now by creating a new file at +app/views/blorgh/posts/index.html.erb+ a
<% end %>
</erb>
-Rather than looking like the default scaffold, the page will now look like this:
-
-!images/engines_post_override.png(Engine scaffold overriden)!
-
h4. Routes
Routes inside an engine are, by default, isolated from the application. This is done by the +isolate_namespace+ call inside the +Engine+ class. This essentially means that the application and its engines can have identically named routes, and that they will not clash.
@@ -674,9 +679,9 @@ Routes inside an engine are, by default, isolated from the application. This is
Routes inside an engine are drawn on the +Engine+ class within +config/routes.rb+, like this:
<ruby>
- Blorgh::Engine.routes.draw do
- resources :posts
- end
+Blorgh::Engine.routes.draw do
+ resources :posts
+end
</ruby>
By having isolated routes such as this, if you wish to link to an area of an engine from within an application, you will need to use the engine's routing proxy method. Calls to normal routing methods such as +posts_path+ may end up going to undesired locations if both the application and the engine both have such a helper defined.
@@ -705,7 +710,7 @@ If a template is rendered from within an engine and it's attempting to use one o
h4. Assets
-Assets within an engine work in an identical way to a full application. Because the engine class inherits from +Rails::Engine+, the application will know to look up in the engine's +app/assets+ directory for potential assets.
+Assets within an engine work in an identical way to a full application. Because the engine class inherits from +Rails::Engine+, the application will know to look up in the engine's +app/assets+ and +lib/assets+ directories for potential assets.
Much like all the other components of an engine, the assets should also be namespaced. This means if you have an asset called +style.css+, it should be placed at +app/assets/stylesheets/[engine name]/style.css+, rather than +app/assets/stylesheets/style.css+. If this asset wasn't namespaced, then there is a possibility that the host application could have an asset named identically, in which case the application's asset would take precedence and the engine's one would be all but ignored.
@@ -717,11 +722,11 @@ Imagine that you did have an asset located at +app/assets/stylesheets/blorgh/sty
You can also specify these assets as dependencies of other assets using the Asset Pipeline require statements in processed files:
-<css>
+<plain>
/*
*= require blorgh/style
*/
-</css>
+</plain>
h4. Separate Assets & Precompiling
@@ -736,7 +741,7 @@ You can define assets for precompilation in +engine.rb+
initializer do |app|
app.config.assets.precompile += %w(admin.css admin.js)
end
-</ruby
+</ruby>
For more information, read the "Asset Pipeline guide":http://guides.rubyonrails.org/asset_pipeline.html
diff --git a/guides/source/form_helpers.textile b/guides/source/form_helpers.textile
index 8934667c5e..033b33ec3b 100644
--- a/guides/source/form_helpers.textile
+++ b/guides/source/form_helpers.textile
@@ -89,7 +89,7 @@ form_tag(:controller => "people", :action => "search", :method => "get", :class
# => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">'
</ruby>
-Here, +method+ and +class+ are appended to the query string of the generated URL because you even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect:
+Here, +method+ and +class+ are appended to the query string of the generated URL because even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect:
<ruby>
form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form")
@@ -150,7 +150,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with
h4. Other Helpers of Interest
-Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, URL fields and email fields:
+Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, URL fields and email fields:
<erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
@@ -161,6 +161,7 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= date_field(:user, :born_on) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
+<%= time_field(:task, :started_at) %>
</erb>
Output:
@@ -174,11 +175,12 @@ Output:
<input id="user_born_on" name="user[born_on]" type="date" />
<input id="user_homepage" name="user[homepage]" type="url" />
<input id="user_address" name="user[address]" type="email" />
+<input id="task_started_at" name="task[started_at]" type="time" />
</html>
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
-IMPORTANT: The search, telephone, date, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
+IMPORTANT: The search, telephone, date, time, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.
@@ -405,6 +407,8 @@ Whenever Rails sees that the internal value of an option being generated matches
TIP: The second argument to +options_for_select+ must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to +options_for_select+ -- you must pass 2. Be aware of values extracted from the +params+ hash as they are all strings.
+WARNING: when +:inlude_blank+ or +:prompt:+ are not present, +:include_blank+ is forced true if the select attribute +required+ is true, display +size+ is one and +multiple+ is not true.
+
h4. Select Boxes for Dealing with Models
In most cases form controls will be tied to a specific database model and as you might expect Rails provides helpers tailored for that purpose. Consistent with other form helpers, when dealing with models you drop the +_tag+ suffix from +select_tag+:
@@ -469,7 +473,7 @@ Rails _used_ to have a +country_select+ helper for choosing countries, but this
h3. Using Date and Time Form Helpers
-You can choose not to use the form helpers generating HTML5 date input fields and use the alternative date and time helpers. These date and time helpers differ from all the other form helpers in two important respects:
+You can choose not to use the form helpers generating HTML5 date and time input fields and use the alternative date and time helpers. These date and time helpers differ from all the other form helpers in two important respects:
# Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your +params+ hash with your date or time.
# Other helpers use the +_tag+ suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, +select_date+, +select_time+ and +select_datetime+ are the barebones helpers, +date_select+, +time_select+ and +datetime_select+ are the equivalent model object helpers.
diff --git a/guides/source/generators.textile b/guides/source/generators.textile
index 920ff997ae..2e9ab0526d 100644
--- a/guides/source/generators.textile
+++ b/guides/source/generators.textile
@@ -451,6 +451,27 @@ Adds a specified source to +Gemfile+:
add_source "http://gems.github.com"
</ruby>
+h4. +inject_into_file+
+
+Injects a block of code into a defined position in your file.
+
+<ruby>
+inject_into_file 'name_of_file.rb', :after => "#The code goes below this line. Don't forget the Line break at the end\n" do <<-'RUBY'
+ puts "Hello World"
+RUBY
+end
+</ruby>
+
+h4. +gsub_file+
+
+Replaces text inside a file.
+
+<ruby>
+gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code'
+</ruby>
+
+Regular Expressions can be used to make this method more precise. You can also use append_file and prepend_file in the same way to place code at the beginning and end of a file respectively.
+
h4. +application+
Adds a line to +config/application.rb+ directly after the application class definition.
diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile
index 0a85c84155..19bd106ff0 100644
--- a/guides/source/getting_started.textile
+++ b/guides/source/getting_started.textile
@@ -10,7 +10,7 @@ you should be familiar with:
endprologue.
-WARNING. This Guide is based on Rails 3.1. Some of the code shown here will not
+WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not
work in earlier versions of Rails.
WARNING: The Edge version of this guide is currently being re-worked. Please excuse us while we re-arrange the place.
@@ -27,8 +27,8 @@ prerequisites installed:
TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails
3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02
though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults
-on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 for smooth
-sailing.
+on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 or
+1.9.3 for smooth sailing.
* The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system
** If you want to learn more about RubyGems, please read the "RubyGems User Guide":http://docs.rubygems.org/read/book/1
@@ -45,10 +45,6 @@ internet for learning Ruby, including:
h3. What is Rails?
-TIP: This section goes into the background and philosophy of the Rails framework
-in detail. You can safely skip this section and come back to it at a later time.
-Section 3 starts you on the path to creating your first Rails application.
-
Rails is a web application development framework written in the Ruby language.
It is designed to make programming web applications easier by making assumptions
about what every developer needs to get started. It allows you to write less
@@ -73,9 +69,11 @@ h3. Creating a New Rails Project
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. You can get the complete code "here":https://github.com/lifo/docrails/tree/master/guides/code/getting_started.
+literally follow along step by step. You can get the complete code
+"here":https://github.com/lifo/docrails/tree/master/guides/code/getting_started.
-By following along with this guide, you'll create a Rails project called <tt>blog</tt>, a
+By following along with this guide, you'll create a Rails project called
++blog+, a
(very) simple weblog. Before you can start building the application, you need to
make sure that you have Rails itself installed.
@@ -89,7 +87,10 @@ To install Rails, use the +gem install+ command provided by RubyGems:
# gem install rails
</shell>
-TIP. If you're working on Windows, you can quickly install Ruby and Rails with "Rails Installer":http://railsinstaller.org.
+TIP. A number of tools exist to help you quickly install Ruby and Ruby
+on Rails on your system. Windows users can use "Rails
+Installer":http://railsinstaller.org, while Mac OS X users can use
+"Rails One Click":http://railsoneclick.com.
To verify that you have everything installed correctly, you should be able to run the following:
@@ -97,7 +98,7 @@ To verify that you have everything installed correctly, you should be able to ru
$ rails --version
</shell>
-If it says something like "Rails 3.2.2" you are ready to continue.
+If it says something like "Rails 3.2.3" you are ready to continue.
h4. Creating the Blog Application
@@ -111,7 +112,8 @@ $ rails new blog
This will create a Rails application called Blog in a directory called blog.
-TIP: You can see all of the switches that the Rails application builder accepts by running <tt>rails new -h</tt>.
+TIP: You can see all of the command line options that the Rails
+application builder accepts by running +rails new -h+.
After you create the blog application, switch to its folder to continue work directly in that application:
@@ -119,10 +121,13 @@ After you create the blog application, switch to its folder to continue work dir
$ cd blog
</shell>
-The +rails new blog+ command we ran above created a folder in your working directory called <tt>blog</tt>. The <tt>blog</tt> directory has a number of auto-generated folders that make up the structure of a Rails application. Most of the work in this tutorial will happen in the <tt>app/</tt> folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default:
+The +rails new blog+ command we ran above created a folder in your
+working directory called +blog+. The +blog+ directory has a number of
+auto-generated files and folders that make up the structure of a Rails
+application. Most of the work in this tutorial will happen in the +app/+ folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default:
|_.File/Folder|_.Purpose|
-|app/|Contains the controllers, models, views and assets for your application. You'll focus on this folder for the remainder of this guide.|
+|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
|config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in "Configuring Rails Applications":configuring.html|
|config.ru|Rack configuration for Rack based servers used to start the application.|
|db/|Contains your current database schema, as well as the database migrations.|
@@ -140,7 +145,7 @@ The +rails new blog+ command we ran above created a folder in your working direc
h3. Hello, Rails!
-One of the traditional places to start with a new language is by getting some text up on screen quickly. To do this, you need to get your Rails application server running.
+To begin with, let's get some text up on screen quickly. To do this, you need to get your Rails application server running.
h4. Starting up the Web Server
@@ -152,11 +157,11 @@ $ rails server
TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the absence of a runtime will give you an +execjs+ error. Usually Mac OS X and Windows come with a JavaScript runtime installed. Rails adds the +therubyracer+ gem to Gemfile in a commented line for new apps and you can uncomment if you need it. +therubyrhino+ is the recommended runtime for JRuby users and is added by default to Gemfile in apps generated under JRuby. You can investigate about all the supported runtimes at "ExecJS":https://github.com/sstephenson/execjs#readme.
-This will fire up an instance of a webserver built into Ruby called WEBrick by default. To see your application in action, open a browser window and navigate to "http://localhost:3000":http://localhost:3000. You should see Rails' default information page:
+This will fire up WEBrick, a webserver built into Ruby by default. To see your application in action, open a browser window and navigate to "http://localhost:3000":http://localhost:3000. You should see the Rails default information page:
!images/rails_welcome.png(Welcome Aboard screenshot)!
-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.
+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 restart 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.
@@ -164,7 +169,7 @@ h4. Say "Hello", Rails
To get Rails saying "Hello", you need to create at minimum a _controller_ and a _view_.
-A controller's purpose is to receive specific requests for the application. What controller receives what request is determined by the _routing_. There is very often more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view.
+A controller's purpose is to receive specific requests for the application. _Routing_ decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view.
A view's purpose is to display this information in a human readable format. An important distinction to make is that it is the _controller_, not the view, where information is collected. The view should just display that information. By default, view templates are written in a language called ERB (Embedded Ruby) which is converted by the request cycle in Rails before being sent to the user.
@@ -178,9 +183,9 @@ Rails will create several files for you. Most important of these are of course t
Open the +app/views/welcome/index.html.erb+ file in your text editor and edit it to contain a single line of code:
-<code class="html">
+<html>
<h1>Hello, Rails!</h1>
-</code>
+</html>
h4. Setting the Application Home Page
@@ -188,7 +193,7 @@ Now that we have made the controller and view, we need to tell Rails when we wan
To fix this, delete the +index.html+ file located inside the +public+ directory of the application.
-You need to do this because Rails will serve any static file in the +public+ directory that matches a route in preference to any dynamic content you generate from the controllers.
+You need to do this because Rails will serve any static file in the +public+ directory that matches a route in preference to any dynamic content you generate from the controllers. The +index.html+ file is special: it will be served if a request comes in at the root route, e.g. http://localhost:3000. If another request such as http://localhost:3000/welcome happened, a static file at <tt>public/welcome.html</tt> would be served first, but only if it existed.
Next, you have to tell Rails where your actual home page is located.
@@ -205,7 +210,7 @@ Blog::Application.routes.draw do
The +root :to => "welcome#index"+ tells Rails to map requests to the root of the application to the welcome controller's index action. This was created earlier when you ran the controller generator (+rails generate controller welcome index+).
-If you navigate to "http://localhost:3000":http://localhost:3000 in your browser, you'll see +Hello, Rails!+.
+If you navigate to "http://localhost:3000":http://localhost:3000 in your browser, you'll see the +Hello, Rails!+ message you put into +app/views/welcome/index.html.erb+, indicating that this new route is indeed going to +WelcomeController+'s +index+ action and is rendering the view correctly.
NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html.
@@ -215,7 +220,7 @@ Now that you've seen how to create a controller, an action and a view, let's cre
In the Blog application, you will now create a new _resource_. A resource is the term used for a collection of similar objects, such as posts, people or animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations.
-In the next section, you will add the ability to create new posts in your application and be able to view them. This is the "CR" from CRUD. The form for doing this will look like this:
+In the next section, you will add the ability to create new posts in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this:
!images/getting_started/new_post.png(The new post form)!
@@ -225,10 +230,9 @@ h4. Laying down the ground work
The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at +/posts/new+. If you attempt to navigate to that now -- by visiting "http://localhost:3000/posts/new":http://localhost:3000/posts/new -- Rails will give you a routing error:
-
!images/getting_started/routing_error_no_route_matches.png(A routing error, no route matches /posts/new)!
-This is because there is nowhere inside the routes for the application -- defined inside +config/routes.rb+ -- that defines this route. By default, Rails has no routes configured at all, and so you must define your routes as you need them.
+This is because there is nowhere inside the routes for the application -- defined inside +config/routes.rb+ -- that defines this route. By default, Rails has no routes configured at all, besides the root route you defined earlier, and so you must define your routes as you need them.
To do this, you're going to need to create a route inside +config/routes.rb+ file, on a new line between the +do+ and the +end+ for the +draw+ method:
@@ -240,7 +244,7 @@ This route is a super-simple route: it defines a new route that only responds to
With the route defined, requests can now be made to +/posts/new+ in the application. Navigate to "http://localhost:3000/posts/new":http://localhost:3000/posts/new and you'll see another routing error:
-!images/getting_started/routing_error_no_controller.png(Another routing error, uninitialized constant PostsController)
+!images/getting_started/routing_error_no_controller.png(Another routing error, uninitialized constant PostsController)!
This error is happening because this route need a controller to be defined. The route is attempting to find that controller so it can serve the request, but with the controller undefined, it just can't do that. The solution to this particular problem is simple: you need to create a controller called +PostsController+. You can do this by running this command:
@@ -259,7 +263,7 @@ A controller is simply a class that is defined to inherit from +ApplicationContr
If you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/new now, you'll get a new error:
-!images/getting_started/unknown_action_new_for_posts.png(Unknown action new for PostsController!)
+!images/getting_started/unknown_action_new_for_posts.png(Unknown action new for PostsController!)!
This error indicates that Rails cannot find the +new+ action inside the +PostsController+ that you just generated. This is because when controllers are generated in Rails they are empty by default, unless you tell it you wanted actions during the generation process.
@@ -272,25 +276,25 @@ end
With the +new+ method defined in +PostsController+, if you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/new you'll see another error:
-!images/getting_started/template_is_missing_posts_new.png(Template is missing for posts/new)
+!images/getting_started/template_is_missing_posts_new.png(Template is missing for posts/new)!
You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view available, Rails errors out.
In the above image, the bottom line has been truncated. Let's see what the full thing looks like:
-<text>
+<blockquote>
Missing template posts/new, application/new with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
-</text>
+</blockquote>
That's quite a lot of text! Let's quickly go through and understand what each part of it does.
-The first part identifies what template is missing. In this case, it's the +posts/new+ template. Rails will first look for this template. If it can't find it, then it will attempt to load a template called +application/new+. It looks for one here because the +PostsController+ inherits from +ApplicationController+.
+The first part identifies what template is missing. In this case, it's the +posts/new+ template. Rails will first look for this template. If not found, then it will attempt to load a template called +application/new+. It looks for one here because the +PostsController+ inherits from +ApplicationController+.
-The next part of the message contains a hash. The +:locale+ key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English -- or "en" -- template. The next key, +:formats+ shows what formats of template Rails is after. The default is +:html+, and so Rails is looking for an HTML template. The final key, +:handlers+, is telling us what _template handlers_ could be used to render our template. +:erb+ is most commonly used for HTML templates, +:builder+ is used for XML templates, and +:coffee+ uses CoffeeScript to build JavaScript templates.
+The next part of the message contains a hash. The +:locale+ key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English -- or "en" -- template. The next key, +:formats+ specifies the format of template to be served in response . The default format is +:html+, and so Rails is looking for an HTML template. The final key, +:handlers+, is telling us what _template handlers_ could be used to render our template. +:erb+ is most commonly used for HTML templates, +:builder+ is used for XML templates, and +:coffee+ uses CoffeeScript to build JavaScript templates.
The final part of this message tells us where Rails has looked for the templates. Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths.
-The simplest template that would work in this case would be one located at +app/views/posts/new.html.erb+. The extension of this file name is key: the first extension is the _format_ of the template, and the second extension is the _handler_ that will be used. Rails is attempting to find a template called +posts/new+ within +app/views+ for the application. The format for this template can only be +html+ and the handler must be one of +erb+, +builder+ or +coffee+. Because you want to create a new HTML form, you will be using the +ERB+ language. Therefore the file should be called +posts/new.html.erb+ and be located inside the +app/views+ directory of the application.
+The simplest template that would work in this case would be one located at +app/views/posts/new.html.erb+. The extension of this file name is key: the first extension is the _format_ of the template, and the second extension is the _handler_ that will be used. Rails is attempting to find a template called +posts/new+ within +app/views+ for the application. The format for this template can only be +html+ and the handler must be one of +erb+, +builder+ or +coffee+. Because you want to create a new HTML form, you will be using the +ERB+ language. Therefore the file should be called +posts/new.html.erb+ and needs to be located inside the +app/views+ directory of the application.
Go ahead now and create a new file at +app/views/posts/new.html.erb+ and write this content in it:
@@ -302,7 +306,9 @@ When you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/n
h4. The first form
-To create a form within this template, you will use a _form builder_. The primary form builder for Rails is provided by a helper method called +form_for+. To use this method, write this code into +app/views/posts/new.html.erb+:
+To create a form within this template, you will use a <em>form
+builder</em>. The primary form builder for Rails is provided by a helper
+method called +form_for+. To use this method, add this code into +app/views/posts/new.html.erb+:
<erb>
<%= form_for :post do |f| %>
@@ -324,11 +330,17 @@ To create a form within this template, you will use a _form builder_. The primar
If you refresh the page now, you'll see the exact same form as in the example. Building forms in Rails is really just that easy!
-When you call +form_for+, you pass it an identifying object for this form. In this case, it's the symbol +:post+. This tells the +form_for+ helper what this form is for. Inside the block for this method, the FormBuilder object -- represented by +f+ -- is used to build two labels and two text fields, one each for the title and text of a post. Finally, a call to +submit+ on the +f+ object will create a submit button for the form.
+When you call +form_for+, you pass it an identifying object for this
+form. In this case, it's the symbol +:post+. This tells the +form_for+
+helper what this form is for. Inside the block for this method, the
++FormBuilder+ object -- represented by +f+ -- is used to build two labels and two text fields, one each for the title and text of a post. Finally, a call to +submit+ on the +f+ object will create a submit button for the form.
There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the +action+ attribute for the form is pointing at +/posts/new+. This is a problem because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new post.
-So the form needs to use a different URL in order to go somewhere else. This can be done quite simply with the +:url+ option of +form_for+. Typically in Rails, the action that is used for new form submissions like this is called "create", and so the form should be pointed to this action.
+The form needs to use a different URL in order to go somewhere else.
+This can be done quite simply with the +:url+ option of +form_for+.
+Typically in Rails, the action that is used for new form submissions
+like this is called "create", and so the form should be pointed to that action.
Edit the +form_for+ line inside +app/views/posts/new.html.erb+ to look like this:
@@ -344,11 +356,11 @@ post "posts/create"
By using the +post+ method rather than the +get+ method, Rails will define a route that will only respond to POST methods. The POST method is the typical method used by forms all over the web.
-With the form and the route for it defined now, you will be able to fill in the form and then click the submit button to begin the process of creating a new post, so go ahead and do that. When you submit the form, you should see a familiar error:
+With the form and its associated route defined, you will be able to fill in the form and then click the submit button to begin the process of creating a new post, so go ahead and do that. When you submit the form, you should see a familiar error:
-!images/getting_started/unknown_action_create_for_posts(Unknown action create for PostsController)!
+!images/getting_started/unknown_action_create_for_posts.png(Unknown action create for PostsController)!
-You will now need to create the +create+ action within the +PostsController+ for this to work.
+You now need to create the +create+ action within the +PostsController+ for this to work.
h4. Creating posts
@@ -375,7 +387,7 @@ def create
end
</ruby>
-The +render+ method here is taking a very simple hash with the key of +text+ and the value of +params[:post].inspect+. The +params+ method here is the object which represents the parameters (or fields) coming in from the form. The +params+ method returns a +HashWithIndifferentAccess+ object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form.
+The +render+ method here is taking a very simple hash with a key of +text+ and value of +params[:post].inspect+. The +params+ method is the object which represents the parameters (or fields) coming in from the form. The +params+ method returns a +HashWithIndifferentAccess+ object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form.
If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following:
@@ -385,25 +397,52 @@ If you re-submit the form one more time you'll now no longer get the missing tem
This action is now displaying the parameters for the post that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them.
+h4. Creating the Post model
+
+Models in Rails use a singular name, and their corresponding database tables use
+a plural name. Rails provides a generator for creating models, which
+most Rails developers tend to use when creating new models.
+To create the new model, run this command in your terminal:
+
+<shell>
+$ rails generate model Post title:string text:text
+</shell>
+
+With that command we told Rails that we want a +Post+ model, together
+with a _title_ attribute of type string, and a _text_ attribute
+of type text. Those attributes are automatically added to the +posts+
+table in the database and mapped to the +Post+ model.
+
+Rails responded by creating a bunch of files. For
+now, we're only interested in +app/models/post.rb+ and
++db/migrate/20120419084633_create_posts.rb+ (your name could be a bit
+different). The latter is responsible
+for creating the database structure, which is what we'll look at next.
+
+TIP: Active Record is smart enough to automatically map column names to
+model attributes, which means you don't have to declare attributes
+inside Rails models, as that will be done automatically by Active
+Record.
+
h4. Running a Migration
-One of the products of the +rails generate scaffold+ command is a _database
-migration_. Migrations are Ruby classes that are designed to make it simple to
+As we've just seen, +rails generate model+ created a _database
+migration_ file inside the +db/migrate+ directory.
+Migrations are Ruby classes that are designed to make it simple to
create and modify database tables. Rails uses rake commands to run migrations,
and it's possible to undo a migration after it's been applied to your database.
Migration filenames include a timestamp to ensure that they're processed in the
order that they were created.
-If you look in the +db/migrate/20100207214725_create_posts.rb+ file (remember,
+If you look in the +db/migrate/20120419084633_create_posts.rb+ file (remember,
yours will have a slightly different name), here's what you'll find:
<ruby>
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
- t.string :name
t.string :title
- t.text :content
+ t.text :text
t.timestamps
end
@@ -415,7 +454,7 @@ The above migration creates a method named +change+ which will be called when yo
run this migration. The action defined in this method is also reversible, which
means Rails knows how to reverse the change made by this migration, in case you
want to reverse it later. When you run this migration it will create a
-+posts+ table with two string columns and a text column. It also creates two
++posts+ table with one string column and a text column. It also creates two
timestamp fields to allow Rails to track post creation and update times. More
information about Rails migrations can be found in the "Rails Database
Migrations":migrations.html guide.
@@ -440,43 +479,180 @@ NOTE. Because you're working in the development environment by default, this
command will apply to the database defined in the +development+ section of your
+config/database.yml+ file. If you would like to execute migrations in another
environment, for instance in production, you must explicitly pass it when
-invoking the command: <tt>rake db:migrate RAILS_ENV=production</tt>.
+invoking the command: +rake db:migrate RAILS_ENV=production+.
+
+h4. Saving data in the controller
+
+Back in +posts_controller+, we need to change the +create+ action
+to use the new +Post+ model to save the data in the database. Open that file
+and change the +create+ action to look like this:
+
+<ruby>
+def create
+ @post = Post.new(params[:post])
+
+ @post.save
+ redirect_to :action => :show, :id => @post.id
+end
+</ruby>
+
+Here's what's going on: every Rails model can be initialized with its
+respective attributes, which are automatically mapped to the respective
+database columns. In the first line we do just that (remember that
++params[:post]+ contains the attributes we're interested in). Then,
++@post.save+ is responsible for saving the model in the database.
+Finally, we redirect the user to the +show+ action,
+wich we'll define later.
+
+TIP: As we'll see later, +@post.save+ returns a boolean indicating
+wherever the model was saved or not.
+
+h4. Showing Posts
+
+If you submit the form again now, Rails will complain about not finding
+the +show+ action. That's not very useful though, so let's add the
++show+ action before proceeding. Open +config/routes.rb+ and add the following route:
+
+<ruby>
+get "posts/:id" => "posts#show"
+</ruby>
+
+The special syntax +:id+ tells rails that this route expects an +:id+
+parameter, which in our case will be the id of the post. Note that this
+time we had to specify the actual mapping, +posts#show+ because
+otherwise Rails would not know which action to render.
+
+As we did before, we need to add the +show+ action in the
++posts_controller+ and its respective view.
+
+<ruby>
+def show
+ @post = Post.find(params[:id])
+end
+</ruby>
+
+A couple of things to note. We use +Post.find+ to find the post we're
+interested in. We also use an instance variable (prefixed by +@+) to
+hold a reference to the post object. We do this because Rails will pass all instance
+variables to the view.
+
+Now, create a new file +app/view/posts/show.html.erb+ with the following
+content:
+
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+</erb>
-h4. Adding a Link
+Finally, if you now go to
+"http://localhost:3000/posts/new":http://localhost:3000/posts/new you'll
+be able to create a post. Try it!
-To hook the posts up to the home page you've already created, you can add a link
-to the home page. Open +app/views/welcome/index.html.erb+ and modify it as follows:
+!images/getting_started/show_action_for_posts.png(Show action for posts)!
+
+h4. Listing all posts
+
+We still need a way to list all our posts, so let's do that. As usual,
+we'll need a route placed into +config/routes.rb+:
+
+<ruby>
+get "posts" => "posts#index"
+</ruby>
+
+And an action for that route inside the +PostsController+ in the +app/controllers/posts_controller.rb+ file:
+
+<ruby>
+def index
+ @posts = Post.all
+end
+</ruby>
+
+And then finally a view for this action, located at +app/views/posts/index.html.erb+:
+
+<erb>
+<h1>Listing posts</h1>
+
+<table>
+ <tr>
+ <th>Title</th>
+ <th>Text</th>
+ </tr>
+
+ <% @posts.each do |post| %>
+ <tr>
+ <td><%= post.title %></td>
+ <td><%= post.text %></td>
+ </tr>
+ <% end %>
+</table>
+</erb>
+
+Now if you go to +http://localhost:3000/posts+ you will see a list of all the posts that you have created.
+
+h4. Adding links
+
+You can now create, show, and list posts. Now let's add some links to
+navigate through pages.
+
+Open +app/views/welcome/index.html.erb+ and modify it as follows:
<ruby>
<h1>Hello, Rails!</h1>
-<%= link_to "My Blog", posts_path %>
+<%= link_to "My Blog", :controller => "posts" %>
</ruby>
The +link_to+ method is one of Rails' built-in view helpers. It creates a
hyperlink based on text to display and where to go - in this case, to the path
for posts.
-h4. Working with Posts in the Browser
+Let's add links to the other views as well, starting with adding this "New Post" link to +app/views/posts/index.html.erb+, placing it above the +<table>+ tag:
+
+<erb>
+<%= link_to 'New post', :action => :new %>
+</erb>
+
+This link will allow you to bring up the form that lets you create a new post. You should also add a link to this template -- +app/views/posts/new.html.erb+ -- to go back to the +index+ action. Do this by adding this underneath the form in this template:
+
+<erb>
+<%= form_for :post do |f| %>
+ ...
+<% end %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+Finally, add another link to the +app/views/posts/show.html.erb+ template to go back to the +index+ action as well, so that people who are viewing a single post can go back and view the whole list again:
-Now you're ready to start working with posts. To do that, navigate to
-"http://localhost:3000":http://localhost:3000/ and then click the "My Blog"
-link:
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
-!images/posts_index.png(Posts Index screenshot)!
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
-This is the result of Rails rendering the +index+ view of your posts. There
-aren't currently any posts in the database, but if you click the +New Post+ link
-you can create one. After that, you'll find that you can edit posts, look at
-their details, or destroy them. All of the logic and HTML to handle this was
-built by the single +rails generate scaffold+ command.
+TIP: If you want to link to an action in the same controller, you don't
+need to specify the +:controller+ option, as Rails will use the current
+controller by default.
TIP: In development mode (which is what you're working in by default), Rails
reloads your application with every browser request, so there's no need to stop
-and restart the web server.
+and restart the web server when a change is made.
-Congratulations, you're riding the rails! Now it's time to see how it all works.
-
-h4. The Model
+h4. Allowing the update of fields
The model file, +app/models/post.rb+ is about as simple as it can get:
@@ -491,6 +667,19 @@ your Rails models for free, including basic database CRUD (Create, Read, Update,
Destroy) operations, data validation, as well as sophisticated search support
and the ability to relate multiple models to one another.
+Rails includes methods to help you secure some of your model fields.
+Open the +app/models/post.rb+ file and edit it:
+
+<ruby>
+class Post < ActiveRecord::Base
+ attr_accessible :text, :title
+end
+</ruby>
+
+This change will ensure that all changes made through HTML forms can edit the content of the text and title fields.
+It will not be possible to define any other field value through forms. You can still define them by calling the `field=` method of course.
+Accessible attributes and the mass assignment probem is covered in details in the "Security guide":security.html#mass-assignment
+
h4. Adding Some Validation
Rails includes methods to help you validate the data that you send to models.
@@ -498,205 +687,288 @@ Open the +app/models/post.rb+ file and edit it:
<ruby>
class Post < ActiveRecord::Base
- validates :name, :presence => true
+ attr_accessible :text, :title
+
validates :title, :presence => true,
:length => { :minimum => 5 }
end
</ruby>
-These changes will ensure that all posts have a name and a title, and that the
-title is at least five characters long. Rails can validate a variety of
-conditions in a model, including the presence or uniqueness of columns, their
+These changes will ensure that all posts have a title that is at least five characters long.
+Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their
format, and the existence of associated objects. Validations are covered in detail
-in "Active Record Validations and Callbacks":active_record_validations_callbacks.html#validations-overview
+in "Active Record Validations and
+Callbacks":active_record_validations_callbacks.html#validations-overview
-h4. Using the Console
+With the validation now in place, when you call +@post.save+ on an invalid
+post, it will return +false+. If you open +app/controllers/posts_controller.rb+
+again, you'll notice that we don't check the result of calling +@post.save+
+inside the +create+ action. If +@post.save+ fails in this situation, we need to
+show the form back to the user. To do this, change the +new+ and +create+
+actions inside +app/controllers/posts_controller.rb+ to these:
-To see your validations in action, you can use the console. The console is a
-command-line tool that lets you execute Ruby code in the context of your
-application:
+<ruby>
+def new
+ @post = Post.new
+end
-<shell>
-$ rails console
-</shell>
+def create
+ @post = Post.new(params[:post])
+
+ if @post.save
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'new'
+ end
+end
+</ruby>
-TIP: The default console will make changes to your database. You can instead
-open a console that will roll back any changes you make by using <tt>rails console
---sandbox</tt>.
+The +new+ action is now creating a new instance variable called +@post+, and
+you'll see why that is in just a few moments.
-After the console loads, you can use it to work with your application's models:
+Notice that inside the +create+ action we use +render+ instead of +redirect_to+ when +save+
+returns +false+. The +render+ method is used so that the +@post+ object is passed back to the +new+ template when it is rendered. This rendering is done within the same request as the form submission, whereas the +redirect_to+ will tell the browser to issue another request.
-<shell>
->> p = Post.new(:content => "A new post")
-=> #<Post id: nil, name: nil, title: nil,
- content: "A new post", created_at: nil,
- updated_at: nil>
->> p.save
-=> false
->> p.errors.full_messages
-=> ["Name can't be blank", "Title can't be blank", "Title is too short (minimum is 5 characters)"]
-</shell>
+If you reload
+"http://localhost:3000/posts/new":http://localhost:3000/posts/new and
+try to save a post without a title, Rails will send you back to the
+form, but that's not very useful. You need to tell the user that
+something went wrong. To do that, you'll modify
++app/views/posts/new.html.erb+ to check for error messages:
+
+<erb>
+<%= form_for :post, :url => { :action => :create } do |f| %>
+ <% if @post.errors.any? %>
+ <div id="errorExplanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited
+ this post from being saved:</h2>
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ <p>
+ <%= f.label :title %><br>
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br>
+ <%= f.text_area :text %>
+ </p>
-This code shows creating a new +Post+ instance, attempting to save it and
-getting +false+ for a return value (indicating that the save failed), and
-inspecting the +errors+ of the post.
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+A few things are going on. We check if there are any errors with
++@post.errors.any?+, and in that case we show a list of all
+errors with +@post.errors.full_messages+.
+
++pluralize+ is a rails helper that takes a number and a string as its
+arguments. If the number is greater than one, the string will be automatically pluralized.
-When you're finished, type +exit+ and hit +return+ to exit the console.
+The reason why we added +@post = Post.new+ in +posts_controller+ is that
+otherwise +@post+ would be +nil+ in our view, and calling
++@post.errors.any?+ would throw an error.
-TIP: Unlike the development web server, the console does not automatically load
-your code afresh for each line. If you make changes to your models (in your editor)
-while the console is open, type +reload!+ at the console prompt to load them.
+TIP: Rails automatically wraps fields that contain an error with a div
+with class +field_with_errors+. You can define a css rule to make them
+standout.
-h4. Listing All Posts
+Now you'll get a nice error message when saving a post without title when you
+attempt to do just that on the "new post form(http://localhost:3000/posts/new)":http://localhost:3000/posts/new.
-Let's dive into the Rails code a little deeper to see how the application is
-showing us the list of Posts. Open the file
-+app/controllers/posts_controller.rb+ and look at the
-+index+ action:
+!images/getting_started/form_with_errors.png(Form With Errors)!
+
+h4. Updating Posts
+
+We've covered the "CR" part of CRUD. Now let's focus on the "U" part,
+updating posts.
+
+The first step we'll take is adding a +edit+ action to
++posts_controller+.
+
+Start by adding a route to +config/routes.rb+:
<ruby>
-def index
- @posts = Post.all
+get "posts/:id/edit" => "posts#edit"
+</ruby>
+
+And then add the controller action:
- respond_to do |format|
- format.html # index.html.erb
- format.json { render :json => @posts }
+<ruby>
+def edit
+ @post = Post.find(params[:id])
+end
+</ruby>
+
+The view will contain a form similar to the one we used when creating
+new posts. Create a file called +app/views/posts/edit.html.erb+ and make
+it look as follows:
+
+<erb>
+<h1>Editing post</h1>
+
+<%= form_for :post, :url => { :action => :update, :id => @post.id },
+:method => :put do |f| %>
+ <% if @post.errors.any? %>
+ <div id="errorExplanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited
+ this post from being saved:</h2>
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ <p>
+ <%= f.label :title %><br>
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br>
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+This time we point the form to the +update+ action, which is not defined yet
+but will be very soon.
+
+The +:method => :put+ option tells Rails that we want this form to be
+submitted via the +PUT+, HTTP method which is the HTTP method you're expected to use to
+*update* resources according to the REST protocol.
+
+TIP: By default forms built with the +form_for_ helper are sent via +POST+.
+
+Next, we need to add the +update+ action. The file
++config/routes.rb+ will need just one more line:
+
+<ruby>
+put "posts/:id" => "posts#update"
+</ruby>
+
+And then create the +update+ action in +app/controllers/posts_controller.rb+:
+
+<ruby>
+def update
+ @post = Post.find(params[:id])
+
+ if @post.update_attributes(params[:post])
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'edit'
end
end
</ruby>
-+Post.all+ returns all of the posts currently in the database as an array
-of +Post+ records that we store in an instance variable called +@posts+.
+The new method, +update_attributes+, is used when you want to update a record
+that already exists, and it accepts an hash containing the attributes
+that you want to update. As before, if there was an error updating the
+post we want to show the form back to the user.
-TIP: For more information on finding records with Active Record, see "Active
-Record Query Interface":active_record_querying.html.
+TIP: you don't need to pass all attributes to +update_attributes+. For
+example, if you'd call +@post.update_attributes(:title => 'A new title')+
+Rails would only update the +title+ attribute, leaving all other
+attributes untouched.
-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+:
+Finally, we want to show a link to the +edit+ action in the list of all the
+posts, so let's add that now to +app/views/posts/index.html.erb+ to make it
+appear next to the "Show" link:
<erb>
-<h1>Listing posts</h1>
<table>
<tr>
- <th>Name</th>
<th>Title</th>
- <th>Content</th>
- <th></th>
+ <th>Text</th>
<th></th>
<th></th>
</tr>
<% @posts.each do |post| %>
<tr>
- <td><%= post.name %></td>
<td><%= post.title %></td>
- <td><%= post.content %></td>
- <td><%= link_to 'Show', post %></td>
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
- <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?',
- :method => :delete %></td>
+ <td><%= post.text %></td>
+ <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
+ <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
</tr>
<% end %>
</table>
-
-<br />
-
-<%= link_to 'New post', new_post_path %>
</erb>
-This view iterates over the contents of the +@posts+ array to display content
-and links. A few things to note in the view:
-
-* +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 and above, this is now the default. To get unescaped HTML, you now use <tt>&lt;%= raw post.name %&gt;</tt>.
-
-TIP: For more details on the rendering process, see "Layouts and Rendering in
-Rails":layouts_and_rendering.html.
-
-h4. Customizing the Layout
-
-The view is only part of the story of how HTML is displayed in your web browser.
-Rails also has the concept of +layouts+, which are containers for views. When
-Rails renders a view to the browser, it does so by putting the view's HTML into
-a layout's HTML. In previous versions of Rails, the +rails generate scaffold+
-command would automatically create a controller specific layout, like
-+app/views/layouts/posts.html.erb+, for the posts controller. However this has
-been changed in Rails 3. An application specific +layout+ is used for all the
-controllers and can be found in +app/views/layouts/application.html.erb+. Open
-this layout in your editor and modify the +body+ tag to include the style directive
-below:
+And we'll also add one to the +app/views/posts/show.html.erb+ template as well,
+so that there's also an "Edit" link on a post's page. Add this at the bottom of
+the template:
<erb>
-<!DOCTYPE html>
-<html>
-<head>
- <title>Blog</title>
- <%= stylesheet_link_tag "application" %>
- <%= javascript_include_tag "application" %>
- <%= csrf_meta_tags %>
-</head>
-<body style="background-color: #EEEEEE;">
+...
-<%= yield %>
-</body>
-</html>
+<%= link_to 'Back', :action => :index %>
+| <%= link_to 'Edit', :action => :edit, :id => @post.id %>
</erb>
-Now when you refresh the +/posts+ page, you'll see a gray background to the
-page. This same gray background will be used throughout all the views.
+And here's how our app looks so far:
-h4. Creating New Posts
+!images/getting_started/index_action_with_edit_link.png(Index action
+with edit link)!
-Creating a new post involves two actions. The first is the +new+ action, which
-instantiates an empty +Post+ object:
+h4. Using partials to clean up duplication in views
-<ruby>
-def new
- @post = Post.new
++partials+ are what Rails uses to remove duplication in views. Here's a
+simple example:
- respond_to do |format|
- format.html # new.html.erb
- format.json { render :json => @post }
- end
-end
-</ruby>
+<erb>
+# app/views/user/show.html.erb
-The +new.html.erb+ view displays this empty Post to the user:
+<h1><%= @user.name %></h1>
-<erb>
-<h1>New post</h1>
+<%= render 'user_details' %>
-<%= render 'form' %>
+# app/views/user/_user_details.html.erb
+
+<%= @user.location %>
-<%= link_to 'Back', posts_path %>
+<%= @user.about_me %>
</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 the form used to edit a post, both having text fields for the name and
-title, a text area for the content, and a button to create the new post or to update
-the existing one.
+The +users/show+ template will automatically include the content of the
++users/_user_details+ template. Note that partials are prefixed by an underscore,
+as to not be confused with regular views. However, you don't include the
+underscore when including them with the +helper+ method.
-If you take a look at +views/posts/_form.html.erb+ file, you will see the
-following:
+TIP: You can read more about partials in the "Layouts and Rendering in
+Rails":layouts_and_rendering.html guide.
+
+Our +edit+ action looks very similar to the +new+ action, in fact they
+both share the same code for displaying the form. Lets clean them up by
+using a partial.
+
+Create a new file +app/views/posts/_form.html.erb+ with the following
+content:
<erb>
-<%= form_for(@post) do |f| %>
+<%= form_for @post do |f| %>
<% if @post.errors.any? %>
<div id="errorExplanation">
<h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
+ this post from being saved:</h2>
<ul>
<% @post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
@@ -704,230 +976,238 @@ following:
</ul>
</div>
<% end %>
-
- <div class="field">
- <%= f.label :name %><br />
- <%= f.text_field :name %>
- </div>
- <div class="field">
- <%= f.label :title %><br />
+ <p>
+ <%= f.label :title %><br>
<%= f.text_field :title %>
- </div>
- <div class="field">
- <%= f.label :content %><br />
- <%= f.text_area :content %>
- </div>
- <div class="actions">
+ </p>
+
+ <p>
+ <%= f.label :text %><br>
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
</erb>
-This partial receives all the instance variables defined in the calling view
-file. In this case, the controller assigned the new +Post+ object to +@post+,
-which will thus be available in both the view and the partial as +@post+.
+Everything except for the +form_for+ declaration remained the same.
+How +form_for+ can figure out the right +action+ and +method+ attributes
+when building the form will be explained in just a moment. For now, let's update the
++app/views/posts/new.html.erb+ view to use this new partial, rewriting it
+completely:
-For more information on partials, refer to the "Layouts and Rendering in
-Rails":layouts_and_rendering.html#using-partials guide.
+<erb>
+<h1>New post</h1>
-The +form_for+ block is used to create an HTML form. Within this block, you have
-access to methods to build various controls on the form. For example,
-+f.text_field :name+ tells Rails to create a text input on the form and to hook
-it up to the +name+ attribute of the instance being displayed. You can only use
-these methods with attributes of the model that the form is based on (in this
-case +name+, +title+, and +content+). Rails uses +form_for+ in preference to
-having you write raw HTML because the code is more succinct, and because it
-explicitly ties the form to a particular model instance.
+<%= render 'form' %>
-The +form_for+ block is also smart enough to work out if you are doing a _New
-Post_ or an _Edit Post_ action, and will set the form +action+ tags and submit
-button names appropriately in the HTML output.
+<%= link_to 'Back', :action => :index %>
+</erb>
-TIP: If you need to create an HTML form that displays arbitrary fields, not tied
-to a model, you should use the +form_tag+ method, which provides shortcuts for
-building forms that are not necessarily tied to a model instance.
+Then do the same for the +app/views/posts/edit.html.erb+ view:
-When the user clicks the +Create Post+ button on this form, the browser will
-send information back to the +create+ action of the controller (Rails knows to
-call the +create+ action because the form is sent with an HTTP POST request;
-that's one of the conventions that were mentioned earlier):
+<erb>
+<h1>Edit post</h1>
-<ruby>
-def create
- @post = Post.new(params[:post])
+<%= render 'form' %>
- respond_to do |format|
- if @post.save
- format.html { redirect_to(@post,
- :notice => 'Post was successfully created.') }
- format.json { render :json => @post,
- :status => :created, :location => @post }
- else
- format.html { render :action => "new" }
- format.json { render :json => @post.errors,
- :status => :unprocessable_entity }
- end
- end
-end
-</ruby>
+<%= link_to 'Back', :action => :index %>
+</erb>
-The +create+ action instantiates a new Post object from the data supplied by the
-user on the form, which Rails makes available in the +params+ hash. After
-successfully saving the new post, +create+ returns the appropriate format that
-the user has requested (HTML in our case). It then redirects the user to the
-resulting post +show+ action and sets a notice to the user that the Post was
-successfully created.
-
-If the post was not successfully saved, due to a validation error, then the
-controller returns the user back to the +new+ action with any error messages so
-that the user has the chance to fix the error and try again.
-
-The "Post was successfully created." message is stored in the Rails
-+flash+ hash (usually just called _the flash_), so that messages can be carried
-over to another action, providing the user with useful information on the status
-of their request. In the case of +create+, the user never actually sees any page
-rendered during the post creation process, because it immediately redirects to
-the new +Post+ as soon as Rails saves the record. The Flash carries over a message to
-the next action, so that when the user is redirected back to the +show+ action,
-they are presented with a message saying "Post was successfully created."
-
-h4. Showing an Individual Post
-
-When you click the +show+ link for a post on the index page, it will bring you
-to a URL like +http://localhost:3000/posts/1+. Rails interprets this as a call
-to the +show+ action for the resource, and passes in +1+ as the +:id+ parameter.
-Here's the +show+ action:
+Point your browser to "http://localhost:3000/posts/new":http://localhost:3000/posts/new and
+try creating a new post. Everything still works. Now try editing the
+post and you'll receive the following error:
+
+!images/getting_started/undefined_method_post_path.png(Undefined method
+post_path)!
+
+To understand this error, you need to understand how +form_for+ works.
+When you pass an object to +form_for+ and you don't specify a +:url+
+option, Rails will try to guess the +action+ and +method+ options by
+checking if the passed object is a new record or not. Rails follows the
+REST convention, so to create a new +Post+ object it will look for a
+route named +posts_path+, and to update a +Post+ object it will look for
+a route named +post_path+ and pass the current object. Similarly, rails
+knows that it should create new objects via POST and update them via
+PUT.
+
+If you run +rake routes+ from the console you'll see that we already
+have a +posts_path+ route, which was created automatically by Rails when we
+defined the route for the index action.
+However, we don't have a +post_path+ yet, which is the reason why we
+received an error before.
-<ruby>
-def show
- @post = Post.find(params[:id])
+<shell>
+# rake routes
+
+ posts GET /posts(.:format) posts#index
+ posts_new GET /posts/new(.:format) posts#new
+posts_create POST /posts/create(.:format) posts#create
+ GET /posts/:id(.:format) posts#show
+ GET /posts/:id/edit(.:format) posts#edit
+ PUT /posts/:id(.:format) posts#update
+ root / welcome#index
+</shell>
- respond_to do |format|
- format.html # show.html.erb
- format.json { render :json => @post }
- end
-end
-</ruby>
+To fix this, open +config/routes.rb+ and modify the +get "posts/:id"+
+line like this:
-The +show+ action uses +Post.find+ to search for a single record in the database
-by its id value. After finding the record, Rails displays it by using
-+app/views/posts/show.html.erb+:
+<ruby>
+get "posts/:id" => "posts#show", :as => :post
+</ruby>
-<erb>
-<p id="notice"><%= notice %></p>
+The +:as+ option tells the +get+ method that we want to make routing helpers
+called +post_url+ and +post_path+ available to our application. These are
+precisely the methods that the +form_for+ needs when editing a post, and so now
+you'll be able to update posts again.
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
+NOTE: The +:as+ option is available on the +post+, +put+, +delete+ and +match+
+routing methods also.
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
+h4. Deleting Posts
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
+We're now ready to cover the "D" part of CRUD, deleting posts from the
+database. Following the REST convention, we're going to add a route for
+deleting posts to +config/routes.rb+:
+<ruby>
+delete "posts/:id" => "posts#destroy"
+</ruby>
-<%= link_to 'Edit', edit_post_path(@post) %> |
-<%= link_to 'Back', posts_path %>
-</erb>
+The +delete+ routing method should be used for routes that destroy
+resources. If this was left as a typical +get+ route, it could be possible for
+people to craft malicious URLs like this:
-h4. Editing Posts
+<html>
+<a href='http://yoursite.com/posts/1/destroy'>look at this cat!</a>
+</html>
-Like creating a new post, editing a post is a two-part process. The first step
-is a request to +edit_post_path(@post)+ with a particular post. This calls the
-+edit+ action in the controller:
+We use the +delete+ method for destroying resources, and this route is mapped to
+the +destroy+ action inside +app/controllers/posts_controller.rb+, which doesn't exist yet, but is
+provided below:
<ruby>
-def edit
+def destroy
@post = Post.find(params[:id])
+ @post.destroy
+
+ redirect_to :action => :index
end
</ruby>
-After finding the requested post, Rails uses the +edit.html.erb+ view to display
-it:
+You can call +destroy+ on Active Record objects when you want to delete
+them from the database. Note that we don't need to add a view for this
+action since we're redirecting to the +index+ action.
-<erb>
-<h1>Editing post</h1>
+Finally, add a 'destroy' link to your +index+ action template
+(+app/views/posts/index.html.erb) to wrap everything
+together.
-<%= render 'form' %>
+<erb>
+<h1>Listing Posts</h1>
+<table>
+ <tr>
+ <th>Title</th>
+ <th>Text</th>
+ <th></th>
+ <th></th>
+ <th></th>
+ </tr>
-<%= link_to 'Show', @post %> |
-<%= link_to 'Back', posts_path %>
+<% @posts.each do |post| %>
+ <tr>
+ <td><%= post.title %></td>
+ <td><%= post.text %></td>
+ <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
+ <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
+ <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :confirm => 'Are you sure?' %></td>
+ </tr>
+<% end %>
+</table>
</erb>
-Again, as with the +new+ action, the +edit+ action is using the +form+ partial.
-This time, however, the form will do a PUT action to the +PostsController+ and the
-submit button will display "Update Post".
+Here we're using +link_to+ in a different way. We wrap the
++:action+ and +:id+ attributes in a hash so that we can pass those two keys in
+first as one argument, and then the final two keys as another argument. The +:method+ and +:confirm+
+options are used as html5 attributes so that when the click is linked,
+Rails will first show a confirm dialog to the user, and then submit the
+link with method +delete+. This is done via the JavaScript file +jquery_ujs+
+which is automatically included into your application's layout
+(+app/views/layouts/application.html.erb+) when you generated the application.
+Without this file, the confirmation dialog box wouldn't appear.
-Submitting the form created by this view will invoke the +update+ action within
-the controller:
+!images/getting_started/confirm_dialog.png(Confirm Dialog)!
-<ruby>
-def update
- @post = Post.find(params[:id])
+Congratulations, you can now create, show, list, update and destroy
+posts. In the next section will see how Rails can aid us when creating
+REST applications, and how we can refactor our Blog app to take
+advantage of it.
- respond_to do |format|
- if @post.update_attributes(params[:post])
- format.html { redirect_to(@post,
- :notice => 'Post was successfully updated.') }
- format.json { head :no_content }
- else
- format.html { render :action => "edit" }
- format.json { render :json => @post.errors,
- :status => :unprocessable_entity }
- end
- end
-end
-</ruby>
+h4. Going Deeper into REST
-In the +update+ action, Rails first uses the +:id+ parameter passed back from
-the edit view to locate the database record that's being edited. The
-+update_attributes+ call then takes the +post+ parameter (a hash) from the request
-and applies it to this record. If all goes well, the user is redirected to the
-post's +show+ action. If there are any problems, it redirects back to the +edit+ action to
-correct them.
+We've now covered all the CRUD actions of a REST app. We did so by
+declaring separate routes with the appropriate verbs into
++config/routes.rb+. Here's how that file looks so far:
-h4. Destroying a Post
+<ruby>
+get "posts" => "posts#index"
+get "posts/new"
+post "posts/create"
+get "posts/:id" => "posts#show", :as => :post
+get "posts/:id/edit" => "posts#edit"
+put "posts/:id" => "posts#update"
+delete "posts/:id" => "posts#destroy"
+</ruby>
-Finally, clicking one of the +destroy+ links sends the associated id to the
-+destroy+ action:
+That's a lot to type for covering a single *resource*. Fortunately,
+Rails provides a +resources+ method which can be used to declare a
+standard REST resource. Here's how +config/routes/rb+ looks after the
+cleanup:
<ruby>
-def destroy
- @post = Post.find(params[:id])
- @post.destroy
+Blog::Application.routes.draw do
- respond_to do |format|
- format.html { redirect_to posts_url }
- format.json { head :no_content }
- end
+ resources :posts
+
+ root :to => "welcome#index"
end
</ruby>
-The +destroy+ method of an Active Record model instance removes the
-corresponding record from the database. After that's done, there isn't any
-record to display, so Rails redirects the user's browser to the index action of
-the controller.
+If you run +rake routes+, you'll see that all the routes that we
+declared before are still available:
+
+<shell>
+# rake routes
+ posts GET /posts(.:format) posts#index
+ POST /posts(.:format) posts#create
+ new_post GET /posts/new(.:format) posts#new
+edit_post GET /posts/:id/edit(.:format) posts#edit
+ post GET /posts/:id(.:format) posts#show
+ PUT /posts/:id(.:format) posts#update
+ DELETE /posts/:id(.:format) posts#destroy
+ root / welcome#index
+</shell>
+
+Also, if you go through the motions of creating, updating and deleting
+posts the app still works as before.
+
+TIP: In general, Rails encourages the use of resources objects in place
+of declaring routes manually. It was only done in this guide as a learning
+exercise. For more information about routing, see
+"Rails Routing from the Outside In":routing.html.
h3. Adding a Second Model
-Now that you've seen what a model built with scaffolding looks like, it's time to
-add a second model to the application. The second model will handle comments on
-blog posts.
+It's time to add a second model to the application. The second model will handle comments on
+posts.
h4. Generating a Model
-Models in Rails use a singular name, and their corresponding database tables use
-a plural name. For the model to hold comments, the convention is to use the name
-+Comment+. Even if you don't want to use the entire apparatus set up by
-scaffolding, most Rails developers still use generators to make things like
-models and controllers. To create the new model, run this command in your
-terminal:
+We're going to see the same generator that we used before when creating
+the +Post+ model. This time we'll create a +Comment+ model to hold
+reference of post comments. Run this command in your terminal:
<shell>
$ rails generate model Comment commenter:string body:text post:references
@@ -1015,7 +1295,6 @@ You'll need to edit the +post.rb+ file to add the other side of the association:
<ruby>
class Post < ActiveRecord::Base
- validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
@@ -1034,9 +1313,7 @@ h4. Adding a Route for Comments
As with the +welcome+ 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. Near the top, you will see the entry for +posts+
-that was added automatically by the scaffold generator: <tt>resources
-:posts</tt>. Edit it as follows:
++config/routes.rb+ file again, and edit it as follows:
<ruby>
resources :posts do
@@ -1054,7 +1331,7 @@ In":routing.html guide.
h4. Generating a Controller
With the model in hand, you can turn your attention to creating a matching
-controller. Again, there's a generator for this:
+controller. Again, we'll use the same generator we used before:
<shell>
$ rails generate controller Comments
@@ -1081,44 +1358,40 @@ So first, we'll wire up the Post show template
(+/app/views/posts/show.html.erb+) to let us make a new comment:
<erb>
-<p id="notice"><%= notice %></p>
-
<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
+ <strong>Title:</strong>
<%= @post.title %>
</p>
<p>
- <b>Content:</b>
- <%= @post.content %>
+ <strong>Text:</strong>
+ <%= @post.text %>
</p>
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
+ <p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
- </div>
- <div class="field">
+ </p>
+ <p>
<%= f.label :body %><br />
<%= f.text_area :body %>
- </div>
- <div class="actions">
+ </p>
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
This adds a form on the +Post+ show page that creates a new comment by
-calling the +CommentsController+ +create+ action. Let's wire that up:
+calling the +CommentsController+ +create+ action. The +form_for+ call here uses
+an array, which will build a nested route, such as +/posts/1/comments+.
+
+Let's wire up the +create+:
<ruby>
class CommentsController < ApplicationController
@@ -1147,60 +1420,53 @@ template. This is where we want the comment to show, so let's add that to the
+app/views/posts/show.html.erb+.
<erb>
-<p id="notice"><%= notice %></p>
-
<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
+ <strong>Title:</strong>
<%= @post.title %>
</p>
<p>
- <b>Content:</b>
- <%= @post.content %>
+ <strong>Text:</strong>
+ <%= @post.text %>
</p>
<h2>Comments</h2>
<% @post.comments.each do |comment| %>
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
<% end %>
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
+ <p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
- </div>
- <div class="field">
+ </p>
+ <p>
<%= f.label :body %><br />
<%= f.text_area :body %>
- </div>
- <div class="actions">
+ </p>
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
-<br />
-
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
Now you can add posts and comments to your blog and have them show up in the
right places.
+!images/getting_started/post_with_comments.png(Post with Comments)!
+
h3. Refactoring
Now that we have posts and comments working, take a look at the
@@ -1209,18 +1475,18 @@ use partials to clean it up.
h4. Rendering Partial Collections
-First we will make a comment partial to extract showing all the comments for the
+First, we will make a comment partial to extract showing all the comments for the
post. Create the file +app/views/comments/_comment.html.erb+ and put the
following into it:
<erb>
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
</erb>
@@ -1229,21 +1495,14 @@ Then you can change +app/views/posts/show.html.erb+ to look like the
following:
<erb>
-<p id="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
<p>
- <b>Title:</b>
+ <strong>Title:</strong>
<%= @post.title %>
</p>
<p>
- <b>Content:</b>
- <%= @post.content %>
+ <strong>Text:</strong>
+ <%= @post.text %>
</p>
<h2>Comments</h2>
@@ -1251,23 +1510,21 @@ following:
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
+ <p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
- </div>
- <div class="field">
+ </p>
+ <p>
<%= f.label :body %><br />
<%= f.text_area :body %>
- </div>
- <div class="actions">
+ </p>
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
-<br />
-
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
This will now render the partial in +app/views/comments/_comment.html.erb+ once
@@ -1283,50 +1540,38 @@ create a file +app/views/comments/_form.html.erb+ containing:
<erb>
<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
+ <p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
- </div>
- <div class="field">
+ </p>
+ <p>
<%= f.label :body %><br />
<%= f.text_area :body %>
- </div>
- <div class="actions">
+ </p>
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
</erb>
Then you make the +app/views/posts/show.html.erb+ look like the following:
<erb>
-<p id="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
<p>
- <b>Title:</b>
+ <strong>Title:</strong>
<%= @post.title %>
</p>
<p>
- <b>Content:</b>
- <%= @post.content %>
+ <strong>Text:</strong>
+ <%= @post.text %>
</p>
-<h2>Comments</h2>
-<%= render @post.comments %>
-
<h2>Add a comment:</h2>
<%= render "comments/form" %>
-<br />
-
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
The second render just defines the partial template we want to render,
@@ -1348,12 +1593,12 @@ So first, let's add the delete link in the
<erb>
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
@@ -1402,7 +1647,6 @@ model, +app/models/post.rb+, as follows:
<ruby>
class Post < ActiveRecord::Base
- validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
has_many :comments, :dependent => :destroy
@@ -1431,11 +1675,8 @@ class PostsController < ApplicationController
http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
- # GET /posts
- # GET /posts.json
def index
@posts = Post.all
- respond_to do |format|
# snipped for brevity
</ruby>
@@ -1457,213 +1698,6 @@ Authentication challenge
!images/challenge.png(Basic HTTP Authentication Challenge)!
-h3. Building a Multi-Model Form
-
-Another feature of your average blog is the ability to tag posts. To implement
-this feature your application needs to interact with more than one model on a
-single form. Rails offers support for nested forms.
-
-To demonstrate this, we will add support for giving each post multiple tags,
-right in the form where you create the post. First, create a new model to hold
-the tags:
-
-<shell>
-$ rails generate model Tag name:string post:references
-</shell>
-
-Again, run the migration to create the database table:
-
-<shell>
-$ rake db:migrate
-</shell>
-
-Next, edit the +post.rb+ file to create the other side of the association, and
-to tell Rails (via the +accepts_nested_attributes_for+ macro) that you intend to
-edit tags via posts:
-
-<ruby>
-class Post < ActiveRecord::Base
- validates :name, :presence => true
- validates :title, :presence => true,
- :length => { :minimum => 5 }
-
- has_many :comments, :dependent => :destroy
- has_many :tags
-
- accepts_nested_attributes_for :tags, :allow_destroy => :true,
- :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
-end
-</ruby>
-
-The +:allow_destroy+ option tells Rails to enable destroying tags through the
-nested attributes (you'll handle that by displaying a "remove" checkbox on the
-view that you'll build shortly). The +:reject_if+ option prevents saving new
-tags that do not have any attributes filled in.
-
-We will modify +views/posts/_form.html.erb+ to render a partial to make a tag:
-
-<erb>
-<% @post.tags.build %>
-<%= form_for(@post) do |post_form| %>
- <% if @post.errors.any? %>
- <div id="errorExplanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
- <div class="field">
- <%= post_form.label :name %><br />
- <%= post_form.text_field :name %>
- </div>
- <div class="field">
- <%= post_form.label :title %><br />
- <%= post_form.text_field :title %>
- </div>
- <div class="field">
- <%= post_form.label :content %><br />
- <%= post_form.text_area :content %>
- </div>
- <h2>Tags</h2>
- <%= render :partial => 'tags/form',
- :locals => {:form => post_form} %>
- <div class="actions">
- <%= post_form.submit %>
- </div>
-<% end %>
-</erb>
-
-Note that we have changed the +f+ in +form_for(@post) do |f|+ to +post_form+ to
-make it easier to understand what is going on.
-
-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 its 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:
-
-<erb>
-<%= form.fields_for :tags do |tag_form| %>
- <div class="field">
- <%= tag_form.label :name, 'Tag:' %>
- <%= tag_form.text_field :name %>
- </div>
- <% unless tag_form.object.nil? || tag_form.object.new_record? %>
- <div class="field">
- <%= tag_form.label :_destroy, 'Remove:' %>
- <%= tag_form.check_box :_destroy %>
- </div>
- <% end %>
-<% end %>
-</erb>
-
-Finally, we will edit the <tt>app/views/posts/show.html.erb</tt> template to
-show our tags.
-
-<erb>
-<p id="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<p>
- <b>Tags:</b>
- <%= @post.tags.map { |t| t.name }.join(", ") %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-With these changes in place, you'll find that you can edit a post and its tags
-directly on the same view.
-
-However, that method call <tt>@post.tags.map { |t| t.name }.join(", ")</tt> is
-awkward, we could handle this by making a helper method.
-
-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.
-
-Open up <tt>app/helpers/posts_helper.rb</tt> and add the following:
-
-<erb>
-module PostsHelper
- def join_tags(post)
- post.tags.map { |t| t.name }.join(", ")
- end
-end
-</erb>
-
-Now you can edit the view in <tt>app/views/posts/show.html.erb</tt> to look like
-this:
-
-<erb>
-<p id="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<p>
- <b>Tags:</b>
- <%= join_tags(@post) %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
h3. What's Next?
Now that you've seen your first Rails application, you should feel free to
diff --git a/guides/source/i18n.textile b/guides/source/i18n.textile
index 320f1e9d20..ee7176a6c8 100644
--- a/guides/source/i18n.textile
+++ b/guides/source/i18n.textile
@@ -127,7 +127,7 @@ If you want to translate your Rails application to a *single language other than
However, you would probably like to *provide support for more locales* in your application. In such case, you need to set and pass the locale between requests.
-WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>. *Do not do so*. The locale should be transparent and a part of the URL. This way you don't break people's basic assumptions about the web itself: if you send a URL of some page to a friend, she should see the same page, same content. A fancy word for this would be that you're being "<em>RESTful</em>":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. There may be some exceptions to this rule, which are discussed below.
+WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, however *do not do this*. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being "<em>RESTful</em>":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. Sometimes there are exceptions to this rule and those are discussed below.
The _setting part_ is easy. You can set the locale in a +before_filter+ in the +ApplicationController+ like this:
@@ -220,7 +220,7 @@ Every helper method dependent on +url_for+ (e.g. helpers for named routes like +
You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this.
-You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the Netherlands locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+path_prefix+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way:
+You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the Dutch locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+path_prefix+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way:
<ruby>
# config/routes.rb
@@ -229,7 +229,7 @@ scope "/:locale" do
end
</ruby>
-Now, when you call the +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed).
+Now, when you call the +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Dutch locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed).
If you don't want to force the use of a locale in your routes you can use an optional path scope (denoted by the parentheses) like so:
@@ -866,19 +866,35 @@ The I18n API will catch all of these exceptions when they are thrown in the back
The reason for this is that during development you'd usually want your views to still render even though a translation is missing.
-In other contexts you might want to change this behaviour, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module:
+In other contexts you might want to change this behaviour, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module or a class with +#call+ method:
<ruby>
module I18n
- def self.just_raise_that_exception(*args)
- raise args.first
+ class JustRaiseExceptionHandler < ExceptionHandler
+ def call(exception, locale, key, options)
+ if exception.is_a?(MissingTranslation)
+ raise exception.to_exception
+ else
+ super
+ end
+ end
end
end
-I18n.exception_handler = :just_raise_that_exception
+I18n.exception_handler = I18n::JustRaiseExceptionHandler.new
</ruby>
-This would re-raise all caught exceptions including +MissingTranslationData+.
+This would re-raise only the +MissingTranslationData+ exception, passing all other input to the default exception handler.
+
+However, if you are using +I18n::Backend::Pluralization+ this handler will also raise +I18n::MissingTranslationData: translation missing: en.i18n.plural.rule+ exception that should normally be ignored to fall back to the default pluralization rule for English locale. To avoid this you may use additional check for translation key:
+
+<ruby>
+if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
+ raise exception.to_exception
+else
+ super
+end
+</ruby>
Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method +#t+ (as well as +#translate+). When a +MissingTranslationData+ exception occurs in this context, the helper wraps the message into a span with the CSS class +translation_missing+.
diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb
index 5439459b42..74805b2754 100644
--- a/guides/source/index.html.erb
+++ b/guides/source/index.html.erb
@@ -13,7 +13,7 @@ Ruby on Rails Guides
and <%= link_to 'Free Kindle Reading Apps', 'http://www.amazon.com/gp/kindle/kcp' %> for the iPad,
iPhone, Mac, Android, etc. Download them from <%= link_to 'here', @mobi %>.
</dd>
- <dd class="work-in-progress">Guides marked with this icon are currently being worked on. While they might still be useful to you, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections to the author.</dd>
+ <dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd>
</dl>
</div>
<% end %>
diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile
index 69e5c1edcc..155a439e64 100644
--- a/guides/source/initialization.textile
+++ b/guides/source/initialization.textile
@@ -137,8 +137,6 @@ h4. +config/boot.rb+
+config/boot.rb+ contains this:
<ruby>
-require 'rubygems'
-
# Set up gems listed in the Gemfile.
gemfile = File.expand_path('../../Gemfile', __FILE__)
begin
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 35b6fc7014..0a8daf7ae5 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -14,6 +14,8 @@
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
+
+<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
<% if @edge %>
diff --git a/guides/source/layouts_and_rendering.textile b/guides/source/layouts_and_rendering.textile
index 4b4f9f3745..b0a87a5981 100644
--- a/guides/source/layouts_and_rendering.textile
+++ b/guides/source/layouts_and_rendering.textile
@@ -78,16 +78,16 @@ If we want to display the properties of all the books in our view, we can do so
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
- <td><%= link_to 'Show', book %></td>
- <td><%= link_to 'Edit', edit_book_path(book) %></td>
- <td><%= link_to 'Remove', book, :confirm => 'Are you sure?', :method => :delete %></td>
+ <td><%= link_to "Show", book %></td>
+ <td><%= link_to "Edit", edit_book_path(book) %></td>
+ <td><%= link_to "Remove", book, :confirm => "Are you sure?", :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
-<%= link_to 'New book', new_book_path %>
+<%= 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. Beginning with Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), and +.builder+ for Builder (XML generator).
@@ -177,13 +177,13 @@ h5. Rendering an Action's Template from Another Controller
What if you want to render a template from an entirely different controller from the one that contains the action code? You can also do that with +render+, which accepts the full path (relative to +app/views+) of the template to render. For example, if you're running code in an +AdminProductsController+ that lives in +app/controllers/admin+, you can render the results of an action to a template in +app/views/products+ this way:
<ruby>
-render 'products/show'
+render "products/show"
</ruby>
Rails knows that this view belongs to a different controller because of the embedded slash character in the string. If you want to be explicit, you can use the +:template+ option (which was required on Rails 2.2 and earlier):
<ruby>
-render :template => 'products/show'
+render :template => "products/show"
</ruby>
h5. Rendering an Arbitrary File
@@ -216,18 +216,18 @@ In fact, in the BooksController class, inside of the update action where we want
<ruby>
render :edit
render :action => :edit
-render 'edit'
-render 'edit.html.erb'
-render :action => 'edit'
-render :action => 'edit.html.erb'
-render 'books/edit'
-render 'books/edit.html.erb'
-render :template => 'books/edit'
-render :template => 'books/edit.html.erb'
-render '/path/to/rails/app/views/books/edit'
-render '/path/to/rails/app/views/books/edit.html.erb'
-render :file => '/path/to/rails/app/views/books/edit'
-render :file => '/path/to/rails/app/views/books/edit.html.erb'
+render "edit"
+render "edit.html.erb"
+render :action => "edit"
+render :action => "edit.html.erb"
+render "books/edit"
+render "books/edit.html.erb"
+render :template => "books/edit"
+render :template => "books/edit.html.erb"
+render "/path/to/rails/app/views/books/edit"
+render "/path/to/rails/app/views/books/edit.html.erb"
+render :file => "/path/to/rails/app/views/books/edit"
+render :file => "/path/to/rails/app/views/books/edit.html.erb"
</ruby>
Which one you use is really a matter of style and convention, but the rule of thumb is to use the simplest one that makes sense for the code you are writing.
@@ -306,7 +306,7 @@ h6. The +:content_type+ Option
By default, Rails will serve the results of a rendering operation with the MIME content-type of +text/html+ (or +application/json+ if you use the +:json+ option, or +application/xml+ for the +:xml+ option.). There are times when you might like to change this, and you can do so by setting the +:content_type+ option:
<ruby>
-render :file => filename, :content_type => 'application/rss'
+render :file => filename, :content_type => "application/rss"
</ruby>
h6. The +:layout+ Option
@@ -316,7 +316,7 @@ With most of the options to +render+, the rendered content is displayed as part
You can use the +:layout+ option to tell Rails to use a specific file as the layout for the current action:
<ruby>
-render :layout => 'special_layout'
+render :layout => "special_layout"
</ruby>
You can also tell Rails to render with no layout at all:
@@ -378,7 +378,7 @@ You can use a symbol to defer the choice of layout until a request is processed:
<ruby>
class ProductsController < ApplicationController
- layout :products_layout
+ layout "products_layout"
def show
@product = Product.find(params[:id])
@@ -398,7 +398,7 @@ You can even use an inline method, such as a Proc, to determine the layout. For
<ruby>
class ProductsController < ApplicationController
- layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'application' }
+ layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
</ruby>
@@ -445,7 +445,7 @@ end
<ruby>
class OldPostsController < SpecialPostsController
- layout nil
+ layout false
def show
@post = Post.find(params[:id])
@@ -583,7 +583,7 @@ def show
@book = Book.find_by_id(params[:id])
if @book.nil?
@books = Book.all
- render "index", :alert => 'Your book was not found!'
+ render "index", :alert => "Your book was not found!"
end
end
</ruby>
@@ -686,7 +686,7 @@ You can specify a full path relative to the document root, or a URL, if you pref
Rails will then output a +script+ tag such as this:
<html>
-<script src='/assets/main.js' type="text/javascript"></script>
+<script src='/assets/main.js'></script>
</html>
The request to this asset is then served by the Sprockets gem.
@@ -718,8 +718,8 @@ If the application does not use the asset pipeline, the +:defaults+ option loads
Outputting +script+ tags such as this:
<html>
-<script src="/javascripts/jquery.js" type="text/javascript"></script>
-<script src="/javascripts/jquery_ujs.js" type="text/javascript"></script>
+<script src="/javascripts/jquery.js"></script>
+<script src="/javascripts/jquery_ujs.js"></script>
</html>
These two files for jQuery, +jquery.js+ and +jquery_ujs.js+ must be placed inside +public/javascripts+ if the application doesn't use the asset pipeline. These files can be downloaded from the "jquery-rails repository on GitHub":https://github.com/indirect/jquery-rails/tree/master/vendor/assets/javascripts
@@ -770,7 +770,7 @@ By default, the combined file will be delivered as +javascripts/all.js+. You can
<erb>
<%= javascript_include_tag "main", "columns",
- :cache => 'cache/main/display' %>
+ :cache => "cache/main/display" %>
</erb>
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
@@ -805,7 +805,7 @@ To include +http://example.com/main.css+:
<%= stylesheet_link_tag "http://example.com/main.css" %>
</erb>
-By default, the +stylesheet_link_tag+ creates links with +media="screen" rel="stylesheet" type="text/css"+. You can override any of these defaults by specifying an appropriate option (+:media+, +:rel+, or +:type+):
+By default, the +stylesheet_link_tag+ creates links with +media="screen" rel="stylesheet"+. You can override any of these defaults by specifying an appropriate option (+:media+, +:rel+):
<erb>
<%= stylesheet_link_tag "main_print", :media => "print" %>
@@ -833,7 +833,7 @@ By default, the combined file will be delivered as +stylesheets/all.css+. You ca
<erb>
<%= stylesheet_link_tag "main", "columns",
- :cache => 'cache/main/display' %>
+ :cache => "cache/main/display" %>
</erb>
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
@@ -860,12 +860,6 @@ You can supply a hash of additional HTML options:
<%= image_tag "icons/delete.gif", {:height => 45} %>
</erb>
-You can also supply an alternate image to show on mouseover:
-
-<erb>
-<%= image_tag "home.gif", :onmouseover => "menu/home_highlight.gif" %>
-</erb>
-
You can supply alternate text for the image which will be used if the user has images turned off in their browser. If you do not specify an alt text explicitly, it defaults to the file name of the file, capitalized and with no extension. For example, these two image tags would return the same code:
<erb>
@@ -884,7 +878,7 @@ In addition to the above special tags, you can supply a final hash of standard H
<erb>
<%= image_tag "home.gif", :alt => "Go Home",
:id => "HomeImage",
- :class => 'nav_bar' %>
+ :class => "nav_bar" %>
</erb>
h5. Linking to Videos with the +video_tag+
@@ -905,7 +899,7 @@ Like an +image_tag+ you can supply a path, either absolute, or relative to the +
The video tag also supports all of the +&lt;video&gt;+ HTML options through the HTML options hash, including:
-* +:poster => 'image_name.png'+, provides an image to put in place of the video before it starts playing.
+* +:poster => "image_name.png"+, provides an image to put in place of the video before it starts playing.
* +:autoplay => true+, starts playing the video on page load.
* +:loop => true+, loops the video once it gets to the end.
* +:controls => true+, provides browser supplied controls for the user to interact with the video.
@@ -1134,13 +1128,6 @@ In Rails 3.0, there is also a shorthand for this. Assuming +@products+ is a coll
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:
-In the event that the collection is empty, +render+ will return nil, so it should be fairly simple to provide alternative content.
-
-<erb>
-<h1>Products</h1>
-<%= render(@products) || 'There are no products available.' %>
-</erb>
-
* +index.html.erb+
<erb>
@@ -1162,6 +1149,13 @@ In the event that the collection is empty, +render+ will return nil, so it shoul
In this case, Rails will use the customer or employee partials as appropriate for each member of the collection.
+In the event that the collection is empty, +render+ will return nil, so it should be fairly simple to provide alternative content.
+
+<erb>
+<h1>Products</h1>
+<%= render(@products) || "There are no products available." %>
+</erb>
+
h5. Local Variables
To use a custom local variable name within the partial, specify the +:as+ option in the call to the partial:
@@ -1175,7 +1169,7 @@ With this change, you can access an instance of the +@products+ collection as th
You can also pass in arbitrary local variables to any partial you are rendering with the +:locals => {}+ option:
<erb>
-<%= render :partial => 'products', :collection => @products,
+<%= render :partial => "products", :collection => @products,
:as => :item, :locals => {:title => "Products Page"} %>
</erb>
@@ -1193,6 +1187,16 @@ h5. Spacer Templates
Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials.
+h5. Partial Layouts
+
+When rendering collections it is also possible to use the +:layout+ option:
+
+<erb>
+<%= render :partial => "product", :collection => @products, :layout => "special_layout" %>
+</erb>
+
+The layout will be rendered together with the partial for each item in the collection. The current object and object_counter variables will be available in the layout as well, the same way they do within the partial.
+
h4. Using Nested Layouts
You may find that your application requires a layout that differs slightly from your regular application layout to support one particular controller. Rather than repeating the main layout and editing it, you can accomplish this by using nested layouts (sometimes called sub-templates). Here's an example:
@@ -1204,9 +1208,9 @@ Suppose you have the following +ApplicationController+ layout:
<erb>
<html>
<head>
- <title><%= @page_title or 'Page Title' %></title>
- <%= stylesheet_link_tag 'layout' %>
- <style type="text/css"><%= yield :stylesheets %></style>
+ <title><%= @page_title or "Page Title" %></title>
+ <%= stylesheet_link_tag "layout" %>
+ <style><%= yield :stylesheets %></style>
</head>
<body>
<div id="top_menu">Top menu items here</div>
@@ -1229,7 +1233,7 @@ On pages generated by +NewsController+, you want to hide the top menu and add a
<div id="right_menu">Right menu items here</div>
<%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
-<%= render :template => 'layouts/application' %>
+<%= render :template => "layouts/application" %>
</erb>
That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div.
diff --git a/guides/source/migrations.textile b/guides/source/migrations.textile
index c11f8e221b..52dba76e68 100644
--- a/guides/source/migrations.textile
+++ b/guides/source/migrations.textile
@@ -51,7 +51,7 @@ end
This migration adds a table called +products+ with a string column called +name+
and a text column called +description+. A primary key column called +id+ will
-also be added, however since this is the default we do not need to ask for this.
+also be added, however since this is the default we do not need to explicitly specify it.
The timestamp columns +created_at+ and +updated_at+ which Active Record
populates automatically will also be added. Reversing this migration is as
simple as dropping the table.
@@ -65,7 +65,7 @@ class AddReceiveNewsletterToUsers < ActiveRecord::Migration
change_table :users do |t|
t.boolean :receive_newsletter, :default => false
end
- User.update_all ["receive_newsletter = ?", true]
+ User.update_all :receive_newsletter => true
end
def down
@@ -82,6 +82,8 @@ it to default to +false+ for new users, but existing users are considered to
have already opted in, so we use the User model to set the flag to +true+ for
existing users.
+h4. Using the change method
+
Rails 3.1 makes migrations smarter by providing a new <tt>change</tt> method.
This method is preferred for writing constructive migrations (adding columns or
tables). The migration knows how to migrate your database and reverse it when
@@ -475,7 +477,16 @@ end
</ruby>
will add an +attachment_id+ column and a string +attachment_type+ column with
-a default value of 'Photo'.
+a default value of 'Photo'. +references+ also allows you to define an
+index directly, instead of using +add_index+ after the +create_table+ call:
+
+<ruby>
+create_table :products do |t|
+ t.references :category, :index => true
+end
+</ruby>
+
+will create an index identical to calling `add_index :products, :category_id`.
NOTE: The +references+ helper does not actually create foreign key constraints
for you. You will need to use +execute+ or a plugin that adds "foreign key
@@ -497,7 +508,7 @@ and
h4. Using the +change+ Method
The +change+ method removes the need to write both +up+ and +down+ methods in
-those cases that Rails know how to revert the changes automatically. Currently,
+those cases that Rails knows how to revert the changes automatically. Currently,
the +change+ method supports only these migration definitions:
* +add_column+
@@ -635,10 +646,9 @@ example,
$ rake db:migrate:up VERSION=20080906120000
</shell>
-will run the +up+ method from the 20080906120000 migration. These tasks still
-check whether the migration has already run, so for example +db:migrate:up
-VERSION=20080906120000+ will do nothing if Active Record believes that
-20080906120000 has already been run.
+will run the +up+ method from the 20080906120000 migration. This task will first
+check whether the migration is already performed and will do nothing if Active Record believes
+that it has already been run.
h4. Changing the output of running migrations
@@ -727,9 +737,7 @@ column.
class AddFlagToProduct < ActiveRecord::Migration
def change
add_column :products, :flag, :boolean
- Product.all.each do |product|
- product.update_attributes!(:flag => 'false')
- end
+ Product.update_all :flag => false
end
end
</ruby>
@@ -752,9 +760,7 @@ column.
class AddFuzzToProduct < ActiveRecord::Migration
def change
add_column :products, :fuzz, :string
- Product.all.each do |product|
- product.update_attributes! :fuzz => 'fuzzy'
- end
+ Product.update_all :fuzz => 'fuzzy'
end
end
</ruby>
@@ -771,7 +777,7 @@ Both migrations work for Alice.
Bob comes back from vacation and:
-# Updates the source - which contains both migrations and the latests version of
+# Updates the source - which contains both migrations and the latest version of
the Product model.
# Runs outstanding migrations with +rake db:migrate+, which
includes the one that updates the +Product+ model.
@@ -804,11 +810,9 @@ class AddFlagToProduct < ActiveRecord::Migration
end
def change
- add_column :products, :flag, :integer
+ add_column :products, :flag, :boolean
Product.reset_column_information
- Product.all.each do |product|
- product.update_attributes!(:flag => false)
- end
+ Product.update_all :flag => false
end
end
</ruby>
@@ -823,9 +827,7 @@ class AddFuzzToProduct < ActiveRecord::Migration
def change
add_column :products, :fuzz, :string
Product.reset_column_information
- Product.all.each do |product|
- product.update_attributes!(:fuzz => 'fuzzy')
- end
+ Product.update_all :fuzz => 'fuzzy'
end
end
</ruby>
diff --git a/guides/source/plugins.textile b/guides/source/plugins.textile
index 97b4eca779..95e38db483 100644
--- a/guides/source/plugins.textile
+++ b/guides/source/plugins.textile
@@ -25,16 +25,14 @@ endprologue.
h3. Setup
-Before you continue, take a moment to decide if your new plugin will be potentially shared across different Rails applications.
+_"vendored plugins"_ were available in previous versions of Rails, but they are deprecated in
+Rails 3.2, and will not be available in the future.
-* If your plugin is specific to your application, your new plugin will be a _vendored plugin_.
-* If you think your plugin may be used across applications, build it as a _gemified plugin_.
+Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared accross
+different rails applications using RubyGems and Bundler if desired.
h4. Generate a gemified plugin.
-Writing your Rails plugin as a gem, rather than as a vendored plugin,
- lets you share your plugin across different rails applications using
- RubyGems and Bundler.
Rails 3.1 ships with a +rails plugin new+ command which creates a
skeleton for developing any kind of Rails extension with the ability
diff --git a/guides/source/rails_on_rack.textile b/guides/source/rails_on_rack.textile
index 9526526bc7..ff862273fd 100644
--- a/guides/source/rails_on_rack.textile
+++ b/guides/source/rails_on_rack.textile
@@ -91,13 +91,15 @@ For a freshly generated Rails application, this might produce something like:
<ruby>
use ActionDispatch::Static
use Rack::Lock
-use ActiveSupport::Cache::Strategy::LocalCache
+use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838>
use Rack::Runtime
+use Rack::MethodOverride
+use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
-use Rack::Sendfile
+use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
@@ -105,8 +107,9 @@ use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
-use Rack::MethodOverride
use ActionDispatch::Head
+use Rack::ConditionalGet
+use Rack::ETag
use ActionDispatch::BestStandardsSupport
run Blog::Application.routes
</ruby>
@@ -145,62 +148,104 @@ You can swap an existing middleware in the middleware stack using +config.middle
<ruby>
# config/application.rb
-# Replace ActionController::Failsafe with Lifo::Failsafe
-config.middleware.swap ActionController::Failsafe, Lifo::Failsafe
+# Replace ActionDispatch::ShowExceptions with Lifo::ShowExceptions
+config.middleware.swap ActionDispatch::ShowExceptions, Lifo::ShowExceptions
</ruby>
h5. Middleware Stack is an Array
The middleware stack behaves just like a normal +Array+. You can use any +Array+ methods to insert, reorder, or remove items from the stack. Methods described in the section above are just convenience methods.
-For example, the following removes the middleware matching the supplied class name:
+Append following lines to your application configuration:
<ruby>
-config.middleware.delete(middleware)
+# config/application.rb
+config.middleware.delete "Rack::Lock"
</ruby>
+And now if you inspect the middleware stack, you'll find that +Rack::Lock+ will not be part of it.
+
+<shell>
+$ rake middleware
+(in /Users/lifo/Rails/blog)
+use ActionDispatch::Static
+use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
+use Rack::Runtime
+...
+run Myapp::Application.routes
+</shell>
+
h4. Internal Middleware Stack
-Much of Action Controller's functionality is implemented as Middlewares. The following table explains the purpose of each of them:
+Much of Action Controller's functionality is implemented as Middlewares. The following list explains the purpose of each of them:
-|_.Middleware|_.Purpose|
-|+Rack::Lock+|Sets <tt>env["rack.multithread"]</tt> flag to +true+ and wraps the application within a Mutex.|
-|+ActionController::Failsafe+|Returns HTTP Status +500+ to the client if an exception gets raised while dispatching.|
-|+ActiveRecord::QueryCache+|Enables the Active Record query cache.|
-|+ActionDispatch::Session::CookieStore+|Uses the cookie based session store.|
-|+ActionDispatch::Session::CacheStore+|Uses the Rails cache based session store.|
-|+ActionDispatch::Session::MemCacheStore+|Uses the memcached based session store.|
-|+ActiveRecord::SessionStore+|Uses the database based session store.|
-|+Rack::MethodOverride+|Sets HTTP method based on +_method+ parameter or <tt>env["HTTP_X_HTTP_METHOD_OVERRIDE"]</tt>.|
-|+Rack::Head+|Discards the response body if the client sends a +HEAD+ request.|
+ *+ActionDispatch::Static+*
+* Used to serve static assets. Disabled if <tt>config.serve_static_assets</tt> is true.
-TIP: It's possible to use any of the above middlewares in your custom Rack stack.
+ *+Rack::Lock+*
+* Sets <tt>env["rack.multithread"]</tt> flag to +true+ and wraps the application within a Mutex.
-h4. Customizing Internal Middleware Stack
+ *+ActiveSupport::Cache::Strategy::LocalCache::Middleware+*
+* Used for memory caching. This cache is not thread safe.
-It's possible to replace the entire middleware stack with a custom stack using <tt>ActionController::Dispatcher.middleware=</tt>.
+ *+Rack::Runtime+*
+* Sets an X-Runtime header, containing the time (in seconds) taken to execute the request.
-Put the following in an initializer:
+ *+Rack::MethodOverride+*
+* Allows the method to be overridden if <tt>params[:_method]</tt> is set. This is the middleware which supports the PUT and DELETE HTTP method types.
-<ruby>
-# config/initializers/stack.rb
-ActionController::Dispatcher.middleware = ActionController::MiddlewareStack.new do |m|
- m.use ActionController::Failsafe
- m.use ActiveRecord::QueryCache
- m.use Rack::Head
-end
-</ruby>
+ *+ActionDispatch::RequestId+*
+* Makes a unique +X-Request-Id+ header available to the response and enables the <tt>ActionDispatch::Request#uuid</tt> method.
-And now inspecting the middleware stack:
+ *+Rails::Rack::Logger+*
+* Notifies the logs that the request has began. After request is complete, flushes all the logs.
-<shell>
-$ rake middleware
-(in /Users/lifo/Rails/blog)
-use ActionController::Failsafe
-use ActiveRecord::QueryCache
-use Rack::Head
-run ActionController::Dispatcher.new
-</shell>
+ *+ActionDispatch::ShowExceptions+*
+* Rescues any exception returned by the application and calls an exceptions app that will wrap it in a format for the end user.
+
+ *+ActionDispatch::DebugExceptions+*
+* Responsible for logging exceptions and showing a debugging page in case the request is local.
+
+ *+ActionDispatch::RemoteIp+*
+* Checks for IP spoofing attacks.
+
+ *+ActionDispatch::Reloader+*
+* Provides prepare and cleanup callbacks, intended to assist with code reloading during development.
+
+ *+ActionDispatch::Callbacks+*
+* Runs the prepare callbacks before serving the request.
+
+ *+ActiveRecord::ConnectionAdapters::ConnectionManagement+*
+* Cleans active connections after each request, unless the <tt>rack.test</tt> key in the request environment is set to +true+.
+
+ *+ActiveRecord::QueryCache+*
+* Enables the Active Record query cache.
+
+ *+ActionDispatch::Cookies+*
+* Sets cookies for the request.
+
+ *+ActionDispatch::Session::CookieStore+*
+* Responsible for storing the session in cookies.
+
+ *+ActionDispatch::Flash+*
+* Sets up the flash keys. Only available if <tt>config.action_controller.session_store</tt> is set to a value.
+
+ *+ActionDispatch::ParamsParser+*
+* Parses out parameters from the request into <tt>params</tt>.
+
+ *+ActionDispatch::Head+*
+* Converts HEAD requests to +GET+ requests and serves them as so.
+
+ *+Rack::ConditionalGet+*
+* Adds support for "Conditional +GET+" so that server responds with nothing if page wasn't changed.
+
+ *+Rack::ETag+*
+* Adds ETag header on all String bodies. ETags are used to validate cache.
+
+ *+ActionDispatch::BestStandardsSupport+*
+* Enables “best standards support” so that IE8 renders some elements correctly.
+
+TIP: It's possible to use any of the above middlewares in your custom Rack stack.
h4. Using Rack Builder
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index e93b1280e0..4a50edbb15 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -25,7 +25,7 @@ GET /patients/17
it asks the router to match it to a controller action. If the first matching route is
<ruby>
-match "/patients/:id" => "patients#show"
+get "/patients/:id" => "patients#show"
</ruby>
the request is dispatched to the +patients+ controller's +show+ action with <tt>{ :id => "17" }</tt> in +params+.
@@ -121,7 +121,7 @@ h4. Singular Resources
Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like +/profile+ to always show the profile of the currently logged in user. In this case, you can use a singular resource to map +/profile+ (rather than +/profile/:id+) to the +show+ action.
<ruby>
-match "profile" => "users#show"
+get "profile" => "users#show"
</ruby>
This resourceful route
@@ -374,7 +374,7 @@ h4. Bound Parameters
When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: +:controller+ maps to the name of a controller in your application, and +:action+ maps to the name of an action within that controller. For example, consider one of the default Rails routes:
<ruby>
-match ':controller(/:action(/:id))'
+get ':controller(/:action(/:id))'
</ruby>
If an incoming request of +/photos/show/1+ is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the +show+ action of the +PhotosController+, and to make the final parameter +"1"+ available as +params[:id]+. This route will also route the incoming request of +/photos+ to +PhotosController#index+, since +:action+ and +:id+ are optional parameters, denoted by parentheses.
@@ -384,7 +384,7 @@ h4. Dynamic Segments
You can set up as many dynamic segments within a regular route as you like. Anything other than +:controller+ or +:action+ will be available to the action as part of +params+. If you set up this route:
<ruby>
-match ':controller/:action/:id/:user_id'
+get ':controller/:action/:id/:user_id'
</ruby>
An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
@@ -392,7 +392,7 @@ An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action o
NOTE: You can't use +:namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
<ruby>
-match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
+get ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
</ruby>
TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash.
@@ -402,7 +402,7 @@ h4. Static Segments
You can specify static segments when creating a route:
<ruby>
-match ':controller/:action/:id/with_user/:user_id'
+get ':controller/:action/:id/with_user/:user_id'
</ruby>
This route would respond to paths such as +/photos/show/1/with_user/2+. In this case, +params+ would be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
@@ -412,7 +412,7 @@ h4. The Query String
The +params+ will also include any parameters from the query string. For example, with this route:
<ruby>
-match ':controller/:action/:id'
+get ':controller/:action/:id'
</ruby>
An incoming path of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
@@ -422,7 +422,7 @@ h4. Defining Defaults
You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply them as defaults:
<ruby>
-match 'photos/:id' => 'photos#show'
+get 'photos/:id' => 'photos#show'
</ruby>
With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+.
@@ -430,7 +430,7 @@ With this route, Rails will match an incoming path of +/photos/12+ to the +show+
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:
<ruby>
-match 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
+get 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
</ruby>
Rails would match +photos/12+ to the +show+ action of +PhotosController+, and set +params[:format]+ to +"jpg"+.
@@ -440,49 +440,45 @@ h4. Naming Routes
You can specify a name for any route using the +:as+ option.
<ruby>
-match 'exit' => 'sessions#destroy', :as => :logout
+get 'exit' => 'sessions#destroy', :as => :logout
</ruby>
This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/exit+
h4. HTTP Verb Constraints
-You can use the +:via+ option to constrain the request to one or more HTTP methods:
+In general, you should use the +get+, +post+, +put+ and +delete+ methods to constrain a route to a particular verb. You can use the +match+ method with the +:via+ option to match multiple verbs at once:
<ruby>
-match 'photos/show' => 'photos#show', :via => :get
+match 'photos' => 'photos#show', :via => [:get, :post]
</ruby>
-There is a shorthand version of this as well:
+You can match all verbs to a particular route using +:via => :all+:
<ruby>
-get 'photos/show'
+match 'photos' => 'photos#show', :via => :all
</ruby>
-You can also permit more than one verb to a single route:
-
-<ruby>
-match 'photos/show' => 'photos#show', :via => [:get, :post]
-</ruby>
+You should avoid routing all verbs to an action unless you have a good reason to, as routing both +GET+ requests and +POST+ requests to a single action has security implications.
h4. Segment Constraints
You can use the +:constraints+ option to enforce a format for a dynamic segment:
<ruby>
-match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
+get 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
</ruby>
This route would match paths such as +/photos/A12345+. You can more succinctly express the same route this way:
<ruby>
-match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
</ruby>
+:constraints+ takes regular expressions with the restriction that regexp anchors can't be used. For example, the following route will not work:
<ruby>
-match '/:id' => 'posts#show', :constraints => {:id => /^\d/}
+get '/:id' => 'posts#show', :constraints => {:id => /^\d/}
</ruby>
However, note that you don't need to use anchors because all routes are anchored at the start.
@@ -490,8 +486,8 @@ However, note that you don't need to use anchors because all routes are anchored
For example, the following routes would allow for +posts+ with +to_param+ values like +1-hello-world+ that always begin with a number and +users+ with +to_param+ values like +david+ that never begin with a number to share the root namespace:
<ruby>
-match '/:id' => 'posts#show', :constraints => { :id => /\d.+/ }
-match '/:username' => 'users#show'
+get '/:id' => 'posts#show', :constraints => { :id => /\d.+/ }
+get '/:username' => 'users#show'
</ruby>
h4. Request-Based Constraints
@@ -501,7 +497,7 @@ You can also constrain a route based on any method on the <a href="action_contro
You specify a request-based constraint the same way that you specify a segment constraint:
<ruby>
-match "photos", :constraints => {:subdomain => "admin"}
+get "photos", :constraints => {:subdomain => "admin"}
</ruby>
You can also specify constraints in a block form:
@@ -530,17 +526,28 @@ class BlacklistConstraint
end
TwitterClone::Application.routes.draw do
- match "*path" => "blacklist#index",
+ get "*path" => "blacklist#index",
:constraints => BlacklistConstraint.new
end
</ruby>
+You can also specify constraints as a lambda:
+
+<ruby>
+TwitterClone::Application.routes.draw do
+ get "*path" => "blacklist#index",
+ :constraints => lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) }
+end
+</ruby>
+
+Both the +matches?+ method and the lambda gets the +request+ object as an argument.
+
h4. Route Globbing
Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example
<ruby>
-match 'photos/*other' => 'photos#unknown'
+get 'photos/*other' => 'photos#unknown'
</ruby>
This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
@@ -548,7 +555,7 @@ This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params
Wildcard segments can occur anywhere in a route. For example,
<ruby>
-match 'books/*section/:title' => 'books#show'
+get 'books/*section/:title' => 'books#show'
</ruby>
would match +books/some/section/last-words-a-memoir+ with +params[:section]+ equals +"some/section"+, and +params[:title]+ equals +"last-words-a-memoir"+.
@@ -556,7 +563,7 @@ would match +books/some/section/last-words-a-memoir+ with +params[:section]+ equ
Technically a route can have even more than one wildcard segment. The matcher assigns segments to parameters in an intuitive way. For example,
<ruby>
-match '*a/foo/*b' => 'test#index'
+get '*a/foo/*b' => 'test#index'
</ruby>
would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+.
@@ -564,19 +571,19 @@ would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +par
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>
-match '*pages' => 'pages#show'
+get '*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>
-match '*pages' => 'pages#show', :format => false
+get '*pages' => 'pages#show', :format => false
</ruby>
NOTE: If you want to make the format segment mandatory, so it cannot be omitted, you can supply +:format => true+ like this:
<ruby>
-match '*pages' => 'pages#show', :format => true
+get '*pages' => 'pages#show', :format => true
</ruby>
h4. Redirection
@@ -584,20 +591,20 @@ h4. Redirection
You can redirect any path to another path using the +redirect+ helper in your router:
<ruby>
-match "/stories" => redirect("/posts")
+get "/stories" => redirect("/posts")
</ruby>
You can also reuse dynamic segments from the match in the path to redirect to:
<ruby>
-match "/stories/:name" => redirect("/posts/%{name}")
+get "/stories/:name" => redirect("/posts/%{name}")
</ruby>
You can also provide a block to redirect, which receives the params and (optionally) the request object:
<ruby>
-match "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" }
-match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
+get "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" }
+get "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
</ruby>
Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
@@ -609,10 +616,10 @@ h4. Routing to Rack Applications
Instead of a String, like +"posts#index"+, which corresponds to the +index+ action in the +PostsController+, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher.
<ruby>
-match "/application.js" => Sprockets
+match "/application.js" => Sprockets, :via => :all
</ruby>
-As long as +Sprockets+ responds to +call+ and returns a <tt>[status, headers, body]</tt>, the router won't know the difference between the Rack application and an action.
+As long as +Sprockets+ responds to +call+ and returns a <tt>[status, headers, body]</tt>, the router won't know the difference between the Rack application and an action. This is an appropriate use of +:via => :all+, as you will want to allow your Rack application to handle all verbs as it considers appropriate.
NOTE: For the curious, +"posts#index"+ actually expands out to +PostsController.action(:index)+, which returns a valid Rack application.
@@ -627,6 +634,8 @@ root 'pages#main' # shortcut for the above
You should put the +root+ route at the top of the file, because it is the most popular route and should be matched first. You also need to delete the +public/index.html+ file for the root route to take effect.
+NOTE: The +root+ route only routes +GET+ requests to the action.
+
h3. Customizing Resourceful Routes
While the default routes and helpers generated by +resources :posts+ will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.
@@ -820,6 +829,24 @@ end
This will create routing helpers such as +magazine_periodical_ads_url+ and +edit_magazine_periodical_ad_path+.
+h3. Breaking Up a Large Route File
+
+If you have a large route file that you would like to break up into multiple files, you can use the +#draw+ method in your router:
+
+<ruby>
+draw :admin
+</ruby>
+
+Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method. You can then use the normal routing DSL inside that file:
+
+<ruby>
+# in config/routes/admin.rb
+
+namespace :admin do
+ resources :posts
+end
+</ruby>
+
h3. Inspecting and Testing Routes
Rails offers facilities for inspecting and testing your routes.
diff --git a/guides/source/security.textile b/guides/source/security.textile
index 747a4d6791..ac55d60368 100644
--- a/guides/source/security.textile
+++ b/guides/source/security.textile
@@ -1,7 +1,6 @@
h2. Ruby On Rails Security Guide
-This manual describes common security problems in web applications and how to avoid them with Rails. If you have any questions or suggestions, please
-mail me, Heiko Webers, at 42 {_et_} rorsecurity.info. After reading it, you should be familiar with:
+This manual describes common security problems in web applications and how to avoid them with Rails. After reading it, you should be familiar with:
* All countermeasures _(highlight)that are highlighted_
* The concept of sessions in Rails, what to put in there and popular attack methods
@@ -385,7 +384,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<plus>. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
+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
@@ -628,7 +627,7 @@ h4. Whitelists versus Blacklists
-- _When sanitizing, protecting or verifying something, whitelists over blacklists._
-A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although, sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _(highlight)prefer to use whitelist approaches_:
+A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _(highlight)prefer to use whitelist approaches_:
* Use before_filter :only => [...] instead of :except => [...]. This way you don't forget to turn it off for newly added actions.
* Use attr_accessible instead of attr_protected. See the mass-assignment section for details
diff --git a/guides/source/testing.textile b/guides/source/testing.textile
index 60b0aa89b9..d35be6a70e 100644
--- a/guides/source/testing.textile
+++ b/guides/source/testing.textile
@@ -412,7 +412,7 @@ NOTE: +assert_valid(record)+ has been deprecated. Please use +assert(record.vali
|+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|
+|+assert_response(type, message = nil)+ |Asserts that the response comes with a specific status code. You can specify +:success+ to indicate 200-299, +:redirect+ to indicate 300-399, +:missing+ to indicate 404, or +:error+ to match the 500-599 range|
|+assert_redirected_to(options = {}, message=nil)+ |Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that +assert_redirected_to(:controller => "weblog")+ will also match the redirection of +redirect_to(:controller => "weblog", :action => "show")+ and so on.|
|+assert_template(expected = nil, message=nil)+ |Asserts that the request was rendered with the appropriate template file.|
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index e63548abc9..2b2e65c813 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -38,6 +38,10 @@ h4(#identity_map4_0). IdentityMap
Rails 4.0 has removed <tt>IdentityMap</tt> from <tt>ActiveRecord</tt>, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
+h4(#active_model4_0). ActiveModel
+
+Rails 4.0 has changed how errors attach with the ConfirmationValidator. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+
h3. Upgrading from Rails 3.1 to Rails 3.2
If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb
index f1fe1e0f33..5e340499c4 100644
--- a/guides/w3c_validator.rb
+++ b/guides/w3c_validator.rb
@@ -26,7 +26,6 @@
#
# ---------------------------------------------------------------------------
-require 'rubygems'
require 'w3c_validators'
include W3CValidators
@@ -76,7 +75,7 @@ module RailsGuides
error_summary += "\n #{name}"
error_detail += "\n\n #{name} has #{errors.size} validation error(s):\n"
errors.each do |error|
- error_detail += "\n "+error.to_s.gsub("\n", "")
+ error_detail += "\n "+error.to_s.delete("\n")
end
end
diff --git a/load_paths.rb b/load_paths.rb
index 6b224d4ad5..0ad8fcfeda 100644
--- a/load_paths.rb
+++ b/load_paths.rb
@@ -1,4 +1,3 @@
# bust gem prelude
-require 'rubygems' unless defined? Gem
require 'bundler'
Bundler.setup
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index bc34ced283..1f88843ee9 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,13 @@
## Rails 4.0.0 (unreleased) ##
+* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
+
+* The application generator generates `public/humans.txt` with some basic data. *Paul Campbell*
+
+* Add `config.queue_consumer` to allow the default consumer to be configurable. *Carlos Antonio da Silva*
+
+* Add Rails.queue as an interface with a default implementation that consumes jobs in a separate thread. *Yehuda Katz*
+
* Remove Rack::SSL in favour of ActionDispatch::SSL. *Rafael Mendonça França*
* Remove Active Resource from Rails framework. *Prem Sichangrist*
diff --git a/railties/Rakefile b/railties/Rakefile
index c4a91a1d36..993ba840ff 100755..100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rubygems/package_task'
@@ -21,7 +20,8 @@ namespace :test do
'test',
'lib',
"#{File.dirname(__FILE__)}/../activesupport/lib",
- "#{File.dirname(__FILE__)}/../actionpack/lib"
+ "#{File.dirname(__FILE__)}/../actionpack/lib",
+ "#{File.dirname(__FILE__)}/../activemodel/lib"
]
ruby "-I#{dash_i.join ':'}", file
end
@@ -60,12 +60,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the guides"
-task :pguides => :generate_guides do
- require 'rake/contrib/sshpublisher'
- mkdir_p 'pkg'
- `tar -czf pkg/guides.gz guides/output`
- Rake::SshFilePublisher.new("web.rubyonrails.org", "/u/sites/guides.rubyonrails.org/public", "pkg", "guides.gz").upload
- `ssh web.rubyonrails.org 'cd /u/sites/guides.rubyonrails.org/public/ && tar -xvzf guides.gz && mv guides/output/* . && rm -rf guides*'`
-end
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 6b431d3ee3..670477f91a 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -22,14 +22,15 @@ end
module Rails
autoload :Info, 'rails/info'
autoload :InfoController, 'rails/info_controller'
+ autoload :Queueing, 'rails/queueing'
class << self
def application
- @@application ||= nil
+ @application ||= nil
end
def application=(application)
- @@application = application
+ @application = application
end
# The Configuration instance used to configure the Rails environment
@@ -37,6 +38,25 @@ module Rails
application.config
end
+ # Rails.queue is the application's queue. You can push a job onto
+ # the queue by:
+ #
+ # Rails.queue.push job
+ #
+ # A job is an object that responds to +run+. Queue consumers will
+ # pop jobs off of the queue and invoke the queue's +run+ method.
+ #
+ # Note that depending on your queue implementation, jobs may not
+ # be executed in the same process as they were created in, and
+ # are never executed in the same thread as they were created in.
+ #
+ # If necessary, a queue implementation may need to serialize your
+ # job for distribution to another process. The documentation of
+ # your queue will specify the requirements for that serialization.
+ def queue
+ application.queue
+ end
+
def initialize!
application.initialize!
end
@@ -46,15 +66,15 @@ module Rails
end
def logger
- @@logger ||= nil
+ @logger ||= nil
end
def logger=(logger)
- @@logger = logger
+ @logger = logger
end
def backtrace_cleaner
- @@backtrace_cleaner ||= begin
+ @backtrace_cleaner ||= begin
# Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded
require 'rails/backtrace_cleaner'
Rails::BacktraceCleaner.new
@@ -74,11 +94,11 @@ module Rails
end
def cache
- @@cache ||= nil
+ @cache ||= nil
end
def cache=(cache)
- @@cache = cache
+ @cache = cache
end
# Returns all rails groups for loading based on:
@@ -87,14 +107,11 @@ module Rails
# * The environment variable RAILS_GROUPS;
# * The optional envs given as argument and the hash with group dependencies;
#
- # == Examples
- #
# groups :assets => [:development, :test]
#
# # Returns
# # => [:default, :development, :assets] for Rails.env == "development"
# # => [:default, :production] for Rails.env == "production"
- #
def groups(*groups)
hash = groups.extract_options!
env = Rails.env
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index fcb981bb9a..c4edbae55b 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/hash/reverse_merge'
require 'fileutils'
require 'rails/engine'
@@ -67,9 +66,10 @@ module Rails
end
end
- attr_accessor :assets, :sandbox
+ attr_accessor :assets, :sandbox, :queue_consumer
alias_method :sandbox?, :sandbox
attr_reader :reloaders
+ attr_writer :queue
delegate :default_url_options, :default_url_options=, :to => :routes
@@ -200,6 +200,14 @@ module Rails
@config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd))
end
+ def queue #:nodoc:
+ @queue ||= build_queue
+ end
+
+ def build_queue # :nodoc:
+ config.queue.new
+ end
+
def to_app
self
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 1cfcd30c5b..a2e5dece16 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -8,10 +8,11 @@ module Rails
attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :autoflush_log,
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:dependency_loading, :exceptions_app, :file_watcher, :filter_parameters,
- :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, :preload_frameworks,
- :railties_order, :relative_url_root, :secret_token,
+ :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
+ :preload_frameworks, :railties_order, :relative_url_root, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
- :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump
+ :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump,
+ :queue, :queue_consumer
attr_writer :log_level
attr_reader :encoding
@@ -43,6 +44,8 @@ module Rails
@autoflush_log = true
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new
@use_schema_cache_dump = true
+ @queue = Rails::Queueing::Queue
+ @queue_consumer = Rails::Queueing::ThreadedConsumer
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
@@ -106,7 +109,7 @@ module Rails
# YAML::load.
def database_configuration
require 'erb'
- YAML::load(ERB.new(IO.read(paths["config/database"].first)).result)
+ YAML.load ERB.new(IO.read(paths["config/database"].first)).result
end
def log_level
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 7da495211d..84f2601f28 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -22,7 +22,7 @@ module Rails
initializer :add_builtin_route do |app|
if Rails.env.development?
app.routes.append do
- match '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/properties' => "rails/info#properties"
end
end
end
@@ -93,6 +93,13 @@ module Rails
ActiveSupport::Dependencies.unhook!
end
end
+
+ initializer :activate_queue_consumer do |app|
+ if config.queue == Rails::Queueing::Queue
+ app.queue_consumer = config.queue_consumer.start(app.queue)
+ at_exit { app.queue_consumer.shutdown }
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb
index 1e5ce67a58..b23fb3e920 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/route_inspector.rb
@@ -16,7 +16,7 @@ module Rails
class_name = app.class.name.to_s
if class_name == "ActionDispatch::Routing::Mapper::Constraints"
rack_app(app.app)
- elsif class_name !~ /^ActionDispatch::Routing/
+ elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
app
end
end
@@ -67,7 +67,7 @@ module Rails
@engines = Hash.new
end
- def format all_routes, filter = nil
+ def format(all_routes, filter = nil)
if filter
all_routes = all_routes.select{ |route| route.defaults[:controller] == filter }
end
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
index 6f9a200aa9..19f616ec50 100644
--- a/railties/lib/rails/application/routes_reloader.rb
+++ b/railties/lib/rails/application/routes_reloader.rb
@@ -3,12 +3,13 @@ require "active_support/core_ext/module/delegation"
module Rails
class Application
class RoutesReloader
- attr_reader :route_sets, :paths
+ attr_reader :route_sets, :paths, :external_routes
delegate :execute_if_updated, :execute, :updated?, :to => :updater
def initialize
- @paths = []
- @route_sets = []
+ @paths = []
+ @route_sets = []
+ @external_routes = []
end
def reload!
@@ -23,7 +24,11 @@ module Rails
def updater
@updater ||= begin
- updater = ActiveSupport::FileUpdateChecker.new(paths) { reload! }
+ dirs = @external_routes.each_with_object({}) do |dir, hash|
+ hash[dir.to_s] = %w(rb)
+ end
+
+ updater = ActiveSupport::FileUpdateChecker.new(paths, dirs) { reload! }
updater.execute
updater
end
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index cc26db849d..8cc8eb1103 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -17,9 +17,7 @@ module Rails
private
def add_gem_filters
- return unless defined?(Gem)
-
- gems_paths = (Gem.path + [Gem.default_dir]).uniq.map!{ |p| Regexp.escape(p) }
+ gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
return if gems_paths.empty?
gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 71fe604e69..7f473c237c 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -57,16 +57,19 @@ when 'server'
when 'dbconsole'
require 'rails/commands/dbconsole'
- require APP_PATH
- Rails::DBConsole.start(Rails.application)
+ Rails::DBConsole.start
when 'application', 'runner'
require "rails/commands/#{command}"
when 'new'
- puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n"
- puts "Type 'rails' for help."
- exit(1)
+ if ARGV.first.in?(['-h', '--help'])
+ require 'rails/commands/application'
+ else
+ puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n"
+ puts "Type 'rails' for help."
+ exit(1)
+ end
when '--version', '-v'
ARGV.unshift '--version'
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 60d1aed73a..2cb6d5ca2e 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -19,7 +19,6 @@ else
end
end
-require 'rubygems' if ARGV.include?("--dev")
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index d7c9e820dc..cd6a03fe51 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -27,7 +27,7 @@ module Rails
opt.on("-e", "--environment=name", String,
"Specifies the environment to run this console under (test/development/production).",
"Default: development") { |v| options[:environment] = v.strip }
- opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v }
+ opt.on("--debugger", 'Enable the debugger.') { |v| options[:debugger] = v }
opt.parse!(arguments)
end
@@ -73,10 +73,10 @@ module Rails
def require_debugger
begin
- require 'ruby-debug'
+ require 'debugger'
puts "=> Debugger enabled"
rescue Exception
- puts "You need to install ruby-debug19 to run the console in debugging mode. With gems, use 'gem install ruby-debug19'"
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
exit
end
end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 6fc127efae..aaba47117f 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -5,12 +5,37 @@ require 'rbconfig'
module Rails
class DBConsole
- def self.start(app)
- new(app).start
+ attr_reader :arguments, :config
+
+ def self.start
+ new(config).start
+ end
+
+ def self.config
+ config = begin
+ YAML.load(ERB.new(IO.read("config/database.yml")).result)
+ rescue SyntaxError, StandardError
+ require APP_PATH
+ Rails.application.config.database_configuration
+ end
+
+ unless config[env]
+ abort "No database is configured for the environment '#{env}'"
+ end
+
+ config[env]
end
- def initialize(app)
- @app = app
+ def self.env
+ if Rails.respond_to?(:env)
+ Rails.env
+ else
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+ end
+ end
+
+ def initialize(config, arguments = ARGV)
+ @config, @arguments = config, arguments
end
def start
@@ -31,28 +56,10 @@ module Rails
options['header'] = h
end
- opt.parse!(ARGV)
- abort opt.to_s unless (0..1).include?(ARGV.size)
+ opt.parse!(arguments)
+ abort opt.to_s unless (0..1).include?(arguments.size)
end
- unless config = @app.config.database_configuration[Rails.env]
- abort "No database is configured for the environment '#{Rails.env}'"
- end
-
-
- def find_cmd(*commands)
- dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
- commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
-
- full_path_command = nil
- found = commands.detect do |cmd|
- dir = dirs_on_path.detect do |path|
- full_path_command = File.join(path, cmd)
- File.executable? full_path_command
- end
- end
- found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
- end
case config["adapter"]
when /^mysql/
@@ -72,17 +79,17 @@ module Rails
args << config['database']
- exec(find_cmd('mysql', 'mysql5'), *args)
+ find_cmd_and_exec(['mysql', 'mysql5'], *args)
when "postgresql", "postgres"
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && include_password
- exec(find_cmd('psql'), config["database"])
+ find_cmd_and_exec('psql', config["database"])
when "sqlite"
- exec(find_cmd('sqlite'), config["database"])
+ find_cmd_and_exec('sqlite', config["database"])
when "sqlite3"
args = []
@@ -91,7 +98,7 @@ module Rails
args << "-header" if options['header']
args << config['database']
- exec(find_cmd('sqlite3'), *args)
+ find_cmd_and_exec('sqlite3', *args)
when "oracle", "oracle_enhanced"
logon = ""
@@ -102,12 +109,35 @@ module Rails
logon << "@#{config['database']}" if config['database']
end
- exec(find_cmd('sqlplus'), logon)
+ find_cmd_and_exec('sqlplus', logon)
else
abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
end
end
+
+ protected
+
+ def find_cmd_and_exec(commands, *args)
+ commands = Array(commands)
+
+ dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
+ commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+
+ full_path_command = nil
+ found = commands.detect do |cmd|
+ dir = dirs_on_path.detect do |path|
+ full_path_command = File.join(path, cmd)
+ File.executable? full_path_command
+ end
+ end
+
+ if found
+ exec full_path_command, *args
+ else
+ abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
+ end
+ end
end
end
diff --git a/railties/lib/rails/commands/plugin_new.rb b/railties/lib/rails/commands/plugin_new.rb
index 0287ba0638..4d7bf3c9f3 100644
--- a/railties/lib/rails/commands/plugin_new.rb
+++ b/railties/lib/rails/commands/plugin_new.rb
@@ -1,5 +1,3 @@
-require 'rubygems' if ARGV.include?("--dev")
-
if ARGV.first != "new"
ARGV[0] = "--help"
else
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index e8cc5d9e3b..2802981e7a 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -9,7 +9,6 @@ if ARGV.first.nil?
end
ARGV.clone.options do |opts|
- script_name = File.basename($0)
opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)"
opts.separator ""
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 721a47a974..4c4caad69f 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -17,7 +17,7 @@ module Rails
opts.on("-c", "--config=file", String,
"Use custom rackup configuration file") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true }
- opts.on("-u", "--debugger", "Enable ruby-debugging for the server.") { options[:debugger] = true }
+ opts.on("-u", "--debugger", "Enable the debugger") { options[:debugger] = true }
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index d8ca6cbd21..3d66019e5e 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -1,6 +1,6 @@
require 'active_support/deprecation'
require 'active_support/ordered_options'
-require 'active_support/core_ext/hash/deep_dup'
+require 'active_support/core_ext/object'
require 'rails/paths'
require 'rails/rack'
@@ -16,7 +16,7 @@ module Rails
#
# config.middleware.use Magical::Unicorns
#
- # This will put the +Magical::Unicorns+ middleware on the end of the stack.
+ # This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack.
# You can use +insert_before+ if you wish to add a middleware before another:
#
# config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 131d6e5711..47856c87c6 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -39,8 +39,6 @@ module Rails
# and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to
# the current engine.
#
- # Example:
- #
# class MyEngine < Rails::Engine
# # Add a load path for this specific Engine
# config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)
@@ -148,7 +146,7 @@ module Rails
#
# # ENGINE/config/routes.rb
# MyEngine::Engine.routes.draw do
- # match "/" => "posts#index"
+ # get "/" => "posts#index"
# end
#
# == Mount priority
@@ -158,7 +156,7 @@ module Rails
#
# MyRailsApp::Application.routes.draw do
# mount MyEngine::Engine => "/blog"
- # match "/blog/omg" => "main#omg"
+ # get "/blog/omg" => "main#omg"
# end
#
# +MyEngine+ is mounted at <tt>/blog</tt>, and <tt>/blog/omg</tt> points to application's
@@ -167,7 +165,7 @@ module Rails
# It's much better to swap that:
#
# MyRailsApp::Application.routes.draw do
- # match "/blog/omg" => "main#omg"
+ # get "/blog/omg" => "main#omg"
# mount MyEngine::Engine => "/blog"
# end
#
@@ -256,7 +254,7 @@ module Rails
# # config/routes.rb
# MyApplication::Application.routes.draw do
# mount MyEngine::Engine => "/my_engine", :as => "my_engine"
- # match "/foo" => "foo#index"
+ # get "/foo" => "foo#index"
# end
#
# Now, you can use the <tt>my_engine</tt> helper inside your application:
@@ -332,15 +330,12 @@ module Rails
#
# == Loading priority
#
- # In order to change engine's priority you can use config.railties_order in main application.
+ # In order to change engine's priority you can use +config.railties_order+ in main application.
# It will affect the priority of loading views, helpers, assets and all the other files
# related to engine or application.
#
- # Example:
- #
# # load Blog::Engine with highest priority, followed by application and other railties
# config.railties_order = [Blog::Engine, :main_app, :all]
- #
class Engine < Railtie
autoload :Configuration, "rails/engine/configuration"
autoload :Railties, "rails/engine/railties"
@@ -486,7 +481,10 @@ module Rails
end
def routes
- @routes ||= ActionDispatch::Routing::RouteSet.new
+ @routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes|
+ routes.draw_paths.concat paths["config/routes"].paths
+ end
+
@routes.append(&Proc.new) if block_given?
@routes
end
@@ -516,7 +514,7 @@ module Rails
#
# Blog::Engine.load_seed
def load_seed
- seed_file = paths["db/seeds"].existent.first
+ seed_file = paths["db/seeds.rb"].existent.first
load(seed_file) if seed_file
end
@@ -544,11 +542,13 @@ module Rails
end
initializer :add_routing_paths do |app|
- paths = self.paths["config/routes"].existent
+ paths = self.paths["config/routes.rb"].existent
+ external_paths = self.paths["config/routes"].paths
if routes? || paths.any?
app.routes_reloader.paths.unshift(*paths)
app.routes_reloader.route_sets << routes
+ app.routes_reloader.external_routes.unshift(*external_paths)
end
end
@@ -567,8 +567,9 @@ module Rails
end
initializer :load_environment_config, :before => :load_environment_hook, :group => :all do
- environment = paths["config/environments"].existent.first
- require environment if environment
+ paths["config/environments"].existent.each do |environment|
+ require environment
+ end
end
initializer :append_assets_path, :group => :all do |app|
@@ -603,7 +604,12 @@ module Rails
desc "Copy migrations from #{railtie_name} to application"
task :migrations do
ENV["FROM"] = railtie_name
- Rake::Task["railties:install:migrations"].invoke
+ if Rake::Task.task_defined?("railties:install:migrations")
+ Rake::Task["railties:install:migrations"].invoke
+ else
+ Rake::Task["app:railties:install:migrations"].invoke
+ end
+
end
end
end
@@ -616,7 +622,7 @@ module Rails
end
def routes?
- defined?(@routes)
+ defined?(@routes) && @routes
end
def has_migrations?
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index b71119af77..ffbc0b4bd6 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -34,6 +34,10 @@ The common rails commands available for engines are:
destroy Undo code generated with "generate" (short-cut alias: "d")
All commands can be run with -h for more information.
+
+If you want to run any commands that need to be run in context
+of the application, like `rails server` or `rails console`,
+you should do it from application's directory (typically test/dummy).
EOT
exit(1)
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index d7405cb519..d3b42021fc 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -52,10 +52,11 @@ module Rails
paths.add "config/environments", :glob => "#{Rails.env}.rb"
paths.add "config/initializers", :glob => "**/*.rb"
paths.add "config/locales", :glob => "*.{rb,yml}"
- paths.add "config/routes", :with => "config/routes.rb"
+ paths.add "config/routes.rb"
+ paths.add "config/routes", :glob => "**/*.rb"
paths.add "db"
paths.add "db/migrate"
- paths.add "db/seeds", :with => "db/seeds.rb"
+ paths.add "db/seeds.rb"
paths.add "vendor", :load_path => true
paths.add "vendor/assets", :glob => "*"
paths
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index b9c1b01f54..4fa990171d 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -52,6 +52,7 @@ module Rails
:orm => false,
:performance_tool => nil,
:resource_controller => :controller,
+ :resource_route => true,
:scaffold_controller => :scaffold_controller,
:stylesheets => true,
:stylesheet_engine => :css,
@@ -94,7 +95,6 @@ module Rails
# some of them are not available by adding a fallback:
#
# Rails::Generators.fallbacks[:shoulda] = :test_unit
- #
def self.fallbacks
@fallbacks ||= {}
end
@@ -114,8 +114,6 @@ module Rails
# Generators names must end with "_generator.rb". This is required because Rails
# looks in load paths and loads the generator just before it's going to be used.
#
- # ==== Examples
- #
# find_by_namespace :webrat, :rails, :integration
#
# Will search for the following generators:
@@ -124,7 +122,6 @@ module Rails
#
# Notice that "rails:generators:webrat" could be loaded as well, what
# Rails looks for is the first and last parts of the namespace.
- #
def self.find_by_namespace(name, base=nil, context=nil) #:nodoc:
lookups = []
lookups << "#{base}:#{name}" if base
@@ -172,6 +169,7 @@ module Rails
[
"rails",
+ "resource_route",
"#{orm}:migration",
"#{orm}:model",
"#{orm}:observer",
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 32793b1a70..6cd2ea2bbd 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -8,12 +8,9 @@ module Rails
# Adds an entry into Gemfile for the supplied gem. If env
# is specified, add the gem to the given environment.
#
- # ==== Example
- #
# gem "rspec", :group => :test
# gem "technoweenie-restful-authentication", :lib => "restful-authentication", :source => "http://gems.github.com/"
# gem "rails", "3.0", :git => "git://github.com/rails/rails"
- #
def gem(*args)
options = args.extract_options!
name, version = args
@@ -43,12 +40,9 @@ module Rails
# Wraps gem entries inside a group.
#
- # ==== Example
- #
# gem_group :development, :test do
# gem "rspec-rails"
# end
- #
def gem_group(*names, &block)
name = names.map(&:inspect).join(", ")
log :gemfile, "group #{name}"
@@ -66,8 +60,6 @@ module Rails
# Add the given source to Gemfile
#
- # ==== Example
- #
# add_source "http://gems.github.com/"
def add_source(source, options={})
log :source, source
@@ -82,6 +74,13 @@ module Rails
# If options :env is specified, the line is appended to the corresponding
# file in config/environments.
#
+ # environment do
+ # "config.autoload_paths += %W(#{config.root}/extras)"
+ # end
+ #
+ # environment(nil, :env => "development") do
+ # "config.active_record.observers = :cacher"
+ # end
def environment(data=nil, options={}, &block)
sentinel = /class [a-z_:]+ < Rails::Application/i
env_file_sentinel = /::Application\.configure do/
@@ -101,12 +100,9 @@ module Rails
# Run a command in git.
#
- # ==== Examples
- #
# git :init
# git :add => "this.file that.rb"
# git :add => "onefile.rb", :rm => "badfile.cxx"
- #
def git(commands={})
if commands.is_a?(Symbol)
run "git #{commands}"
@@ -120,15 +116,12 @@ module Rails
# Create a new file in the vendor/ directory. Code can be specified
# in a block or a data string can be given.
#
- # ==== Examples
- #
# vendor("sekrit.rb") do
# sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--"
# "salt = '#{sekrit_salt}'"
# end
#
# vendor("foreign.rb", "# Foreign code is fun")
- #
def vendor(filename, data=nil, &block)
log :vendor, filename
create_file("vendor/#{filename}", data, :verbose => false, &block)
@@ -137,14 +130,11 @@ module Rails
# Create a new file in the lib/ directory. Code can be specified
# in a block or a data string can be given.
#
- # ==== Examples
- #
# lib("crypto.rb") do
# "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
# end
#
# lib("foreign.rb", "# Foreign code is fun")
- #
def lib(filename, data=nil, &block)
log :lib, filename
create_file("lib/#{filename}", data, :verbose => false, &block)
@@ -152,22 +142,19 @@ module Rails
# Create a new Rakefile with the provided code (either in a block or a string).
#
- # ==== Examples
- #
# rakefile("bootstrap.rake") do
# project = ask("What is the UNIX name of your project?")
#
# <<-TASK
# namespace :#{project} do
# task :bootstrap do
- # puts "i like boots!"
+ # puts "I like boots!"
# end
# end
# TASK
# end
#
- # rakefile("seed.rake", "puts 'im plantin ur seedz'")
- #
+ # rakefile('seed.rake', 'puts "Planting seeds"')
def rakefile(filename, data=nil, &block)
log :rakefile, filename
create_file("lib/tasks/#{filename}", data, :verbose => false, &block)
@@ -175,8 +162,6 @@ module Rails
# Create a new initializer with the provided code (either in a block or a string).
#
- # ==== Examples
- #
# initializer("globals.rb") do
# data = ""
#
@@ -188,7 +173,6 @@ module Rails
# end
#
# initializer("api.rb", "API_KEY = '123456'")
- #
def initializer(filename, data=nil, &block)
log :initializer, filename
create_file("config/initializers/#{filename}", data, :verbose => false, &block)
@@ -198,10 +182,7 @@ module Rails
# The second parameter is the argument string that is passed to
# the generator or an Array that is joined.
#
- # ==== Example
- #
# generate(:authenticated, "user session")
- #
def generate(what, *args)
log :generate, what
argument = args.map {|arg| arg.to_s }.flatten.join(" ")
@@ -211,12 +192,9 @@ module Rails
# Runs the supplied rake task
#
- # ==== Example
- #
# rake("db:migrate")
# rake("db:migrate", :env => "production")
# rake("gems:install", :sudo => true)
- #
def rake(command, options={})
log :rake, command
env = options[:env] || ENV["RAILS_ENV"] || 'development'
@@ -226,10 +204,7 @@ module Rails
# Just run the capify command in root
#
- # ==== Example
- #
# capify!
- #
def capify!
log :capify, ""
in_root { run("#{extify(:capify)} .", :verbose => false) }
@@ -237,10 +212,7 @@ module Rails
# Make an entry in Rails routing file config/routes.rb
#
- # === Example
- #
- # route "root :to => 'welcome'"
- #
+ # route "root :to => 'welcome#index'"
def route(routing_code)
log :route, routing_code
sentinel = /\.routes\.draw do\s*$/
@@ -252,10 +224,7 @@ module Rails
# Reads the given file at the source root and prints it in the console.
#
- # === Example
- #
# readme "README"
- #
def readme(path)
log File.read(find_in_source_paths(path))
end
@@ -265,7 +234,6 @@ module Rails
# Define log for backwards compatibility. If just one argument is sent,
# invoke say, otherwise invoke say_status. Differently from say and
# similarly to say_status, this method respects the quiet? option given.
- #
def log(*args)
if args.size == 1
say args.first.to_s unless options.quiet?
@@ -276,7 +244,6 @@ module Rails
end
# Add an extension to the given name based on the platform.
- #
def extify(name)
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
"#{name}.bat"
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index bb2a9fcf22..2c1742c6be 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -49,6 +49,9 @@ module Rails
class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false,
:desc => "Skip JavaScript files"
+ class_option :skip_index_html, :type => :boolean, :aliases => "-I", :default => false,
+ :desc => "Skip public/index.html and app/assets/images/rails.png files"
+
class_option :dev, :type => :boolean, :default => false,
:desc => "Setup the #{name} with Gemfile pointing to your Rails checkout"
@@ -120,7 +123,7 @@ module Rails
end
def database_gemfile_entry
- options[:skip_active_record] ? "" : "gem '#{gem_for_database}'\n"
+ options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
end
def include_all_railties?
@@ -134,22 +137,24 @@ module Rails
def rails_gemfile_entry
if options.dev?
<<-GEMFILE.strip_heredoc
- gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
- gem 'journey', :git => 'https://github.com/rails/journey.git'
- gem 'arel', :git => 'https://github.com/rails/arel.git'
+ gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}'
+ gem 'journey', github: 'rails/journey'
+ gem 'arel', github: 'rails/arel'
+ gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
- gem 'rails', :git => 'https://github.com/rails/rails.git'
- gem 'journey', :git => 'https://github.com/rails/journey.git'
- gem 'arel', :git => 'https://github.com/rails/arel.git'
+ gem 'rails', github: 'rails/rails'
+ gem 'journey', github: 'rails/journey'
+ gem 'arel', github: 'rails/arel'
+ gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
GEMFILE
else
<<-GEMFILE.strip_heredoc
gem 'rails', '#{Rails::VERSION::STRING}'
# Bundle edge Rails instead:
- # gem 'rails', :git => 'https://github.com/rails/rails.git'
+ # gem 'rails', github: 'rails/rails'
GEMFILE
end
end
@@ -189,9 +194,9 @@ module Rails
# Gems used only for assets and not required
# in production environments by default.
group :assets do
- gem 'sprockets-rails', :git => 'https://github.com/rails/sprockets-rails.git'
- gem 'sass-rails', :git => 'https://github.com/rails/sass-rails.git'
- gem 'coffee-rails', :git => 'https://github.com/rails/coffee-rails.git'
+ gem 'sprockets-rails', github: 'rails/sprockets-rails'
+ gem 'sass-rails', github: 'rails/sass-rails'
+ gem 'coffee-rails', github: 'rails/coffee-rails'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
#{javascript_runtime_gemfile_entry}
@@ -203,7 +208,7 @@ module Rails
# Gems used only for assets and not required
# in production environments by default.
group :assets do
- gem 'sprockets-rails', :git => 'https://github.com/rails/sprockets-rails.git'
+ gem 'sprockets-rails', github: 'rails/sprockets-rails'
gem 'sass-rails', '~> 4.0.0.beta'
gem 'coffee-rails', '~> 4.0.0.beta'
@@ -225,7 +230,7 @@ module Rails
if defined?(JRUBY_VERSION)
"gem 'therubyrhino'\n"
else
- "# gem 'therubyracer', :platform => :ruby\n"
+ "# gem 'therubyracer', platform: :ruby\n"
end
end
@@ -242,7 +247,7 @@ module Rails
# end-user gets the bundler commands called anyway, so no big deal.
#
# Thanks to James Tucker for the Gem tricks involved in this call.
- print `"#{Gem.ruby}" -rubygems "#{Gem.bin_path('bundler', 'bundle')}" #{command}`
+ print `"#{Gem.ruby}" "#{Gem.bin_path('bundler', 'bundle')}" #{command}`
end
def run_bundle
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 60e94486bb..28d7680669 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -31,10 +31,9 @@ module Rails
# root otherwise uses a default description.
def self.desc(description=nil)
return super if description
- usage = source_root && File.expand_path("../USAGE", source_root)
- @desc ||= if usage && File.exist?(usage)
- ERB.new(File.read(usage)).result(binding)
+ @desc ||= if usage_path
+ ERB.new(File.read(usage_path)).result(binding)
else
"Description:\n Create #{base_name.humanize.downcase} files for #{generator_name} generator."
end
@@ -188,10 +187,7 @@ module Rails
# Remove a previously added hook.
#
- # ==== Examples
- #
# remove_hook_for :orm
- #
def self.remove_hook_for(*names)
remove_invocation(*names)
@@ -213,7 +209,8 @@ module Rails
# root, you should use source_root.
def self.default_source_root
return unless base_name && generator_name
- path = File.expand_path(File.join(base_name, generator_name, 'templates'), base_root)
+ return unless default_generator_root
+ path = File.join(default_generator_root, 'templates')
path if File.exists?(path)
end
@@ -248,7 +245,6 @@ module Rails
# Check whether the given class names are already taken by user
# application or Ruby on Rails.
- #
def class_collisions(*class_names) #:nodoc:
return unless behavior == :invoke
@@ -275,13 +271,11 @@ module Rails
end
# Use Rails default banner.
- #
def self.banner
"rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]".gsub(/\s+/, ' ')
end
# Sets the base_name taking into account the current class namespace.
- #
def self.base_name
@base_name ||= begin
if base = name.to_s.split('::').first
@@ -292,7 +286,6 @@ module Rails
# Removes the namespaces and get the generator name. For example,
# Rails::Generators::ModelGenerator will return "model" as generator name.
- #
def self.generator_name
@generator_name ||= begin
if generator = name.to_s.split('::').last
@@ -304,20 +297,17 @@ module Rails
# Return the default value for the option name given doing a lookup in
# Rails::Generators.options.
- #
def self.default_value_for_option(name, options)
default_for_option(Rails::Generators.options, name, options, options[:default])
end
# Return default aliases for the option name given doing a lookup in
# Rails::Generators.aliases.
- #
def self.default_aliases_for_option(name, options)
default_for_option(Rails::Generators.aliases, name, options, options[:aliases])
end
# Return default for the option name given doing a lookup in config.
- #
def self.default_for_option(config, name, options, default)
if generator_name and c = config[generator_name.to_sym] and c.key?(name)
c[name]
@@ -331,14 +321,12 @@ module Rails
end
# Keep hooks configuration that are used on prepare_for_invocation.
- #
def self.hooks #:nodoc:
@hooks ||= from_superclass(:hooks, {})
end
# Prepare class invocation to search on Rails namespace if a previous
# added hook is being used.
- #
def self.prepare_for_invocation(name, value) #:nodoc:
return super unless value.is_a?(String) || value.is_a?(Symbol)
@@ -354,7 +342,6 @@ module Rails
# Small macro to add ruby as an option to the generator with proper
# default value plus an instance helper method called shebang.
- #
def self.add_shebang_option!
class_option :ruby, :type => :string, :aliases => "-r", :default => Thor::Util.ruby_command,
:desc => "Path to the Ruby binary of your choice", :banner => "PATH"
@@ -373,6 +360,19 @@ module Rails
}
end
+ def self.usage_path
+ paths = [
+ source_root && File.expand_path("../USAGE", source_root),
+ default_generator_root && File.join(default_generator_root, "USAGE")
+ ]
+ paths.compact.detect { |path| File.exists? path }
+ end
+
+ def self.default_generator_root
+ path = File.expand_path(File.join(base_name, generator_name), base_root)
+ path if File.exists?(path)
+ end
+
end
end
end
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 303331a4f0..d78d97b2b4 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -1,23 +1,27 @@
<h1>Listing <%= plural_table_name %></h1>
<table>
- <tr>
-<% attributes.each do |attribute| -%>
- <th><%= attribute.human_name %></th>
-<% end -%>
- <th></th>
- <th></th>
- <th></th>
- </tr>
+ <thead>
+ <tr>
+ <% attributes.each do |attribute| -%>
+ <th><%= attribute.human_name %></th>
+ <% end -%>
+ <th></th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
- <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %>
-<% attributes.each do |attribute| -%>
- <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
-<% end -%>
- <td><%%= link_to 'Show', <%= singular_table_name %> %></td>
- <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
- <td><%%= link_to 'Destroy', <%= singular_table_name %>, confirm: 'Are you sure?', method: :delete %></td>
- <%% end %>
+ <tbody>
+ <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %>
+ <% attributes.each do |attribute| -%>
+ <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
+ <% end -%>
+ <td><%%= link_to 'Show', <%= singular_table_name %> %></td>
+ <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
+ <td><%%= link_to 'Destroy', <%= singular_table_name %>, confirm: 'Are you sure?', method: :delete %></td>
+ <%% end %>
+ </tbody>
</table>
<br />
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
index 67f263efbb..e15c963971 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
@@ -2,7 +2,7 @@
<% attributes.each do |attribute| -%>
<p>
- <b><%= attribute.human_name %>:</b>
+ <strong><%= attribute.human_name %>:</strong>
<%%= @<%= singular_table_name %>.<%= attribute.name %> %>
</p>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 7dfc1aa599..25d0161e4c 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -1,6 +1,4 @@
require 'active_support/time'
-require 'active_support/core_ext/object/inclusion'
-require 'active_support/core_ext/object/blank'
module Rails
module Generators
@@ -21,9 +19,20 @@ module Rails
has_index, type = type, nil if INDEX_OPTIONS.include?(type)
type, attr_options = *parse_type_and_options(type)
+ type = type.to_sym if type
+
+ if type && reference?(type)
+ references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { :unique => true } : true
+ attr_options[:index] = references_index
+ end
+
new(name, type, has_index, attr_options)
end
+ def reference?(type)
+ [:references, :belongs_to].include? type
+ end
+
private
# parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals
@@ -42,7 +51,7 @@ module Rails
def initialize(name, type=nil, index_type=false, attr_options={})
@name = name
- @type = (type.presence || :string).to_sym
+ @type = type || :string
@has_index = INDEX_OPTIONS.include?(index_type)
@has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type)
@attr_options = attr_options
@@ -87,7 +96,7 @@ module Rails
end
def reference?
- self.type.in?(:references, :belongs_to)
+ self.class.reference?(type)
end
def has_index?
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 0c5c4f6e09..5bf98bb6e0 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -3,7 +3,6 @@ module Rails
# Holds common methods for migrations. It assumes that migrations has the
# [0-9]*_name format and can be used by another frameworks (like Sequel)
# just by implementing the next migration version method.
- #
module Migration
attr_reader :migration_number, :migration_file_name, :migration_class_name
@@ -38,10 +37,7 @@ module Rails
# The migration version, migration file name, migration class name are
# available as instance variables in the template to be rendered.
#
- # ==== Examples
- #
# migration_template "migration.rb", "db/migrate/add_foo_to_bar.rb"
- #
def migration_template(source, destination=nil, config={})
destination = File.expand_path(destination || source, self.destination_root)
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 862fd9e88d..e85d1b8fa2 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -40,7 +40,7 @@ module Rails
def indent(content, multiplier = 2)
spaces = " " * multiplier
- content = content.each_line.map {|line| "#{spaces}#{line}" }.join
+ content = content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
end
def wrap_with_namespace(content)
@@ -99,7 +99,7 @@ module Rails
end
def i18n_scope
- @i18n_scope ||= file_path.gsub('/', '.')
+ @i18n_scope ||= file_path.tr('/', '.')
end
def table_name
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index f0745df667..c06b0f8994 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -97,6 +97,11 @@ module Rails
def public_directory
directory "public", "public", :recursive => false
+ if options[:skip_index_html]
+ remove_file "public/index.html"
+ remove_file 'app/assets/images/rails.png'
+ git_keep 'app/assets/images'
+ end
end
def script
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 676662b9f5..bf47e66cc4 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -22,4 +22,4 @@ source 'https://rubygems.org'
# gem 'capistrano', :group => :development
# To use debugger
-# gem 'ruby-debug19', :require => 'ruby-debug'
+# gem 'debugger'
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index d2014bd35f..b5d7b6436b 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -86,8 +86,8 @@ programming in general.
Debugger support is available through the debugger command when you start your
Mongrel or WEBrick server with --debugger. This means that you can break out of
execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug19 to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug19</tt>. Example:
+resume execution! You need to install the 'debugger' gem to run the server in debugging
+mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example:
class WeblogController < ActionController::Base
def index
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 4dc1023f1f..6eb23f68a3 100755..100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
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
index 3b5cc6648e..3192ec897b 100644
--- 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
@@ -10,4 +10,4 @@
*
*= require_self
*= require_tree .
-*/
+ */
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 c8a3c13b95..d816f973e6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -55,7 +55,7 @@ module <%= app_const_base %>
# parameters by using an attr_accessible or attr_protected declaration.
<%= comment_if :skip_active_record %>config.active_record.whitelist_attributes = true
- # Specifies wether or not has_many or has_one association option :dependent => :restrict raises
+ # Specifies whether or not has_many or has_one association option :dependent => :restrict raises
# an exception. If set to true, then an ActiveRecord::DeleteRestrictionError exception would be
# raised. If set to false, then an error will be added on the model instead.
<%= comment_if :skip_active_record %>config.active_record.dependent_restrict_raises = false
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 4489e58688..3596736667 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,5 +1,3 @@
-require 'rubygems'
-
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index f08f86aac3..467a4e725f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -24,6 +24,9 @@ development:
# domain socket that doesn't need configuration. Windows does not have
# domain sockets, so uncomment these lines.
#host: localhost
+
+ # The TCP port the server listens on. Defaults to 5432.
+ # If your server runs on a different port number, change accordingly.
#port: 5432
# Schema search path. The server defaults to $user,public
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 eb4dfa7c89..24bcec854c 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
@@ -35,4 +35,7 @@
# Expands the lines which load the assets.
config.assets.debug = true
<%- end -%>
+
+ # In development, use an in-memory queue for queueing
+ config.queue = Rails::Queueing::Queue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 7b2c86db24..072aa8355d 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
@@ -32,8 +32,8 @@
# 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
+ # Set to :debug to see everything in the log.
+ config.log_level = :info
# Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
@@ -76,4 +76,8 @@
# Use default logging formatter so that PID and timestamp are not suppressed
config.log_formatter = ::Logger::Formatter.new
+
+ # Default the production mode queue to an in-memory queue. You will probably
+ # want to replace this with an out-of-process queueing solution
+ config.queue = Rails::Queueing::Queue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index b725dd19f6..b27b88a3c6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -33,4 +33,7 @@
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
+
+ # Use the testing queue
+ config.queue = Rails::Queueing::TestQueue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index ea81748464..286e93c3cf 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -3,11 +3,11 @@
# first created -> highest priority.
# Sample of regular route:
- # match 'products/:id' => 'catalog#view'
+ # get 'products/:id' => 'catalog#view'
# Keep in mind you can assign values other than :controller and :action
# Sample of named route:
- # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
+ # get 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# This route can be invoked with purchase_url(:id => product.id)
# Sample resource route (maps HTTP verbs to controller actions automatically):
@@ -51,8 +51,4 @@
# root :to => 'welcome#index'
# See how all your routes lay out with "rake routes"
-
- # This is a legacy wild controller route that's not recommended for RESTful applications.
- # Note: This route will make all actions in every controller accessible via GET requests.
- # match ':controller(/:action(/:id))(.:format)'
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html
index 9a48320a5f..276c8c1c6a 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
- <style type="text/css">
+ <style>
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html
index 83660ab187..3f1bfb3417 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/422.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/422.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>The change you wanted was rejected (422)</title>
- <style type="text/css">
+ <style>
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html
index f3648a0dbc..dfdb7d0b05 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
- <style type="text/css">
+ <style>
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
diff --git a/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt b/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt
new file mode 100644
index 0000000000..f081e08b6c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt
@@ -0,0 +1,7 @@
+# See more about this file at: http://humanstxt.org/
+# For format suggestions, see: http://humanstxt.org/Standard.html
+/* TEAM */
+
+/* APP */
+ Name: <%= app_const_base %>
+ Software: Ruby on Rails
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 a1d50995c5..dd09a96de9 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/index.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>Ruby on Rails: Welcome aboard</title>
- <style type="text/css" media="screen">
+ <style media="screen">
body {
margin: 0;
margin-bottom: 25px;
@@ -171,7 +171,7 @@
font-style: italic;
}
</style>
- <script type="text/javascript">
+ <script>
function about() {
info = document.getElementById('about-content');
if (window.XMLHttpRequest)
diff --git a/railties/lib/rails/generators/rails/app/templates/public/robots.txt b/railties/lib/rails/generators/rails/app/templates/public/robots.txt
index 085187fa58..1a3a5e4dd2 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/robots.txt
+++ b/railties/lib/rails/generators/rails/app/templates/public/robots.txt
@@ -1,5 +1,5 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
-# User-Agent: *
+# User-agent: *
# Disallow: /
diff --git a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb
index 3fea27b916..2a849b7f2b 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb
@@ -3,7 +3,7 @@ require 'rails/performance_test_help'
class BrowsingTest < ActionDispatch::PerformanceTest
# Refer to the documentation for all available options
- # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory],
# :output => 'tmp/performance', :formats => [:flat] }
def test_homepage
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 f4263d1b98..722e37e20b 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
@@ -302,7 +302,7 @@ task :default => :test
dummy_application_path = File.expand_path("#{dummy_path}/config/application.rb", destination_root)
unless options[:pretend] || !File.exists?(dummy_application_path)
contents = File.read(dummy_application_path)
- contents[(contents.index("module Dummy"))..-1]
+ contents[(contents.index(/module ([\w]+)\n(.*)class Application/m))..-1]
end
end
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index 8588e88077..82ffeebb86 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.summary = "TODO: Summary of <%= camelized %>."
s.description = "TODO: Description of <%= camelized %>."
- s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
+ s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
<% unless options.skip_test_unit? -%>
s.test_files = Dir["test/**/*"]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index d316b00c43..9399c9cb77 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -20,4 +20,4 @@ gem "jquery-rails"
<% end -%>
# To use debugger
-# gem 'ruby-debug19', :require => 'ruby-debug'
+# gem 'debugger'
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index b7bc69d2e5..743036362e 100755..100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
begin
require 'bundler/setup'
rescue LoadError
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb
index eba0681370..c78bfb7f63 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb
@@ -1,4 +1,3 @@
-require 'rubygems'
gemfile = File.expand_path('../../../../Gemfile', __FILE__)
if File.exist?(gemfile)
diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb
index 7c7b289d19..3a0586ee43 100644
--- a/railties/lib/rails/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb
@@ -14,16 +14,7 @@ module Rails
class_option :actions, :type => :array, :banner => "ACTION ACTION", :default => [],
:desc => "Actions for the resource controller"
- class_option :http, :type => :boolean, :default => false,
- :desc => "Generate resource with HTTP actions only"
-
- def add_resource_route
- return if options[:actions].present?
- route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ")
- route_config << "resources :#{file_name.pluralize}"
- route_config << " end" * regular_class_path.size
- route route_config
- end
+ hook_for :resource_route, :required => true
end
end
end
diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
new file mode 100644
index 0000000000..6a5d62803c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
@@ -0,0 +1,13 @@
+module Rails
+ module Generators
+ class ResourceRouteGenerator < NamedBase
+ def add_resource_route
+ return if options[:actions].present?
+ route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ")
+ route_config << "resources :#{file_name.pluralize}"
+ route_config << " end" * regular_class_path.size
+ route route_config
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 083eb49d65..0618b16984 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -10,9 +10,6 @@ module Rails
class_option :orm, :banner => "NAME", :type => :string, :required => true,
:desc => "ORM to generate the controller for"
- class_option :http, :type => :boolean, :default => false,
- :desc => "Generate controller with HTTP actions only"
-
def create_controller_files
template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb")
end
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 3c5b39fa16..48833869e5 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -50,7 +50,7 @@ module Rails
end
def controller_i18n_scope
- @controller_i18n_scope ||= controller_file_path.gsub('/', '.')
+ @controller_i18n_scope ||= controller_file_path.tr('/', '.')
end
# Loads the ORM::Generators::ActiveModel class. This class is responsible
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index d81c4c3e1d..ff9cf0087e 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -31,7 +31,6 @@ module Rails
include FileUtils
class_attribute :destination_root, :current_path, :generator_class, :default_arguments
- delegate :destination_root, :current_path, :generator_class, :default_arguments, :to => :'self.class'
# Generators frequently change the current path using +FileUtils.cd+.
# So we need to store the path at file load and revert back to it after each test.
@@ -79,8 +78,8 @@ module Rails
#
# Finally, when a block is given, it yields the file content:
#
- # assert_file "app/controller/products_controller.rb" do |controller|
- # assert_instance_method :index, content do |index|
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
# assert_match(/Product\.all/, index)
# end
# end
@@ -135,7 +134,7 @@ module Rails
# Asserts a given migration does not exist. You need to supply an absolute path or a
# path relative to the configured destination:
#
- # assert_no_file "config/random.rb"
+ # assert_no_migration "db/migrate/create_products.rb"
#
def assert_no_migration(relative)
file_name = migration_file_name(relative)
@@ -159,8 +158,8 @@ module Rails
# Asserts the given method exists in the given content. When a block is given,
# it yields the content of the method.
#
- # assert_file "app/controller/products_controller.rb" do |controller|
- # assert_instance_method :index, content do |index|
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
# assert_match(/Product\.all/, index)
# end
# end
@@ -182,7 +181,7 @@ module Rails
# Asserts the given attribute type gets a proper default value:
#
- # assert_field_type :string, "MyString"
+ # assert_field_default_value :string, "MyString"
#
def assert_field_default_value(attribute_type, value)
assert_equal(value, create_generated_attribute(attribute_type).default)
diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
index e82e321914..c9af2ca832 100644
--- a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
+++ b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
@@ -1,3 +1,2 @@
-require 'rubygems'
require 'minitest/autorun'
require 'active_support'
diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
index 9e76587a0d..ca7fee3b6e 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -10,9 +10,6 @@ module TestUnit
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
- class_option :http, :type => :boolean, :default => false,
- :desc => "Generate functional test with HTTP actions only"
-
def create_test_files
template "functional_test.rb",
File.join("test/functional", controller_class_path, "#{controller_file_name}_controller_test.rb")
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index e9bd0f181e..b787d91821 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -1,3 +1,5 @@
+require "pathname"
+
module Rails
module Paths
# This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.
@@ -8,7 +10,7 @@ module Rails
# root.add "app/controllers", :eager_load => true
#
# The command above creates a new root object and add "app/controllers" as a path.
- # This means we can get a +Rails::Paths::Path+ object back like below:
+ # This means we can get a <tt>Rails::Paths::Path</tt> object back like below:
#
# path = root["app/controllers"]
# path.eager_load? # => true
@@ -114,7 +116,7 @@ module Rails
class Path
include Enumerable
- attr_reader :path
+ attr_reader :path, :root
attr_accessor :glob
def initialize(root, current, paths, options = {})
@@ -160,7 +162,7 @@ module Rails
end
def each(&block)
- @paths.each &block
+ @paths.each(&block)
end
def <<(path)
@@ -180,6 +182,14 @@ module Rails
@paths
end
+ def paths
+ raise "You need to set a path root" unless @root.path
+
+ map do |p|
+ Pathname.new(@root.path).join(p)
+ end
+ end
+
# Expands all paths against the root and return all unique values.
def expanded
raise "You need to set a path root" unless @root.path
diff --git a/railties/lib/rails/queueing.rb b/railties/lib/rails/queueing.rb
new file mode 100644
index 0000000000..b4bc7fcd18
--- /dev/null
+++ b/railties/lib/rails/queueing.rb
@@ -0,0 +1,73 @@
+require "thread"
+
+module Rails
+ module Queueing
+ # A Queue that simply inherits from STDLIB's Queue. Everytime this
+ # queue is used, Rails automatically sets up a ThreadedConsumer
+ # to consume it.
+ class Queue < ::Queue
+ end
+
+ # In test mode, the Rails queue is backed by an Array so that assertions
+ # can be made about its contents. The test queue provides a +jobs+
+ # method to make assertions about the queue's contents and a +drain+
+ # method to drain the queue and run the jobs.
+ #
+ # Jobs are run in a separate thread to catch mistakes where code
+ # assumes that the job is run in the same thread.
+ class TestQueue < ::Queue
+ # Get a list of the jobs off this queue. This method may not be
+ # available on production queues.
+ def jobs
+ @que.dup
+ end
+
+ # Drain the queue, running all jobs in a different thread. This method
+ # may not be available on production queues.
+ def drain
+ # run the jobs in a separate thread so assumptions of synchronous
+ # jobs are caught in test mode.
+ Thread.new { pop.run until empty? }.join
+ end
+ end
+
+ # The threaded consumer will run jobs in a background thread in
+ # development mode or in a VM where running jobs on a thread in
+ # production mode makes sense.
+ #
+ # When the process exits, the consumer pushes a nil onto the
+ # queue and joins the thread, which will ensure that all jobs
+ # are executed before the process finally dies.
+ class ThreadedConsumer
+ def self.start(queue)
+ new(queue).start
+ end
+
+ def initialize(queue)
+ @queue = queue
+ end
+
+ def start
+ @thread = Thread.new do
+ while job = @queue.pop
+ begin
+ job.run
+ rescue Exception => e
+ handle_exception e
+ end
+ end
+ end
+ self
+ end
+
+ def shutdown
+ @queue.push nil
+ @thread.join
+ end
+
+ def handle_exception(e)
+ Rails.logger.error "Job Error: #{e.message}\n#{e.backtrace.join("\n")}"
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb
index 5a78da1731..902361ce77 100644
--- a/railties/lib/rails/rack/debugger.rb
+++ b/railties/lib/rails/rack/debugger.rb
@@ -6,13 +6,13 @@ module Rails
ARGV.clear # clear ARGV so that rails server options aren't passed to IRB
- require 'ruby-debug'
+ require 'debugger'
::Debugger.start
::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings)
puts "=> Debugger enabled"
rescue LoadError
- puts "You need to install ruby-debug19 to run the server in debugging mode. With gems, use 'gem install ruby-debug19'"
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
exit
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index e8563f4daf..2102f8a03c 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -22,7 +22,7 @@ module Rails
#
# * creating initializers
# * configuring a Rails framework for the application, like setting a generator
- # * adding config.* keys to the environment
+ # * +adding config.*+ keys to the environment
# * setting up a subscriber with ActiveSupport::Notifications
# * adding rake tasks
#
@@ -162,7 +162,7 @@ module Rails
protected
def generate_railtie_name(class_or_module)
- ActiveSupport::Inflector.underscore(class_or_module).gsub("/", "_")
+ ActiveSupport::Inflector.underscore(class_or_module).tr("/", "_")
end
end
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index cf9e4ad500..1c6b3769a5 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -43,7 +43,7 @@ module Rails
ActiveSupport.on_load(:before_configuration, :yield => true, &block)
end
- # Third configurable block to run. Does not run if config.cache_classes
+ # 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)
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 4cd60fdc39..31e34023c0 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -14,6 +14,9 @@
# of the line (or closing ERB comment tag) is considered to be their text.
class SourceAnnotationExtractor
class Annotation < Struct.new(:line, :tag, :text)
+ def self.directories
+ @@directories ||= %w(app config lib script test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',')
+ end
# Returns a representation of the annotation that looks like this:
#
@@ -30,8 +33,9 @@ class SourceAnnotationExtractor
# Prints all annotations with tag +tag+ under the root directories +app+, +config+, +lib+,
# +script+, and +test+ (recursively). Only filenames with extension
- # +.builder+, +.rb+, and +.erb+ are taken into account. The +options+
- # hash is passed to each annotation's +to_s+.
+ # +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+, +.scss+, +.js+, and
+ # +.coffee+ 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.
def self.enumerate(tag, options={})
@@ -47,13 +51,14 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dirs+ (recursively) to arrays
# with their annotations.
- def find(dirs=%w(app config lib script test))
+ def find(dirs = Annotation.directories)
dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
end
# Returns a hash that maps filenames under +dir+ (recursively) to arrays
# with their annotations. Only files with annotations are included, and only
- # those with extension +.builder+, +.rb+, +.erb+, +.haml+, +.slim+ and +.coffee+
+ # those with extension +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+,
+ # +.scss+, +.js+, and +.coffee+
# are taken into account.
def find_in(dir)
results = {}
@@ -65,6 +70,8 @@ class SourceAnnotationExtractor
results.update(find_in(item))
elsif item =~ /\.(builder|rb|coffee)$/
results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
+ elsif item =~ /\.(css|scss|js)$/
+ results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/))
elsif item =~ /\.erb$/
results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
elsif item =~ /\.haml$/
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 2851ca4189..4ec49eee76 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -2,7 +2,7 @@ require 'rdoc/task'
# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
class RDocTaskWithoutDescriptions < RDoc::Task
- include ::Rake::DSL
+ include ::Rake::DSL if defined?(::Rake::DSL)
def define
task rdoc_task_name
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index e84b1d0644..7067253279 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -21,7 +21,11 @@ Gem::Specification.new do |s|
s.rdoc_options << '--exclude' << '.'
s.add_dependency('rake', '>= 0.8.7')
- s.add_dependency('thor', '~> 0.14.6')
+
+ # The current API of the Thor gem (0.14) will remain stable at least until Thor 2.0. Because
+ # Thor is so heavily used by other gems, we will accept Thor's semver guarantee to reduce
+ # the possibility of conflicts.
+ s.add_dependency('thor', '>= 0.14.6', '< 2.0')
s.add_dependency('rdoc', '~> 3.4')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 29ebdc6511..dfcf5aa27d 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -1,3 +1,5 @@
+ENV["RAILS_ENV"] ||= "test"
+
require File.expand_path("../../../load_paths", __FILE__)
require 'stringio'
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index a2a7f184b8..ecacb34cb2 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -15,7 +15,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
@@ -45,8 +45,8 @@ module ApplicationTests
# the debug_assets params isn't used if compile is off
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
- assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body)
+ assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
test "assets aren't concatened when compile is true is on and debug_assets params is true" do
@@ -58,8 +58,8 @@ module ApplicationTests
class ::PostsController < ActionController::Base ; end
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
- assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 1469c9af4d..e23a19d69c 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -28,7 +28,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] }
+ get '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] }
end
RUBY
@@ -204,7 +204,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
@@ -230,7 +230,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
@@ -334,7 +334,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/omg', :to => "omg#index"
+ get '/omg', :to => "omg#index"
end
RUBY
@@ -382,8 +382,8 @@ module ApplicationTests
# the debug_assets params isn't used if compile is off
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
- assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body)
+ assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
test "assets aren't concatened when compile is true is on and debug_assets params is true" do
@@ -397,8 +397,8 @@ module ApplicationTests
class ::PostsController < ActionController::Base ; end
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
- assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
end
test "assets can access model information when precompiling" do
@@ -513,7 +513,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index ac5ac2b93e..252dd0e31a 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -41,6 +41,14 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
+ test "multiple queue construction is possible" do
+ require 'rails'
+ require "#{app_path}/config/environment"
+ mail_queue = Rails.application.build_queue
+ image_processing_queue = Rails.application.build_queue
+ assert_not_equal mail_queue, image_processing_queue
+ end
+
test "Rails.groups returns available groups" do
require "rails"
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index a08e5b2374..c2d4a0f2c8 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -116,7 +116,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match "/:controller(/:action)"
+ get "/:controller(/:action)"
end
RUBY
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index abb277dc1d..02d20bc150 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -85,7 +85,7 @@ en:
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] }
+ get '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] }
end
RUBY
@@ -109,7 +109,7 @@ en:
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
+ get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
end
RUBY
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 92951e1676..e0286502f3 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -77,8 +77,8 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/load', :to => lambda { |env| [200, {}, Post.all] }
- match '/unload', :to => lambda { |env| [200, {}, []] }
+ get '/load', :to => lambda { |env| [200, {}, Post.all] }
+ get '/unload', :to => lambda { |env| [200, {}, []] }
end
RUBY
@@ -107,7 +107,7 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
+ get '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
end
RUBY
@@ -146,7 +146,7 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
+ get '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
end
RUBY
@@ -182,7 +182,7 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
$counter = 0
AppTemplate::Application.routes.draw do
- match '/c', :to => lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ get '/c', :to => lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
@@ -213,8 +213,8 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/title', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] }
- match '/body', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] }
+ get '/title', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] }
+ get '/body', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] }
end
RUBY
@@ -272,7 +272,7 @@ class LoadingTest < ActiveSupport::TestCase
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match "/:controller(/:action)"
+ get "/:controller(/:action)"
end
RUBY
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index 561b020707..54b18542c2 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -46,7 +46,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
end
diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
index a80898092d..d1a614e181 100644
--- a/railties/test/application/middleware/exceptions_test.rb
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -17,31 +17,32 @@ module ApplicationTests
end
test "show exceptions middleware filter backtrace before logging" do
- my_middleware = Struct.new(:app) do
- def call(env)
- raise "Failure"
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ raise 'oops'
+ end
end
- end
-
- app.config.middleware.use my_middleware
+ RUBY
- stringio = StringIO.new
- Rails.logger = Logger.new(stringio)
+ get "/foo"
+ assert_equal 500, last_response.status
- get "/"
- assert_no_match(/action_dispatch/, stringio.string)
+ log = File.read(Rails.application.config.paths["log"].first)
+ assert_no_match(/action_dispatch/, log, log)
+ assert_match(/oops/, log, log)
end
test "renders active record exceptions as 404" do
- my_middleware = Struct.new(:app) do
- def call(env)
- raise ActiveRecord::RecordNotFound
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ raise ActiveRecord::RecordNotFound
+ end
end
- end
-
- app.config.middleware.use my_middleware
+ RUBY
- get "/"
+ get "/foo"
assert_equal 404, last_response.status
end
@@ -104,7 +105,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ post ':controller(/:action)'
end
RUBY
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index f4e77ee244..07134cc935 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -26,5 +26,25 @@ module ApplicationTests
require "#{app_path}/config/environment"
assert app.config.session_options[:secure], "Expected session to be marked as secure"
end
+
+ test "session is not loaded if it's not used" do
+ make_basic_app
+
+ class ::OmgController < ActionController::Base
+ def index
+ if params[:flash]
+ flash[:notice] = "notice"
+ end
+
+ render :nothing => true
+ end
+ end
+
+ get "/?flash=true"
+ get "/"
+
+ assert last_request.env["HTTP_COOKIE"]
+ assert !last_response.headers["Set-Cookie"]
+ end
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index fc5fb60174..ba747bc633 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -148,6 +148,13 @@ module ApplicationTests
assert_equal "Rack::Config", middleware.first
end
+ test "can't change middleware after it's built" do
+ boot!
+ assert_raise RuntimeError do
+ app.config.middleware.use Rack::Config
+ end
+ end
+
# ConditionalGet + Etag
test "conditional get + etag middlewares handle http caching based on body" do
make_basic_app
diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb
index 4029984ce9..e0893f53be 100644
--- a/railties/test/application/paths_test.rb
+++ b/railties/test/application/paths_test.rb
@@ -50,6 +50,8 @@ module ApplicationTests
assert_path @paths["config/locales"], "config/locales/en.yml"
assert_path @paths["config/environment"], "config/environment.rb"
assert_path @paths["config/environments"], "config/environments/development.rb"
+ assert_path @paths["config/routes.rb"], "config/routes.rb"
+ assert_path @paths["config/routes"], "config/routes"
assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first
end
diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb
new file mode 100644
index 0000000000..da8bdeed52
--- /dev/null
+++ b/railties/test/application/queue_test.rb
@@ -0,0 +1,145 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class GeneratorsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def app_const
+ @app_const ||= Class.new(Rails::Application)
+ end
+
+ test "the queue is a TestQueue in test mode" do
+ app("test")
+ assert_kind_of Rails::Queueing::TestQueue, Rails.application.queue
+ assert_kind_of Rails::Queueing::TestQueue, Rails.queue
+ end
+
+ test "the queue is a Queue in development mode" do
+ app("development")
+ assert_kind_of Rails::Queueing::Queue, Rails.application.queue
+ assert_kind_of Rails::Queueing::Queue, Rails.queue
+ end
+
+ test "in development mode, an enqueued job will be processed in a separate thread" do
+ app("development")
+
+ job = Struct.new(:origin, :target).new(Thread.current)
+ def job.run
+ self.target = Thread.current
+ end
+
+ Rails.queue.push job
+ sleep 0.1
+
+ assert job.target, "The job was run"
+ assert_not_equal job.origin, job.target
+ end
+
+ test "in test mode, explicitly draining the queue will process it in a separate thread" do
+ app("test")
+
+ job = Struct.new(:origin, :target).new(Thread.current)
+ def job.run
+ self.target = Thread.current
+ end
+
+ Rails.queue.push job
+ Rails.queue.drain
+
+ assert job.target, "The job was run"
+ assert_not_equal job.origin, job.target
+ end
+
+ test "in test mode, the queue can be observed" do
+ app("test")
+
+ job = Struct.new(:id) do
+ def run
+ end
+ end
+
+ jobs = (1..10).map do |id|
+ job.new(id)
+ end
+
+ jobs.each do |job|
+ Rails.queue.push job
+ end
+
+ assert_equal jobs, Rails.queue.jobs
+ end
+
+ def setup_custom_queue
+ add_to_env_config "production", <<-RUBY
+ require "my_queue"
+ config.queue = MyQueue
+ RUBY
+
+ app_file "lib/my_queue.rb", <<-RUBY
+ class MyQueue
+ def push(job)
+ job.run
+ end
+ end
+ RUBY
+
+ app("production")
+ end
+
+ test "a custom queue implementation can be provided" do
+ setup_custom_queue
+
+ assert_kind_of MyQueue, Rails.queue
+
+ job = Struct.new(:id, :ran) do
+ def run
+ self.ran = true
+ end
+ end
+
+ job1 = job.new(1)
+ Rails.queue.push job1
+
+ assert_equal true, job1.ran
+ end
+
+ test "a custom consumer implementation can be provided" do
+ add_to_env_config "production", <<-RUBY
+ require "my_queue_consumer"
+ config.queue_consumer = MyQueueConsumer
+ RUBY
+
+ app_file "lib/my_queue_consumer.rb", <<-RUBY
+ class MyQueueConsumer < Rails::Queueing::ThreadedConsumer
+ attr_reader :started
+
+ def start
+ @started = true
+ self
+ end
+ end
+ RUBY
+
+ app("production")
+
+ assert_kind_of MyQueueConsumer, Rails.application.queue_consumer
+ assert Rails.application.queue_consumer.started
+ end
+
+ test "default consumer is not used with custom queue implementation" do
+ setup_custom_queue
+
+ assert_nil Rails.application.queue_consumer
+ end
+ end
+end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 04abf9e3a1..05d73dfc5c 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -12,11 +12,14 @@ module ApplicationTests
teardown_app
end
- test 'notes' do
+ test 'notes finds notes for certain file_types' do
app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>"
app_file "app/views/home/index.html.haml", "-# TODO: note in haml"
app_file "app/views/home/index.html.slim", "/ TODO: note in slim"
app_file "app/assets/javascripts/application.js.coffee", "# TODO: note in coffee"
+ app_file "app/assets/javascripts/application.js", "// TODO: note in js"
+ app_file "app/assets/stylesheets/application.css", "// TODO: note in css"
+ app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby"
boot_rails
@@ -35,15 +38,90 @@ module ApplicationTests
assert_match /note in slim/, output
assert_match /note in ruby/, output
assert_match /note in coffee/, output
+ assert_match /note in js/, output
+ assert_match /note in css/, output
+ assert_match /note in scss/, output
- assert_equal 5, lines.size
+ assert_equal 8, lines.size
lines.each do |line|
assert_equal 4, line[0].size
assert_equal ' ', line[1]
end
end
+ end
+
+ test 'notes finds notes in default directories' do
+ app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
+ app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "lib/some_file.rb", "# TODO: note in lib directory"
+ app_file "script/run_something.rb", "# TODO: note in script directory"
+ app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
+
+ app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
+
+ boot_rails
+
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake notes`
+ lines = output.scan(/\[([0-9\s]+)\]/).flatten
+
+ assert_match /note in app directory/, output
+ assert_match /note in config directory/, output
+ assert_match /note in lib directory/, output
+ assert_match /note in script directory/, output
+ assert_match /note in test directory/, output
+ assert_no_match /note in some_other directory/, output
+
+ assert_equal 5, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
+ end
+ end
+
+ test 'notes finds notes in custom directories' do
+ app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
+ app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "lib/some_file.rb", "# TODO: note in lib directory"
+ app_file "script/run_something.rb", "# TODO: note in script directory"
+ app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
+
+ app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
+
+ boot_rails
+
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes`
+ lines = output.scan(/\[([0-9\s]+)\]/).flatten
+
+ assert_match /note in app directory/, output
+ assert_match /note in config directory/, output
+ assert_match /note in lib directory/, output
+ assert_match /note in script directory/, output
+ assert_match /note in test directory/, output
+
+ assert_match /note in some_other directory/, output
+
+ assert_equal 6, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
+ end
end
private
diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb
index 6393cfff4b..3b8c874b5b 100644
--- a/railties/test/application/route_inspect_test.rb
+++ b/railties/test/application/route_inspect_test.rb
@@ -13,6 +13,12 @@ module ApplicationTests
app.config.assets = ActiveSupport::OrderedOptions.new
app.config.assets.prefix = '/sprockets'
Rails.stubs(:application).returns(app)
+ Rails.stubs(:env).returns("development")
+ end
+
+ def draw(&block)
+ @set.draw(&block)
+ @inspector.format(@set.routes)
end
def test_displaying_routes_for_engines
@@ -25,12 +31,11 @@ module ApplicationTests
get '/cart', :to => 'cart#show'
end
- @set.draw do
+ output = draw do
get '/custom/assets', :to => 'custom_assets#show'
mount engine => "/blog", :as => "blog"
end
- output = @inspector.format @set.routes
expected = [
"custom_assets GET /custom/assets(.:format) custom_assets#show",
" blog /blog Blog::Engine",
@@ -41,26 +46,23 @@ module ApplicationTests
end
def test_cart_inspect
- @set.draw do
+ output = draw do
get '/cart', :to => 'cart#show'
end
- output = @inspector.format @set.routes
assert_equal ["cart GET /cart(.:format) cart#show"], output
end
def test_inspect_shows_custom_assets
- @set.draw do
+ output = draw do
get '/custom/assets', :to => 'custom_assets#show'
end
- output = @inspector.format @set.routes
assert_equal ["custom_assets GET /custom/assets(.:format) custom_assets#show"], output
end
def test_inspect_routes_shows_resources_route
- @set.draw do
+ output = draw do
resources :articles
end
- output = @inspector.format @set.routes
expected = [
" articles GET /articles(.:format) articles#index",
" POST /articles(.:format) articles#create",
@@ -74,51 +76,45 @@ module ApplicationTests
end
def test_inspect_routes_shows_root_route
- @set.draw do
+ output = draw do
root :to => 'pages#main'
end
- output = @inspector.format @set.routes
- assert_equal ["root / pages#main"], output
+ assert_equal ["root GET / pages#main"], output
end
def test_inspect_routes_shows_dynamic_action_route
- @set.draw do
- match 'api/:action' => 'api'
+ output = draw do
+ get 'api/:action' => 'api'
end
- output = @inspector.format @set.routes
- assert_equal [" /api/:action(.:format) api#:action"], output
+ assert_equal [" GET /api/:action(.:format) api#:action"], output
end
def test_inspect_routes_shows_controller_and_action_only_route
- @set.draw do
- match ':controller/:action'
+ output = draw do
+ get ':controller/:action'
end
- output = @inspector.format @set.routes
- assert_equal [" /:controller/:action(.:format) :controller#:action"], output
+ assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output
end
def test_inspect_routes_shows_controller_and_action_route_with_constraints
- @set.draw do
- match ':controller(/:action(/:id))', :id => /\d+/
+ output = draw do
+ get ':controller(/:action(/:id))', :id => /\d+/
end
- output = @inspector.format @set.routes
- assert_equal [" /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output
+ assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output
end
def test_rake_routes_shows_route_with_defaults
- @set.draw do
- match 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
+ output = draw do
+ get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
end
- output = @inspector.format @set.routes
- assert_equal [%Q[ /photos/:id(.:format) photos#show {:format=>"jpg"}]], output
+ assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output
end
def test_rake_routes_shows_route_with_constraints
- @set.draw do
- match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+ output = draw do
+ get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
- output = @inspector.format @set.routes
- assert_equal [" /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output
+ assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output
end
class RackApp
@@ -127,11 +123,10 @@ module ApplicationTests
end
def test_rake_routes_shows_route_with_rack_app
- @set.draw do
- match 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
+ output = draw do
+ get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
end
- output = @inspector.format @set.routes
- assert_equal [" /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output
+ assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output
end
def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints
@@ -141,23 +136,33 @@ module ApplicationTests
end
end
- @set.draw do
+ output = draw do
scope :constraint => constraint.new do
mount RackApp => '/foo'
end
end
- output = @inspector.format @set.routes
assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output
end
def test_rake_routes_dont_show_app_mounted_in_assets_prefix
- @set.draw do
- match '/sprockets' => RackApp
+ output = draw do
+ get '/sprockets' => RackApp
end
- output = @inspector.format @set.routes
assert_no_match(/RackApp/, output.first)
assert_no_match(/\/sprockets/, output.first)
end
+
+ def test_redirect
+ output = draw do
+ get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" }
+ get "/bar" => redirect(path: "/foo/bar", status: 307)
+ get "/foobar" => redirect{ "/foo/bar" }
+ end
+
+ assert_equal " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", output[0]
+ assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1]
+ assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2]
+ end
end
end
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 28ce3beea9..977a5fc7e8 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -53,7 +53,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
@@ -94,7 +94,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
@@ -126,8 +126,8 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'admin/foo', :to => 'admin/foo#index'
- match 'foo', :to => 'foo#index'
+ get 'admin/foo', :to => 'admin/foo#index'
+ get 'foo', :to => 'foo#index'
end
RUBY
@@ -141,13 +141,13 @@ module ApplicationTests
test "routes appending blocks" do
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller/:action'
+ get ':controller/:action'
end
RUBY
add_to_config <<-R
routes.append do
- match '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] }
+ get '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] }
end
R
@@ -158,7 +158,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-R
AppTemplate::Application.routes.draw do
- match 'lol' => 'hello#index'
+ get 'lol' => 'hello#index'
end
R
@@ -166,7 +166,90 @@ module ApplicationTests
assert_equal 'WIN', last_response.body
end
+ test "routes drawing from config/routes" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ draw :external
+ end
+ RUBY
+
+ app_file 'config/routes/external.rb', <<-RUBY
+ get ':controller/:action'
+ RUBY
+
+ controller :success, <<-RUBY
+ class SuccessController < ActionController::Base
+ def index
+ render :text => "success!"
+ end
+ end
+ RUBY
+
+ app 'development'
+ get '/success/index'
+ assert_equal 'success!', last_response.body
+ end
+
{"development" => "baz", "production" => "bar"}.each do |mode, expected|
+ test "reloads routes when external configuration is changed in #{mode}" do
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def bar
+ render :text => "bar"
+ end
+
+ def baz
+ render :text => "baz"
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ draw :external
+ end
+ RUBY
+
+ app_file 'config/routes/external.rb', <<-RUBY
+ get 'foo', :to => 'foo#bar'
+ RUBY
+
+ app(mode)
+
+ get '/foo'
+ assert_equal 'bar', last_response.body
+
+ app_file 'config/routes/external.rb', <<-RUBY
+ get 'foo', :to => 'foo#baz'
+ RUBY
+
+ sleep 0.1
+
+ get '/foo'
+ assert_equal expected, last_response.body
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ draw :external
+ draw :other_external
+ end
+ RUBY
+
+ app_file 'config/routes/other_external.rb', <<-RUBY
+ get 'win', :to => 'foo#baz'
+ RUBY
+
+ sleep 0.1
+
+ get '/win'
+
+ if mode == "development"
+ assert_equal expected, last_response.body
+ else
+ assert_equal 404, last_response.status
+ end
+ end
+
test "reloads routes when configuration is changed in #{mode}" do
controller :foo, <<-RUBY
class FooController < ApplicationController
@@ -182,7 +265,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'foo', :to => 'foo#bar'
+ get 'foo', :to => 'foo#bar'
end
RUBY
@@ -193,7 +276,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'foo', :to => 'foo#baz'
+ get 'foo', :to => 'foo#baz'
end
RUBY
@@ -214,7 +297,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'foo', :to => ::InitializeRackApp
+ get 'foo', :to => ::InitializeRackApp
end
RUBY
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index 85a8a15fcc..f7e60749a7 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -31,7 +31,7 @@ module ApplicationTests
end
MyApp.routes.draw do
- match "/" => "omg#index", :as => :omg
+ get "/" => "omg#index", :as => :omg
end
require 'rack/test'
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index cbe7d35f6d..2dd74f8fd1 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -1,26 +1,24 @@
require 'abstract_unit'
require 'rails/backtrace_cleaner'
-if defined? Gem
- class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
- def setup
- @cleaner = Rails::BacktraceCleaner.new
- end
+class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
+ def setup
+ @cleaner = Rails::BacktraceCleaner.new
+ end
+
+ test "should format installed gems correctly" do
+ @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ @result = @cleaner.clean(@backtrace, :all)
+ assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
+ end
- test "should format installed gems correctly" do
- @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ test "should format installed gems not in Gem.default_dir correctly" do
+ @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
+ # skip this test if default_dir is the only directory on Gem.path
+ if @target_dir
+ @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
@result = @cleaner.clean(@backtrace, :all)
assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
end
-
- test "should format installed gems not in Gem.default_dir correctly" do
- @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
- # skip this test if default_dir is the only directory on Gem.path
- if @target_dir
- @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
- @result = @cleaner.clean(@backtrace, :all)
- assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
- end
- end
end
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
new file mode 100644
index 0000000000..85a7edfacd
--- /dev/null
+++ b/railties/test/commands/dbconsole_test.rb
@@ -0,0 +1,160 @@
+require 'abstract_unit'
+require 'rails/commands/dbconsole'
+
+class Rails::DBConsoleTest < ActiveSupport::TestCase
+ def teardown
+ %w[PGUSER PGHOST PGPORT PGPASSWORD].each{|key| ENV.delete(key)}
+ end
+
+ def test_config
+ Rails::DBConsole.const_set(:APP_PATH, "erb")
+
+ app_config({})
+ capture_abort { Rails::DBConsole.config }
+ assert aborted
+ assert_match /No database is configured for the environment '\w+'/, output
+
+ app_config(test: "with_init")
+ assert_equal Rails::DBConsole.config, "with_init"
+
+ app_db_file("test:\n without_init")
+ assert_equal Rails::DBConsole.config, "without_init"
+
+ app_db_file("test:\n <%= Rails.something_app_specific %>")
+ assert_equal Rails::DBConsole.config, "with_init"
+
+ app_db_file("test:\n\ninvalid")
+ assert_equal Rails::DBConsole.config, "with_init"
+ end
+
+ def test_env
+ assert_equal Rails::DBConsole.env, "test"
+
+ Rails.stubs(:respond_to?).with(:env).returns(false)
+ assert_equal Rails::DBConsole.env, "test"
+
+ ENV['RAILS_ENV'] = nil
+ ENV['RACK_ENV'] = "rack_env"
+ assert_equal Rails::DBConsole.env, "rack_env"
+
+ ENV['RAILS_ENV'] = "rails_env"
+ assert_equal Rails::DBConsole.env, "rails_env"
+ ensure
+ ENV['RAILS_ENV'] = "test"
+ end
+
+ def test_mysql
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], 'db')
+ start(adapter: 'mysql', database: 'db')
+ assert !aborted
+ end
+
+ def test_mysql_full
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db')
+ start(adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8')
+ assert !aborted
+ end
+
+ def test_mysql_include_password
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--user=user', '--password=qwerty', 'db')
+ start({adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}, ['-p'])
+ assert !aborted
+ end
+
+ def test_postgresql
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start(adapter: 'postgresql', database: 'db')
+ assert !aborted
+ end
+
+ def test_postgresql_full
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start(adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432)
+ assert !aborted
+ assert_equal 'user', ENV['PGUSER']
+ assert_equal 'host', ENV['PGHOST']
+ assert_equal '5432', ENV['PGPORT']
+ assert_not_equal 'q1w2e3', ENV['PGPASSWORD']
+ end
+
+ def test_postgresql_include_password
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start({adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}, ['-p'])
+ assert !aborted
+ assert_equal 'user', ENV['PGUSER']
+ assert_equal 'q1w2e3', ENV['PGPASSWORD']
+ end
+
+ def test_sqlite
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite', 'db')
+ start(adapter: 'sqlite', database: 'db')
+ assert !aborted
+ end
+
+ def test_sqlite3
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', 'db')
+ start(adapter: 'sqlite3', database: 'db')
+ assert !aborted
+ end
+
+ def test_sqlite3_mode
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', 'db')
+ start({adapter: 'sqlite3', database: 'db'}, ['--mode', 'html'])
+ assert !aborted
+ end
+
+ def test_sqlite3_header
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', 'db')
+ start({adapter: 'sqlite3', database: 'db'}, ['--header'])
+ assert !aborted
+ end
+
+ def test_oracle
+ dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user@db')
+ start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret')
+ assert !aborted
+ end
+
+ def test_oracle_include_password
+ dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user/secret@db')
+ start({adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}, ['-p'])
+ assert !aborted
+ end
+
+ def test_unknown_command_line_client
+ start(adapter: 'unknown', database: 'db')
+ assert aborted
+ assert_match /Unknown command-line client for db/, output
+ end
+
+ private
+ attr_reader :aborted, :output
+
+ def dbconsole
+ @dbconsole ||= Rails::DBConsole.new(nil)
+ end
+
+ def start(config = {}, argv = [])
+ dbconsole.stubs(config: config.stringify_keys, arguments: argv)
+ capture_abort { dbconsole.start }
+ end
+
+ def capture_abort
+ @aborted = false
+ @output = capture(:stderr) do
+ begin
+ yield
+ rescue SystemExit
+ @aborted = true
+ end
+ end
+ end
+
+ def app_db_file(result)
+ IO.stubs(:read).with("config/database.yml").returns(result)
+ end
+
+ def app_config(result)
+ Rails.application.config.stubs(:database_configuration).returns(result.stringify_keys)
+ end
+end
diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb
new file mode 100644
index 0000000000..68406dce4c
--- /dev/null
+++ b/railties/test/engine_test.rb
@@ -0,0 +1,24 @@
+require 'abstract_unit'
+
+class EngineTest < ActiveSupport::TestCase
+ it "reports routes as available only if they're actually present" do
+ engine = Class.new(Rails::Engine) do
+ def initialize(*args)
+ @routes = nil
+ super
+ end
+ end
+
+ assert !engine.routes?
+ end
+
+ it "does not add more paths to routes on each call" do
+ engine = Class.new(Rails::Engine)
+
+ engine.routes
+ length = engine.routes.draw_paths.length
+
+ engine.routes
+ assert_equal length, engine.routes.draw_paths.length
+ end
+end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index d8887a6471..26e912fd9e 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -83,6 +83,16 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_equal false, $?.success?
end
+ def test_application_new_show_help_message_inside_existing_rails_directory
+ app_root = File.join(destination_root, 'myfirstapp')
+ run_generator [app_root]
+ output = Dir.chdir(app_root) do
+ `rails new --help`
+ end
+ assert_match(/rails new APP_PATH \[options\]/, output)
+ assert_equal true, $?.success?
+ end
+
def test_application_name_is_detected_if_it_exists_and_app_folder_renamed
app_root = File.join(destination_root, "myapp")
app_moved_root = File.join(destination_root, "myapp_moved")
@@ -136,9 +146,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "config/database.yml", /sqlite3/
unless defined?(JRUBY_VERSION)
- assert_file "Gemfile", /^gem\s+["']sqlite3["']$/
+ assert_gem "sqlite3"
else
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/
+ assert_gem "activerecord-jdbcsqlite3-adapter"
end
end
@@ -146,9 +156,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator([destination_root, "-d", "mysql"])
assert_file "config/database.yml", /mysql/
unless defined?(JRUBY_VERSION)
- assert_file "Gemfile", /^gem\s+["']mysql2["']$/
+ assert_gem "mysql2"
else
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/
+ assert_gem "activerecord-jdbcmysql-adapter"
end
end
@@ -156,45 +166,45 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator([destination_root, "-d", "postgresql"])
assert_file "config/database.yml", /postgresql/
unless defined?(JRUBY_VERSION)
- assert_file "Gemfile", /^gem\s+["']pg["']$/
+ assert_gem "pg"
else
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/
+ assert_gem "activerecord-jdbcpostgresql-adapter"
end
end
def test_config_jdbcmysql_database
run_generator([destination_root, "-d", "jdbcmysql"])
assert_file "config/database.yml", /mysql/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/
+ assert_gem "activerecord-jdbcmysql-adapter"
# TODO: When the JRuby guys merge jruby-openssl in
# jruby this will be removed
- assert_file "Gemfile", /^gem\s+["']jruby-openssl["']$/ if defined?(JRUBY_VERSION)
+ assert_gem "jruby-openssl" if defined?(JRUBY_VERSION)
end
def test_config_jdbcsqlite3_database
run_generator([destination_root, "-d", "jdbcsqlite3"])
assert_file "config/database.yml", /sqlite3/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/
+ assert_gem "activerecord-jdbcsqlite3-adapter"
end
def test_config_jdbcpostgresql_database
run_generator([destination_root, "-d", "jdbcpostgresql"])
assert_file "config/database.yml", /postgresql/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/
+ assert_gem "activerecord-jdbcpostgresql-adapter"
end
def test_config_jdbc_database
run_generator([destination_root, "-d", "jdbc"])
assert_file "config/database.yml", /jdbc/
assert_file "config/database.yml", /mssql/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbc-adapter["']$/
+ assert_gem "activerecord-jdbc-adapter"
end
def test_config_jdbc_database_when_no_option_given
if defined?(JRUBY_VERSION)
run_generator([destination_root])
assert_file "config/database.yml", /sqlite3/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/
+ assert_gem "activerecord-jdbcsqlite3-adapter"
end
end
@@ -234,12 +244,19 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_javascript_runtime
run_generator([destination_root])
if defined?(JRUBY_VERSION)
- assert_file "Gemfile", /gem\s+["']therubyrhino["']$/
+ assert_gem "therubyrhino"
else
- assert_file "Gemfile", /# gem\s+["']therubyracer["']+, :platform => :ruby$/
+ assert_file "Gemfile", /# gem\s+["']therubyracer["']+, platform: :ruby$/
end
end
+ def test_generator_if_skip_index_html_is_given
+ run_generator [destination_root, "--skip-index-html"]
+ assert_no_file "public/index.html"
+ assert_no_file "app/assets/images/rails.png"
+ assert_file "app/assets/images/.gitkeep"
+ end
+
def test_creation_of_a_test_directory
run_generator
assert_file 'test'
@@ -261,9 +278,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require jquery}, contents
assert_match %r{^//= require jquery_ujs}, contents
end
- assert_file 'Gemfile' do |contents|
- assert_match(/^gem 'jquery-rails'/, contents)
- end
+ assert_gem "jquery-rails"
end
def test_other_javascript_libraries
@@ -272,9 +287,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require prototype}, contents
assert_match %r{^//= require prototype_ujs}, contents
end
- assert_file 'Gemfile' do |contents|
- assert_match(/^gem 'prototype-rails'/, contents)
- end
+ assert_gem "prototype-rails"
end
def test_javascript_is_skipped_if_required
@@ -284,11 +297,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_inclusion_of_ruby_debug19
+ def test_inclusion_of_debugger
run_generator
- assert_file "Gemfile" do |contents|
- assert_match(/gem 'ruby-debug19', :require => 'ruby-debug'/, contents)
- end
+ assert_file "Gemfile", /# gem 'debugger'/
end
def test_template_from_dir_pwd
@@ -302,7 +313,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_default_usage
- File.expects(:exist?).returns(false)
+ Rails::Generators::AppGenerator.expects(:usage_path).returns(nil)
assert_match(/Create rails files for app generator/, Rails::Generators::AppGenerator.desc)
end
@@ -366,11 +377,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/run bundle install/, output)
end
+ def test_humans_txt_file
+ run_generator [File.join(destination_root, 'things-43')]
+ assert_file "things-43/public/humans.txt", /Name: Things43/, /Software: Ruby on Rails/
+ end
+
protected
def action(*args, &block)
silence(:stdout) { generator.send(*args, &block) }
end
+
+ def assert_gem(gem)
+ assert_file "Gemfile", /^gem\s+["']#{gem}["']$/
+ end
end
class CustomAppGeneratorTest < Rails::Generators::TestCase
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index fd84164340..b320e40654 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -152,4 +152,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_properly_identifies_usage_file
+ assert generator_class.send(:usage_path)
+ end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index e8d933935d..fd3b8c8a17 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -283,7 +283,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_match(/add_index/, up)
+ assert_match(/index: true/, up)
end
end
end
@@ -293,7 +293,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_match(/add_index/, up)
+ assert_match(/index: true/, up)
end
end
end
@@ -303,7 +303,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_no_match(/add_index/, up)
+ assert_no_match(/index: true/, up)
end
end
end
@@ -313,7 +313,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_no_match(/add_index/, up)
+ assert_no_match(/index: true/, up)
end
end
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 5c63b13dce..c495258343 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -56,11 +56,20 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
run_generator
assert_file "config/routes.rb", /get "account\/foo"/, /get "account\/bar"/
end
-#
+
def test_invokes_default_template_engine_even_with_no_action
run_generator ["account"]
assert_file "app/views/test_app/account"
end
+
+ def test_namespaced_controller_dont_indent_blank_lines
+ run_generator
+ assert_file "app/controllers/test_app/account_controller.rb" do |content|
+ content.split("\n").each do |line|
+ assert_no_match(/^\s+$/, line, "Don't indent blank lines")
+ end
+ end
+ end
end
class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 4bb5f04a79..51374e5f3f 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -32,7 +32,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] }
assert_equal "Invalid plugin name things4.3. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
-
+
content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
end
@@ -211,7 +211,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_creating_gemspec
run_generator
assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
- assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*"\]/
+ assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.rdoc"\]/
assert_file "bukkits.gemspec", /s.test_files = Dir\["test\/\*\*\/\*"\]/
assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/
end
@@ -236,6 +236,13 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "test/dummy"
end
+ def test_creating_dummy_application_with_different_name
+ run_generator [destination_root, "--dummy_path", "spec/fake"]
+ assert_file "spec/fake"
+ assert_file "spec/fake/config/application.rb"
+ assert_no_file "test/dummy"
+ end
+
def test_creating_dummy_without_tests_but_with_dummy_path
run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"]
assert_file "spec/dummy"
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 7123950add..9b456c64ef 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -14,10 +14,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
assert_file "test/unit/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/product_lines.yml"
- assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/
- assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :product_id/
- assert_migration "db/migrate/create_product_lines.rb", /references :user/
- assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :user_id/
+ assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/
+ assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/
# Route
assert_file "config/routes.rb" do |route|
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 92117855b7..e78e67725d 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -104,13 +104,13 @@ module SharedGeneratorTests
generator([destination_root], :dev => true).expects(:bundle_command).with('install').once
quietly { generator.invoke_all }
rails_path = File.expand_path('../../..', Rails.root)
- assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/
+ assert_file 'Gemfile', /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
generator([destination_root], :edge => true).expects(:bundle_command).with('install').once
quietly { generator.invoke_all }
- assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("https://github.com/rails/rails.git")}["']$}
+ assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
end
def test_skip_gemfile
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index ac4c2abfc8..03be81e59f 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -8,7 +8,7 @@
# Rails booted up.
require 'fileutils'
-require 'rubygems'
+require 'bundler/setup'
require 'minitest/autorun'
require 'active_support/test_case'
@@ -17,9 +17,8 @@ RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
# These files do not require any others and are needed
# to run the tests
-require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/testing/isolation"
-require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/testing/declarative"
-require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/core_ext/kernel/reporting"
+require "active_support/testing/isolation"
+require "active_support/core_ext/kernel/reporting"
module TestHelpers
module Paths
@@ -112,7 +111,7 @@ module TestHelpers
routes = File.read("#{app_path}/config/routes.rb")
if routes =~ /(\n\s*end\s*)\Z/
File.open("#{app_path}/config/routes.rb", 'w') do |f|
- f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)'\n" + $1
+ f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', :via => :all\n" + $1
end
end
@@ -143,7 +142,7 @@ module TestHelpers
app.initialize!
app.routes.draw do
- match "/" => "omg#index"
+ get "/" => "omg#index"
end
require 'rack/test'
@@ -161,7 +160,7 @@ module TestHelpers
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
end
@@ -249,7 +248,6 @@ module TestHelpers
def use_frameworks(arr)
to_remove = [:actionmailer,
- :activemodel,
:activerecord] - arr
if to_remove.include? :activerecord
remove_from_config "config.active_record.whitelist_attributes = true"
@@ -268,7 +266,6 @@ class ActiveSupport::TestCase
include TestHelpers::Paths
include TestHelpers::Rack
include TestHelpers::Generation
- extend ActiveSupport::Testing::Declarative
end
# Create a scope and build a fixture rails app
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index d334034e7d..aa04cad033 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -29,6 +29,7 @@ class PathsTest < ActiveSupport::TestCase
test "creating a root level path" do
@root.add "app"
assert_equal ["/foo/bar/app"], @root["app"].to_a
+ assert_equal [Pathname.new("/foo/bar/app")], @root["app"].paths
end
test "creating a root level path with options" do
@@ -191,6 +192,7 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].glob = "*.rb"
assert_equal "*.rb", @root["app"].glob
+ assert_equal [Pathname.new("/app")], @root["app"].paths
end
test "it should be possible to override a path's default glob without assignment" do
diff --git a/railties/test/queueing/test_queue_test.rb b/railties/test/queueing/test_queue_test.rb
new file mode 100644
index 0000000000..78c6c617fe
--- /dev/null
+++ b/railties/test/queueing/test_queue_test.rb
@@ -0,0 +1,67 @@
+require 'abstract_unit'
+require 'rails/queueing'
+
+class TestQueueTest < ActiveSupport::TestCase
+ class Job
+ def initialize(&block)
+ @block = block
+ end
+
+ def run
+ @block.call if @block
+ end
+ end
+
+ def setup
+ @queue = Rails::Queueing::TestQueue.new
+ end
+
+ def test_drain_raises
+ @queue.push Job.new { raise }
+ assert_raises(RuntimeError) { @queue.drain }
+ end
+
+ def test_jobs
+ @queue.push 1
+ @queue.push 2
+ assert_equal [1,2], @queue.jobs
+ end
+
+ def test_contents
+ assert @queue.empty?
+ job = Job.new
+ @queue.push job
+ refute @queue.empty?
+ assert_equal job, @queue.pop
+ end
+
+ def test_order
+ processed = []
+
+ job1 = Job.new { processed << 1 }
+ job2 = Job.new { processed << 2 }
+
+ @queue.push job1
+ @queue.push job2
+ @queue.drain
+
+ assert_equal [1,2], processed
+ end
+
+ def test_drain
+ t = nil
+ ran = false
+
+ job = Job.new do
+ ran = true
+ t = Thread.current
+ end
+
+ @queue.push job
+ @queue.drain
+
+ assert @queue.empty?
+ assert ran, "The job runs synchronously when the queue is drained"
+ assert_not_equal t, Thread.current
+ end
+end
diff --git a/railties/test/queueing/threaded_consumer_test.rb b/railties/test/queueing/threaded_consumer_test.rb
new file mode 100644
index 0000000000..c34a966d6e
--- /dev/null
+++ b/railties/test/queueing/threaded_consumer_test.rb
@@ -0,0 +1,100 @@
+require 'abstract_unit'
+require 'rails/queueing'
+
+class TestThreadConsumer < ActiveSupport::TestCase
+ class Job
+ attr_reader :id
+ def initialize(id, &block)
+ @id = id
+ @block = block
+ end
+
+ def run
+ @block.call if @block
+ end
+ end
+
+ def setup
+ @queue = Rails::Queueing::Queue.new
+ @consumer = Rails::Queueing::ThreadedConsumer.start(@queue)
+ end
+
+ def teardown
+ @queue.push nil
+ end
+
+ test "the jobs are executed" do
+ ran = false
+
+ job = Job.new(1) do
+ ran = true
+ end
+
+ @queue.push job
+ sleep 0.1
+ assert_equal true, ran
+ end
+
+ test "the jobs are not executed synchronously" do
+ ran = false
+
+ job = Job.new(1) do
+ sleep 0.1
+ ran = true
+ end
+
+ @queue.push job
+ assert_equal false, ran
+ end
+
+ test "shutting down the queue synchronously drains the jobs" do
+ ran = false
+
+ job = Job.new(1) do
+ sleep 0.1
+ ran = true
+ end
+
+ @queue.push job
+ assert_equal false, ran
+
+ @consumer.shutdown
+
+ assert_equal true, ran
+ end
+
+ test "log job that raises an exception" do
+ require "active_support/log_subscriber/test_helper"
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ Rails.logger = logger
+
+ job = Job.new(1) do
+ raise "RuntimeError: Error!"
+ end
+
+ @queue.push job
+ sleep 0.1
+
+ assert_equal 1, logger.logged(:error).size
+ assert_match(/Job Error: RuntimeError: Error!/, logger.logged(:error).last)
+ end
+
+ test "test overriding exception handling" do
+ @consumer.shutdown
+ @consumer = Class.new(Rails::Queueing::ThreadedConsumer) do
+ attr_reader :last_error
+ def handle_exception(e)
+ @last_error = e.message
+ end
+ end.start(@queue)
+
+ job = Job.new(1) do
+ raise "RuntimeError: Error!"
+ end
+
+ @queue.push job
+ sleep 0.1
+
+ assert_equal "RuntimeError: Error!", @consumer.last_error
+ end
+end
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 8a9363fb80..f7a30a16d2 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -11,7 +11,7 @@ class InfoControllerTest < ActionController::TestCase
def setup
Rails.application.routes.draw do
- match '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/properties' => "rails/info#properties"
end
@request.stubs(:local? => true)
@controller.stubs(:consider_all_requests_local? => false)
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 1da66062aa..b9fb071d23 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -43,7 +43,7 @@ class InfoTest < ActiveSupport::TestCase
def test_frameworks_exist
Rails::Info.frameworks.each do |framework|
- dir = File.dirname(__FILE__) + "/../../" + framework.gsub('_', '')
+ dir = File.dirname(__FILE__) + "/../../" + framework.delete('_')
assert File.directory?(dir), "#{framework.classify} does not exist"
end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 5e93a8e783..55f72f532f 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -86,7 +86,6 @@ module RailtiesTest
add_to_config "ActiveRecord::Base.timestamped_migrations = false"
boot_rails
- railties = Rails.application.railties.all.map(&:railtie_name)
Dir.chdir(app_path) do
output = `bundle exec rake bukkits:install:migrations`
@@ -112,6 +111,37 @@ module RailtiesTest
end
end
+ test "mountable engine should copy migrations within engine_path" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ @plugin.write "db/migrate/0_add_first_name_to_users.rb", <<-RUBY
+ class AddFirstNameToUsers < ActiveRecord::Migration
+ end
+ RUBY
+
+ @plugin.write "Rakefile", <<-RUBY
+ APP_RAKEFILE = '#{app_path}/Rakefile'
+ load 'rails/tasks/engine.rake'
+ RUBY
+
+ add_to_config "ActiveRecord::Base.timestamped_migrations = false"
+
+ boot_rails
+
+ Dir.chdir(@plugin.path) do
+ output = `bundle exec rake app:bukkits:install:migrations`
+ assert File.exists?("#{app_path}/db/migrate/0_add_first_name_to_users.bukkits.rb")
+ assert_match(/Copied migration 0_add_first_name_to_users.bukkits.rb from bukkits/, output)
+ assert_equal 1, Dir["#{app_path}/db/migrate/*.rb"].length
+ end
+ end
+
test "no rake task without migrations" do
boot_rails
require 'rake'
@@ -218,7 +248,7 @@ module RailtiesTest
end
Rails.application.routes.draw do
- match "/sprokkit", :to => Sprokkit
+ get "/sprokkit", :to => Sprokkit
end
RUBY
@@ -241,7 +271,7 @@ module RailtiesTest
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match 'foo', :to => 'foo#index'
+ get 'foo', :to => 'foo#index'
end
RUBY
@@ -255,8 +285,8 @@ module RailtiesTest
@plugin.write "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
- match 'foo', :to => 'bar#index'
- match 'bar', :to => 'bar#index'
+ get 'foo', :to => 'bar#index'
+ get 'bar', :to => 'bar#index'
end
RUBY
@@ -336,7 +366,7 @@ YAML
Rails.application.routes.draw do
namespace :admin do
namespace :foo do
- match "bar", :to => "bar#index"
+ get "bar", :to => "bar#index"
end
end
end
@@ -491,7 +521,7 @@ YAML
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Engine.routes.draw do
- match "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, ['foo']] }
+ get "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, ['foo']] }
end
RUBY
@@ -507,10 +537,11 @@ YAML
assert_equal "foo", last_response.body
end
- test "it loads its environment file" do
+ test "it loads its environments file" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
class Engine < ::Rails::Engine
+ config.paths["config/environments"].push "config/environments/additional.rb"
end
end
RUBY
@@ -521,9 +552,16 @@ YAML
end
RUBY
+ @plugin.write "config/environments/additional.rb", <<-RUBY
+ Bukkits::Engine.configure do
+ config.additional_environment_loaded = true
+ end
+ RUBY
+
boot_rails
assert Bukkits::Engine.config.environment_loaded
+ assert Bukkits::Engine.config.additional_environment_loaded
end
test "it passes router in env" do
@@ -570,18 +608,18 @@ YAML
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match "/bar" => "bar#index", :as => "bar"
+ get "/bar" => "bar#index", :as => "bar"
mount Bukkits::Engine => "/bukkits", :as => "bukkits"
end
RUBY
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Engine.routes.draw do
- match "/foo" => "foo#index", :as => "foo"
- match "/foo/show" => "foo#show"
- match "/from_app" => "foo#from_app"
- match "/routes_helpers_in_view" => "foo#routes_helpers_in_view"
- match "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace"
+ get "/foo" => "foo#index", :as => "foo"
+ get "/foo/show" => "foo#show"
+ get "/from_app" => "foo#from_app"
+ get "/routes_helpers_in_view" => "foo#routes_helpers_in_view"
+ get "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace"
resources :posts
end
RUBY
@@ -738,7 +776,7 @@ YAML
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Awesome::Engine.routes.draw do
- match "/foo" => "foo#index"
+ get "/foo" => "foo#index"
end
RUBY
@@ -1008,8 +1046,8 @@ YAML
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
- match "/foo" => "main#foo"
- match "/bar" => "main#bar"
+ get "/foo" => "main#foo"
+ get "/bar" => "main#bar"
end
RUBY
@@ -1080,7 +1118,7 @@ YAML
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
- match "/foo" => "main#foo"
+ get "/foo" => "main#foo"
end
RUBY
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 2bb9df6b64..4c0fdee556 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -18,13 +18,13 @@ module ApplicationTests
AppTemplate::Application.routes.draw do
mount Weblog::Engine, :at => '/', :as => 'weblog'
resources :posts
- match "/engine_route" => "application_generating#engine_route"
- match "/engine_route_in_view" => "application_generating#engine_route_in_view"
- match "/weblog_engine_route" => "application_generating#weblog_engine_route"
- match "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view"
- match "/url_for_engine_route" => "application_generating#url_for_engine_route"
- match "/polymorphic_route" => "application_generating#polymorphic_route"
- match "/application_polymorphic_path" => "application_generating#application_polymorphic_path"
+ get "/engine_route" => "application_generating#engine_route"
+ get "/engine_route_in_view" => "application_generating#engine_route_in_view"
+ get "/weblog_engine_route" => "application_generating#weblog_engine_route"
+ get "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view"
+ get "/url_for_engine_route" => "application_generating#url_for_engine_route"
+ get "/polymorphic_route" => "application_generating#polymorphic_route"
+ get "/application_polymorphic_path" => "application_generating#application_polymorphic_path"
scope "/:user", :user => "anonymous" do
mount Blog::Engine => "/blog"
end
@@ -42,7 +42,7 @@ module ApplicationTests
@simple_plugin.write "config/routes.rb", <<-RUBY
Weblog::Engine.routes.draw do
- match '/weblog' => "weblogs#index", :as => 'weblogs'
+ get '/weblog' => "weblogs#index", :as => 'weblogs'
end
RUBY
@@ -86,9 +86,9 @@ module ApplicationTests
@plugin.write "config/routes.rb", <<-RUBY
Blog::Engine.routes.draw do
resources :posts
- match '/generate_application_route', :to => 'posts#generate_application_route'
- match '/application_route_in_view', :to => 'posts#application_route_in_view'
- match '/engine_polymorphic_path', :to => 'posts#engine_polymorphic_path'
+ get '/generate_application_route', :to => 'posts#generate_application_route'
+ get '/application_route_in_view', :to => 'posts#application_route_in_view'
+ get '/engine_polymorphic_path', :to => 'posts#engine_polymorphic_path'
end
RUBY
diff --git a/tools/profile b/tools/profile
index 51cb7f33e8..6cc935bce5 100755
--- a/tools/profile
+++ b/tools/profile
@@ -7,7 +7,6 @@ ENV['NO_RELOAD'] ||= '1'
ENV['RAILS_ENV'] ||= 'development'
GC.enable_stats
-require 'rubygems'
Gem.source_index
require 'benchmark'