aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Gemfile20
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md78
-rw-r--r--Rakefile95
-rw-r--r--actionmailer/CHANGELOG.md61
-rw-r--r--actionmailer/README.rdoc13
-rw-r--r--actionmailer/Rakefile1
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb116
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb1
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb5
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb6
-rw-r--r--actionmailer/lib/action_mailer/version.rb2
-rw-r--r--actionmailer/test/asset_host_test.rb1
-rw-r--r--actionmailer/test/mailers/base_mailer.rb2
-rw-r--r--actionmailer/test/url_test.rb9
-rw-r--r--actionpack/CHANGELOG.md1137
-rw-r--r--actionpack/Rakefile11
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb4
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb4
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb1
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb2
-rw-r--r--actionpack/lib/action_controller.rb4
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb5
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb69
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb1
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb4
-rw-r--r--actionpack/lib/action_controller/metal/live.rb29
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb1
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb2
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb3
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb31
-rw-r--r--actionpack/lib/action_controller/test_case.rb37
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner.rb5
-rw-r--r--actionpack/lib/action_dispatch.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb30
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb18
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb25
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb70
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y1
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb166
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb96
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb2
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb24
-rw-r--r--actionpack/lib/action_dispatch/routing.rb104
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb64
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb12
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb2
-rw-r--r--actionpack/lib/action_view/buffers.rb6
-rw-r--r--actionpack/lib/action_view/dependency_tracker.rb4
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb29
-rw-r--r--actionpack/lib/action_view/helpers/asset_url_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb73
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb43
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb42
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb25
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb19
-rw-r--r--actionpack/lib/action_view/helpers/tags/check_box.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tags/checkable.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb6
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_helpers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb6
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_select.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tags/color_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_select.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_local_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_select.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/email_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/file_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/hidden_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/label.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/month_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/number_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/password_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/radio_button.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/range_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/search_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/select.rb5
-rw-r--r--actionpack/lib/action_view/helpers/tags/tel_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_area.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_select.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_zone_select.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/url_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/week_field.rb4
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb42
-rw-r--r--actionpack/lib/action_view/lookup_context.rb1
-rw-r--r--actionpack/lib/action_view/path_set.rb6
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb15
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb14
-rw-r--r--actionpack/lib/action_view/renderer/renderer.rb6
-rw-r--r--actionpack/lib/action_view/template.rb19
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb32
-rw-r--r--actionpack/lib/action_view/template/resolver.rb4
-rw-r--r--actionpack/lib/action_view/test_case.rb4
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/selector.rb16
-rw-r--r--actionpack/test/abstract/layouts_test.rb6
-rw-r--r--actionpack/test/abstract_unit.rb2
-rw-r--r--actionpack/test/controller/base_test.rb32
-rw-r--r--actionpack/test/controller/filters_test.rb15
-rw-r--r--actionpack/test/controller/flash_test.rb3
-rw-r--r--actionpack/test/controller/force_ssl_test.rb175
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb3
-rw-r--r--actionpack/test/controller/integration_test.rb18
-rw-r--r--actionpack/test/controller/live_stream_test.rb76
-rw-r--r--actionpack/test/controller/new_base/render_partial_test.rb22
-rw-r--r--actionpack/test/controller/new_base/render_test.rb24
-rw-r--r--actionpack/test/controller/record_identifier_test.rb34
-rw-r--r--actionpack/test/controller/render_test.rb21
-rw-r--r--actionpack/test/controller/routing_test.rb8
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb20
-rw-r--r--actionpack/test/controller/test_case_test.rb5
-rw-r--r--actionpack/test/controller/url_for_test.rb7
-rw-r--r--actionpack/test/dispatch/cookies_test.rb93
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb6
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb7
-rw-r--r--actionpack/test/dispatch/rack_test.rb16
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb9
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb11
-rw-r--r--actionpack/test/dispatch/routing_test.rb115
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb3
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb12
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb41
-rw-r--r--actionpack/test/fixtures/digestor/messages/show.html.erb3
-rw-r--r--actionpack/test/fixtures/public/400.html1
-rw-r--r--actionpack/test/fixtures/public/images/rails.pngbin1787 -> 0 bytes
-rw-r--r--actionpack/test/fixtures/test/change_priority.html.erb (renamed from actionpack/test/fixtures/test/change_priorty.html.erb)0
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb23
-rw-r--r--actionpack/test/template/dependency_tracker_test.rb54
-rw-r--r--actionpack/test/template/digestor_test.rb6
-rw-r--r--actionpack/test/template/form_helper_test.rb10
-rw-r--r--actionpack/test/template/form_options_helper_test.rb58
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb28
-rw-r--r--actionpack/test/template/javascript_helper_test.rb42
-rw-r--r--actionpack/test/template/render_test.rb10
-rw-r--r--actionpack/test/template/template_test.rb7
-rw-r--r--actionpack/test/template/url_helper_test.rb99
-rw-r--r--actionpack/test/ts_isolated.rb15
-rw-r--r--activemodel/CHANGELOG.md201
-rw-r--r--activemodel/README.rdoc16
-rw-r--r--activemodel/Rakefile1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb62
-rw-r--r--activemodel/lib/active_model/callbacks.rb41
-rw-r--r--activemodel/lib/active_model/dirty.rb24
-rw-r--r--activemodel/lib/active_model/errors.rb20
-rw-r--r--activemodel/lib/active_model/lint.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb4
-rw-r--r--activemodel/lib/active_model/secure_password.rb23
-rw-r--r--activemodel/lib/active_model/serializers/json.rb2
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb10
-rw-r--r--activemodel/lib/active_model/translation.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb7
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb6
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb5
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb10
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb4
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb10
-rw-r--r--activemodel/lib/active_model/validations/validates.rb4
-rw-r--r--activemodel/lib/active_model/validations/with.rb3
-rw-r--r--activemodel/lib/active_model/validator.rb32
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb4
-rw-r--r--activemodel/test/cases/callbacks_test.rb6
-rw-r--r--activemodel/test/cases/conversion_test.rb4
-rw-r--r--activemodel/test/cases/errors_test.rb98
-rw-r--r--activemodel/test/cases/model_test.rb4
-rw-r--r--activemodel/test/cases/railtie_test.rb5
-rw-r--r--activemodel/test/cases/secure_password_test.rb9
-rw-r--r--activemodel/test/cases/serialization_test.rb34
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb2
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb58
-rw-r--r--activemodel/test/cases/translation_test.rb43
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb21
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb34
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb28
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb50
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb117
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb26
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb46
-rw-r--r--activemodel/test/cases/validations/validates_test.rb36
-rw-r--r--activemodel/test/cases/validations/validations_context_test.rb12
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb58
-rw-r--r--activemodel/test/cases/validations_test.rb69
-rw-r--r--activemodel/test/models/automobile.rb4
-rw-r--r--activemodel/test/models/contact.rb2
-rw-r--r--activemodel/test/models/reply.rb6
-rw-r--r--activerecord/CHANGELOG.md1921
-rw-r--r--activerecord/README.rdoc6
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc42
-rw-r--r--activerecord/Rakefile3
-rw-r--r--activerecord/activerecord.gemspec4
-rw-r--r--activerecord/examples/associations.pngbin40623 -> 0 bytes
-rw-r--r--activerecord/lib/active_record.rb11
-rw-r--r--activerecord/lib/active_record/association_relation.rb18
-rw-r--r--activerecord/lib/active_record/associations.rb78
-rw-r--r--activerecord/lib/active_record/associations/association.rb26
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb28
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb118
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb22
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb19
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb48
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb40
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb12
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb8
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb19
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb26
-rw-r--r--activerecord/lib/active_record/autosave_association.rb30
-rw-r--r--activerecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb81
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_handling.rb14
-rw-r--r--activerecord/lib/active_record/core.rb18
-rw-r--r--activerecord/lib/active_record/errors.rb30
-rw-r--r--activerecord/lib/active_record/explain.rb15
-rw-r--r--activerecord/lib/active_record/explain_registry.rb30
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/fixtures.rb20
-rw-r--r--activerecord/lib/active_record/inheritance.rb9
-rw-r--r--activerecord/lib/active_record/integration.rb4
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb54
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb5
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb112
-rw-r--r--activerecord/lib/active_record/null_relation.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb70
-rw-r--r--activerecord/lib/active_record/querying.rb9
-rw-r--r--activerecord/lib/active_record/railtie.rb14
-rw-r--r--activerecord/lib/active_record/railties/databases.rake134
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb6
-rw-r--r--activerecord/lib/active_record/reflection.rb176
-rw-r--r--activerecord/lib/active_record/relation.rb106
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb58
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb6
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb106
-rw-r--r--activerecord/lib/active_record/relation/merger.rb84
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb18
-rw-r--r--activerecord/lib/active_record/runtime_registry.rb17
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb5
-rw-r--r--activerecord/lib/active_record/schema_migration.rb42
-rw-r--r--activerecord/lib/active_record/scoping.rb58
-rw-r--r--activerecord/lib/active_record/scoping/default.rb15
-rw-r--r--activerecord/lib/active_record/scoping/named.rb9
-rw-r--r--activerecord/lib/active_record/statement_cache.rb26
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb73
-rw-r--r--activerecord/lib/active_record/tasks/firebird_database_tasks.rb56
-rw-r--r--activerecord/lib/active_record/tasks/oracle_database_tasks.rb45
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb48
-rw-r--r--activerecord/lib/active_record/test_case.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb12
-rw-r--r--activerecord/lib/active_record/validations/associated.rb6
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb7
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb27
-rw-r--r--activerecord/test/cases/adapters/mysql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb19
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb27
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb104
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb66
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb70
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb42
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb4
-rw-r--r--activerecord/test/cases/aggregations_test.rb1
-rw-r--r--activerecord/test/cases/ar_schema_test.rb20
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb22
-rw-r--r--activerecord/test/cases/associations/eager_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb23
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb8
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb114
-rw-r--r--activerecord/test/cases/associations_test.rb16
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb27
-rw-r--r--activerecord/test/cases/autosave_association_test.rb22
-rw-r--r--activerecord/test/cases/base_test.rb161
-rw-r--r--activerecord/test/cases/batches_test.rb12
-rw-r--r--activerecord/test/cases/calculations_test.rb68
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/clone_test.rb7
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb14
-rw-r--r--activerecord/test/cases/column_test.rb36
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb9
-rw-r--r--activerecord/test/cases/core_test.rb33
-rw-r--r--activerecord/test/cases/counter_cache_test.rb12
-rw-r--r--activerecord/test/cases/dirty_test.rb8
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb64
-rw-r--r--activerecord/test/cases/finder_test.rb24
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/integration_test.rb87
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb28
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb31
-rw-r--r--activerecord/test/cases/migration/columns_test.rb6
-rw-r--r--activerecord/test/cases/migration/index_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb35
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb16
-rw-r--r--activerecord/test/cases/persistence_test.rb17
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/readonly_test.rb18
-rw-r--r--activerecord/test/cases/reflection_test.rb6
-rw-r--r--activerecord/test/cases/relation/where_test.rb8
-rw-r--r--activerecord/test/cases/relation_test.rb24
-rw-r--r--activerecord/test/cases/relations_test.rb35
-rw-r--r--activerecord/test/cases/sanitize_test.rb9
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb15
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb (renamed from activerecord/test/cases/relation_scoping_test.rb)340
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb (renamed from activerecord/test/cases/named_scope_test.rb)7
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb331
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb20
-rw-r--r--activerecord/test/cases/statement_cache_test.rb64
-rw-r--r--activerecord/test/cases/store_test.rb5
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb7
-rw-r--r--activerecord/test/cases/tasks/firebird_rake_test.rb100
-rw-r--r--activerecord/test/cases/tasks/oracle_rake_test.rb93
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb4
-rw-r--r--activerecord/test/cases/tasks/sqlserver_rake_test.rb87
-rw-r--r--activerecord/test/cases/timestamp_test.rb100
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb32
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb17
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/fixtures/pets.yml5
-rw-r--r--activerecord/test/fixtures/toys.yml10
-rw-r--r--activerecord/test/models/club.rb2
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/member.rb2
-rw-r--r--activerecord/test/models/member_detail.rb2
-rw-r--r--activerecord/test/models/owner.rb2
-rw-r--r--activerecord/test/models/person.rb1
-rw-r--r--activerecord/test/models/pet.rb2
-rw-r--r--activerecord/test/models/reply.rb1
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb2
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb1
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activesupport/CHANGELOG.md503
-rw-r--r--activesupport/Rakefile9
-rw-r--r--activesupport/activesupport.gemspec4
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb10
-rw-r--r--activesupport/lib/active_support/cache.rb133
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb85
-rw-r--r--activesupport/lib/active_support/callbacks.rb648
-rw-r--r--activesupport/lib/active_support/concern.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/uniq_by.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/benchmark.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date/infinite_comparable.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/diff.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/infinite_comparable.rb35
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb74
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.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/conversions.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/indent.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/xchar.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/time/infinite_comparable.rb5
-rw-r--r--activesupport/lib/active_support/dependencies.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb6
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb26
-rw-r--r--activesupport/lib/active_support/i18n.rb8
-rw-r--r--activesupport/lib/active_support/json/decoding.rb25
-rw-r--r--activesupport/lib/active_support/json/encoding.rb14
-rw-r--r--activesupport/lib/active_support/json/variable.rb18
-rw-r--r--activesupport/lib/active_support/key_generator.rb4
-rw-r--r--activesupport/lib/active_support/lazy_load_hooks.rb2
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb55
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb18
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb64
-rw-r--r--activesupport/lib/active_support/notifications.rb23
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb7
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb5
-rw-r--r--activesupport/lib/active_support/ordered_options.rb8
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb52
-rw-r--r--activesupport/lib/active_support/subscriber.rb93
-rw-r--r--activesupport/lib/active_support/test_case.rb28
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb4
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb21
-rw-r--r--activesupport/lib/active_support/testing/pending.rb14
-rw-r--r--activesupport/lib/active_support/testing/tagged_logging.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb6
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb3
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb2
-rw-r--r--activesupport/test/caching_test.rb53
-rw-r--r--activesupport/test/callback_inheritance_test.rb2
-rw-r--r--activesupport/test/callbacks_test.rb295
-rw-r--r--activesupport/test/concern_test.rb14
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/class/attribute_accessor_test.rb10
-rw-r--r--activesupport/test/core_ext/class/attribute_test.rb9
-rw-r--r--activesupport/test/core_ext/date_and_time_behavior.rb5
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb9
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb17
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb6
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb16
-rw-r--r--activesupport/test/core_ext/module_test.rb36
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb56
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb10
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb28
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb120
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb29
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb20
-rw-r--r--activesupport/test/dependencies/raises_exception_without_blame_file.rb5
-rw-r--r--activesupport/test/dependencies_test.rb9
-rw-r--r--activesupport/test/descendants_tracker_with_autoloading_test.rb6
-rw-r--r--activesupport/test/json/decoding_test.rb34
-rw-r--r--activesupport/test/json/encoding_test.rb28
-rw-r--r--activesupport/test/load_paths_test.rb4
-rw-r--r--activesupport/test/message_encryptor_test.rb7
-rw-r--r--activesupport/test/message_verifier_test.rb7
-rw-r--r--activesupport/test/notifications_test.rb24
-rw-r--r--activesupport/test/ordered_hash_test.rb6
-rw-r--r--activesupport/test/ordered_options_test.rb20
-rw-r--r--activesupport/test/rescuable_test.rb2
-rw-r--r--activesupport/test/test_case_test.rb118
-rw-r--r--activesupport/test/test_test.rb2
-rw-r--r--activesupport/test/ts_isolated.rb16
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb9
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb11
-rw-r--r--activesupport/test/xml_mini/libxmlsax_engine_test.rb11
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb11
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb17
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb9
-rw-r--r--guides/CHANGELOG.md13
-rw-r--r--guides/assets/images/getting_started/challenge.png (renamed from guides/assets/images/challenge.png)bin33373 -> 33373 bytes
-rw-r--r--guides/assets/images/getting_started/rails_welcome.pngbin0 -> 50433 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_create_for_posts.pngbin31314 -> 17698 bytes
-rw-r--r--guides/assets/images/icons/README2
-rw-r--r--guides/assets/images/rails_welcome.pngbin71979 -> 0 bytes
-rw-r--r--guides/bug_report_templates/active_record_gem.rb37
-rw-r--r--guides/bug_report_templates/active_record_master.rb48
-rw-r--r--guides/code/getting_started/.gitignore2
-rw-r--r--guides/code/getting_started/Gemfile25
-rw-r--r--guides/code/getting_started/Gemfile.lock2
-rw-r--r--guides/code/getting_started/app/assets/images/rails.pngbin6646 -> 0 bytes
-rw-r--r--guides/code/getting_started/app/assets/javascripts/application.js3
-rw-r--r--guides/code/getting_started/config/application.rb5
-rw-r--r--guides/code/getting_started/config/environment.rb4
-rw-r--r--guides/code/getting_started/config/initializers/session_store.rb2
-rw-r--r--guides/rails_guides.rb2
-rw-r--r--guides/rails_guides/markdown/renderer.rb2
-rw-r--r--guides/source/2_2_release_notes.md2
-rw-r--r--guides/source/2_3_release_notes.md4
-rw-r--r--guides/source/3_0_release_notes.md9
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md6
-rw-r--r--guides/source/4_0_release_notes.md27
-rw-r--r--guides/source/action_controller_overview.md71
-rw-r--r--guides/source/action_mailer_basics.md350
-rw-r--r--guides/source/action_view_overview.md11
-rw-r--r--guides/source/active_model_basics.md10
-rw-r--r--guides/source/active_record_basics.md193
-rw-r--r--guides/source/active_record_querying.md24
-rw-r--r--guides/source/active_record_validations.md58
-rw-r--r--guides/source/active_support_core_extensions.md23
-rw-r--r--guides/source/active_support_instrumentation.md10
-rw-r--r--guides/source/asset_pipeline.md30
-rw-r--r--guides/source/association_basics.md49
-rw-r--r--guides/source/caching_with_rails.md8
-rw-r--r--guides/source/command_line.md16
-rw-r--r--guides/source/configuring.md8
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md45
-rw-r--r--guides/source/credits.html.erb6
-rw-r--r--guides/source/debugging_rails_applications.md43
-rw-r--r--guides/source/development_dependencies_install.md18
-rw-r--r--guides/source/documents.yaml6
-rw-r--r--guides/source/engines.md54
-rw-r--r--guides/source/form_helpers.md9
-rw-r--r--guides/source/generators.md4
-rw-r--r--guides/source/getting_started.md275
-rw-r--r--guides/source/i18n.md30
-rw-r--r--guides/source/initialization.md72
-rw-r--r--guides/source/layout.html.erb4
-rw-r--r--guides/source/layouts_and_rendering.md89
-rw-r--r--guides/source/migrations.md33
-rw-r--r--guides/source/rails_application_templates.md22
-rw-r--r--guides/source/rails_on_rack.md15
-rw-r--r--guides/source/routing.md12
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md6
-rw-r--r--guides/source/security.md10
-rw-r--r--guides/source/testing.md87
-rw-r--r--guides/source/upgrading_ruby_on_rails.md113
-rw-r--r--guides/source/working_with_javascript_in_rails.md1
-rw-r--r--rails.gemspec6
-rw-r--r--railties/CHANGELOG.md353
-rw-r--r--railties/RDOC_MAIN.rdoc (renamed from README.rdoc)34
-rw-r--r--railties/Rakefile7
-rwxr-xr-xrailties/bin/rails4
-rw-r--r--railties/lib/rails.rb26
-rw-r--r--railties/lib/rails/all.rb4
-rw-r--r--railties/lib/rails/api/task.rb158
-rw-r--r--railties/lib/rails/app_rails_loader.rb64
-rw-r--r--railties/lib/rails/application.rb17
-rw-r--r--railties/lib/rails/application/finisher.rb35
-rw-r--r--railties/lib/rails/commands.rb11
-rw-r--r--railties/lib/rails/commands/application.rb2
-rw-r--r--railties/lib/rails/commands/console.rb4
-rw-r--r--railties/lib/rails/commands/server.rb3
-rw-r--r--railties/lib/rails/commands/test_runner.rb146
-rw-r--r--railties/lib/rails/engine.rb22
-rw-r--r--railties/lib/rails/engine/commands.rb2
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb61
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb14
-rw-r--r--railties/lib/rails/generators/named_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.pngbin6646 -> 0 bytes
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environment.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb5
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE32
-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%.gemspec3
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js4
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_case.rb217
-rw-r--r--railties/lib/rails/generators/testing/assertions.rb121
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb106
-rw-r--r--railties/lib/rails/generators/testing/setup_and_teardown.rb18
-rw-r--r--railties/lib/rails/rack/debugger.rb4
-rw-r--r--railties/lib/rails/railtie.rb40
-rw-r--r--railties/lib/rails/tasks.rb2
-rw-r--r--railties/lib/rails/tasks/documentation.rake156
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb2
-rw-r--r--railties/lib/rails/test_unit/railtie.rb4
-rw-r--r--railties/lib/rails/test_unit/sub_test_task.rb79
-rw-r--r--railties/lib/rails/test_unit/testing.rake45
-rw-r--r--railties/lib/rails/version.rb13
-rw-r--r--railties/railties.gemspec5
-rw-r--r--railties/test/app_rails_loader_test.rb82
-rw-r--r--railties/test/application/assets_test.rb9
-rw-r--r--railties/test/application/configuration_test.rb5
-rw-r--r--railties/test/application/console_test.rb7
-rw-r--r--railties/test/application/initializers/load_path_test.rb1
-rw-r--r--railties/test/application/loading_test.rb57
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb3
-rw-r--r--railties/test/application/middleware/session_test.rb78
-rw-r--r--railties/test/application/middleware_test.rb8
-rw-r--r--railties/test/application/rake_test.rb55
-rw-r--r--railties/test/application/rendering_test.rb45
-rw-r--r--railties/test/application/routing_test.rb52
-rw-r--r--railties/test/application/test_runner_test.rb110
-rw-r--r--railties/test/application/url_generation_test.rb2
-rw-r--r--railties/test/commands/console_test.rb5
-rw-r--r--railties/test/commands/dbconsole_test.rb4
-rw-r--r--railties/test/generators/actions_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb21
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb6
-rw-r--r--railties/test/generators/scaffold_generator_test.rb16
-rw-r--r--railties/test/generators/shared_generator_tests.rb4
-rw-r--r--railties/test/generators_test.rb2
-rw-r--r--railties/test/initializable_test.rb10
-rw-r--r--railties/test/rails_info_test.rb5
-rw-r--r--railties/test/railties/engine_test.rb21
-rw-r--r--railties/test/railties/generators_test.rb2
-rw-r--r--railties/test/railties/mounted_engine_test.rb45
-rw-r--r--railties/test/railties/railtie_test.rb27
-rw-r--r--railties/test/test_info_test.rb59
-rw-r--r--tasks/release.rb21
-rw-r--r--version.rb12
674 files changed, 10490 insertions, 10135 deletions
diff --git a/.gitignore b/.gitignore
index 4c2c8de10c..bc96284375 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
# Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore.
-# Check out http://help.github.com/ignore-files/ for how to set that up.
+# Check out https://help.github.com/articles/ignoring-files for how to set that up.
debug.log
.Gemfile
@@ -20,4 +20,3 @@ pkg
/railties/doc
/railties/tmp
/guides/output
-/RDOC_MAIN.rdoc
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6d3cf072c1..19b7b638b6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,6 +4,8 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea
* If you want to submit a patch, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide.
+* If you want to contribute to Rails documentation, please read the [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation) section of the aforementioned guide.
+
*We only accept bug reports and pull requests in GitHub*.
* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
diff --git a/Gemfile b/Gemfile
index 9abc8a5d66..b0f91c6dac 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,20 +2,23 @@ source 'https://rubygems.org'
gemspec
-gem 'mocha', '~> 0.13.0', require: false
+# This needs to be with require false as it is
+# loaded after loading the test library to
+# ensure correct loading order
+gem 'mocha', '~> 0.14', require: false
+
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.0.0'
-gem 'jquery-rails', github: 'rails/jquery-rails'
+gem 'jquery-rails', '~> 2.2.0'
gem 'turbolinks'
-gem 'coffee-rails', '~> 4.0.0.beta1'
-gem 'thor', github: 'wycats/thor'
+gem 'coffee-rails', '~> 4.0.0'
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
-gem 'uglifier', require: false
+gem 'uglifier', '>= 1.3.0', require: false
group :doc do
- gem 'sdoc', github: 'voloko/sdoc'
+ gem 'sdoc'
gem 'redcarpet', '~> 2.2.2', platforms: :ruby
gem 'w3c_validators'
gem 'kindlerb'
@@ -61,11 +64,6 @@ platforms :jruby do
gem 'json'
gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.7'
- # This is needed by now to let tests work on JRuby
- # TODO: When the JRuby guys merge jruby-openssl in
- # jruby this will be removed
- gem 'jruby-openssl'
-
group :db do
gem 'activerecord-jdbcmysql-adapter', '>= 1.2.7'
gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.7'
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 14e1b44259..14b6456d31 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-4.0.0.beta1
+4.1.0.beta
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..31ee4c1086
--- /dev/null
+++ b/README.md
@@ -0,0 +1,78 @@
+## Welcome to Rails
+
+Rails is a web-application framework that includes everything needed to
+create database-backed web applications according to the
+[Model-View-Controller (MVC)](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
+pattern.
+
+Understanding the MVC pattern is key to understanding Rails. MVC divides your
+application into three layers, each with a specific responsibility.
+
+The _View layer_ is composed of "templates" that are responsible for providing
+appropriate representations of your application's resources. Templates can
+come in a variety of formats, but most view templates are HTML with embedded
+Ruby code (ERB files).
+
+The _Model layer_ represents your domain model (such as Account, Product,
+Person, Post, etc.) and encapsulates the business logic that is specific to
+your application. In Rails, database-backed model classes are derived from
+`ActiveRecord::Base`. Active Record allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. Although most Rails models are backed by a database, models can also
+be ordinary Ruby classes, or Ruby classes that implement a set of interfaces
+as provided by the Active Model module. You can read more about Active Record
+in its [README](activerecord/README.rdoc).
+
+The _Controller layer_ is responsible for handling incoming HTTP requests and
+providing a suitable response. Usually this means returning HTML, but Rails
+controllers can also generate XML, JSON, PDFs, mobile-specific views, and
+more. Controllers manipulate models and render view templates in order to
+generate the appropriate HTTP response.
+
+In Rails, the Controller and View layers are handled together by Action Pack.
+These two layers are bundled in a single package due to their heavy interdependence.
+This is unlike the relationship between Active Record and Action Pack, which are
+independent. Each of these packages can be used independently outside of Rails. You
+can read more about Action Pack in its [README](actionpack/README.rdoc).
+
+## Getting Started
+
+1. Install Rails at the command prompt if you haven't yet:
+
+ gem install rails
+
+2. At the command prompt, create a new Rails application:
+
+ rails new myapp
+
+ where "myapp" is the application name.
+
+3. Change directory to `myapp` and start the web server:
+
+ cd myapp
+ rails server
+
+ Run with `--help` or `-h` for options.
+
+4. Go to http://localhost:3000 and you'll see: "Welcome aboard: You're riding Ruby on Rails!"
+
+5. Follow the guidelines to start developing your application. You may find
+ the following resources handy:
+ * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html)
+ * [Ruby on Rails Guides](http://guides.rubyonrails.org)
+ * [The API Documentation](http://api.rubyonrails.org)
+ * [Ruby on Rails Tutorial](http://ruby.railstutorial.org/ruby-on-rails-tutorial-book)
+
+## Contributing
+
+We encourage you to contribute to Ruby on Rails! Please check out the
+[Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org)
+
+## Code Status
+
+* [![Build Status](https://api.travis-ci.org/rails/rails.png)](https://travis-ci.org/rails/rails)
+* [![Dependencies](https://gemnasium.com/rails/rails.png?travis)](https://gemnasium.com/rails/rails)
+
+## License
+
+Ruby on Rails is released under the [MIT License](http://www.opensource.org/licenses/MIT).
diff --git a/Rakefile b/Rakefile
index e577c079df..d26e1d3a85 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,9 +1,9 @@
-require 'rdoc/task'
require 'sdoc'
require 'net/http'
$:.unshift File.expand_path('..', __FILE__)
require "tasks/release"
+require 'railties/lib/rails/api/task'
desc "Build gem files for all projects"
task :build => "all:build"
@@ -36,7 +36,7 @@ task :smoke do
end
desc "Install gems for all projects."
-task :install => :gem do
+task :install => :build do
version = File.read("RAILS_VERSION").strip
(PROJECTS - ["railties"]).each do |project|
puts "INSTALLING #{project}"
@@ -47,94 +47,7 @@ task :install => :gem do
end
desc "Generate documentation for the Rails framework"
-RDoc::Task.new do |rdoc|
- RDOC_MAIN = 'RDOC_MAIN.rdoc'
-
- # This is a hack.
- #
- # Backslashes are needed to prevent RDoc from autolinking "Rails" to the
- # documentation of the Rails module. On the other hand, as of this
- # writing README.rdoc is displayed in the front page of the project in
- # GitHub, where backslashes are shown and look weird.
- #
- # The temporary solution is to have a README.rdoc without backslashes for
- # GitHub, and gsub it to generate the main page of the API.
- #
- # Also, relative links in GitHub have to point to blobs, whereas in the API
- # they need to point to files.
- #
- # The idea for the future is to have totally different files, since the
- # API is no longer a generic entry point to Rails and deserves a
- # dedicated main page specifically thought as an API entry point.
- rdoc.before_running_rdoc do
- rdoc_main = File.read('README.rdoc')
-
- # The ^(?=\S) assertion prevents code blocks from being processed,
- # since no autolinking happens there and RDoc displays the backslash
- # otherwise.
- rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" }
- rdoc_main.gsub!(%r{link:/rails/rails/blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
-
- # Remove Travis and Gemnasium status images from API pages. Only the GitHub
- # README page gets these images. Travis's HTTPS build image is used to
- # avoid GitHub caching: http://about.travis-ci.org/docs/user/status-images
- rdoc_main.gsub!(/^== Code Status(\n(?!==).*)*/, '')
-
- File.open(RDOC_MAIN, 'w') do |f|
- f.write(rdoc_main)
- end
-
- rdoc.rdoc_files.include(RDOC_MAIN)
- end
-
- rdoc.rdoc_dir = 'doc/rdoc'
- rdoc.title = "Ruby on Rails Documentation"
-
- rdoc.options << '-f' << 'sdoc'
- rdoc.options << '-T' << 'rails'
- rdoc.options << '-e' << 'UTF-8'
- rdoc.options << '-g' # SDoc flag, link methods to GitHub
- rdoc.options << '-m' << RDOC_MAIN
-
- rdoc.rdoc_files.include('railties/CHANGELOG.md')
- rdoc.rdoc_files.include('railties/MIT-LICENSE')
- rdoc.rdoc_files.include('railties/README.rdoc')
- rdoc.rdoc_files.include('railties/lib/**/*.rb')
- rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb')
-
- rdoc.rdoc_files.include('activerecord/README.rdoc')
- rdoc.rdoc_files.include('activerecord/CHANGELOG.md')
- rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
- rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
-
- rdoc.rdoc_files.include('actionpack/README.rdoc')
- rdoc.rdoc_files.include('actionpack/CHANGELOG.md')
- rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb')
- rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')
- rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb')
- rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb')
- rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*')
-
- rdoc.rdoc_files.include('actionmailer/README.rdoc')
- rdoc.rdoc_files.include('actionmailer/CHANGELOG.md')
- rdoc.rdoc_files.include('actionmailer/lib/action_mailer/**/*.rb')
- rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
-
- rdoc.rdoc_files.include('activesupport/README.rdoc')
- rdoc.rdoc_files.include('activesupport/CHANGELOG.md')
- rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb')
- rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*')
-
- rdoc.rdoc_files.include('activemodel/README.rdoc')
- rdoc.rdoc_files.include('activemodel/CHANGELOG.md')
- rdoc.rdoc_files.include('activemodel/lib/active_model/**/*.rb')
-end
-
-# Enhance rdoc task to copy referenced images also
-task :rdoc do
- FileUtils.mkdir_p "doc/rdoc/files/examples/"
- FileUtils.copy "activerecord/examples/associations.png", "doc/rdoc/files/examples/associations.png"
-end
+Rails::API::RepoTask.new('rdoc')
desc 'Bump all versions to match version.rb'
task :update_versions do
@@ -165,7 +78,7 @@ task :update_versions do
end
#
-# We have a webhook configured in Github that gets invoked after pushes.
+# We have a webhook configured in GitHub that gets invoked after pushes.
# This hook triggers the following tasks:
#
# * updates the local checkout
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 487e57be7b..9e9d07b386 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,60 +1,3 @@
-## Rails 4.0.0 (unreleased) ##
+* No changes.
-
-## Rails 4.0.0.beta1 (February 25, 2013) ##
-
-* Allow passing interpolations to `#default_i18n_subject`, e.g.:
-
- # config/locales/en.yml
- en:
- user_mailer:
- welcome:
- subject: 'Hello, %{username}'
-
- # app/mailers/user_mailer.rb
- class UserMailer < ActionMailer::Base
- def welcome(user)
- mail(subject: default_i18n_subject(username: user.name))
- end
- end
-
- *Olek Janiszewski*
-
-* Eager loading made to use relation's `in_clause_length` instead of host's one.
- Fixes #8474.
-
- *Boris Staal*
-
-* Explicit multipart messages no longer set the order of the MIME parts.
- *Nate Berkopec*
-
-* Do not render views when mail() isn't called.
- Fixes #7761.
-
- *Yves Senn*
-
-* Allow delivery method options to be set per mail instance *Aditya Sanghi*
-
- If your smtp delivery settings are dynamic,
- you can now override settings per mail instance for e.g.
-
- def my_mailer(user,company)
- mail to: user.email, subject: "Welcome!",
- delivery_method_options: { user_name: company.smtp_user,
- password: company.smtp_password }
- end
-
- This will ensure that your default SMTP settings will be overridden
- by the company specific ones. You only have to override the settings
- that are dynamic and leave the static setting in your environment
- configuration file (e.g. config/environments/production.rb)
-
-* Allow to set default Action Mailer options via `config.action_mailer.default_options=` *Robert Pankowecki*
-
-* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu*
-
-* Allow callbacks to be defined in mailers similar to `ActionController::Base`. You can configure default
- settings, headers, attachments, delivery settings or change delivery using
- `before_filter`, `after_filter` etc. *Justin S. Leitgeb*
-
-Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/actionmailer/CHANGELOG.md) for previous changes.
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 9feb2add5b..a14a6ba18f 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -1,6 +1,6 @@
= Action Mailer -- Easy email delivery and testing
-Action Mailer is a framework for designing email-service layers. These layers
+Action Mailer is a framework for designing email service layers. These layers
are used to consolidate code for sending out forgotten passwords, welcome
wishes on signup, invoices for billing, and any other use case that requires
a written notification to either a person or another system.
@@ -67,18 +67,18 @@ simply call the method and optionally call +deliver+ on the return value.
Calling the method returns a Mail Message object:
- message = Notifier.welcome # => Returns a Mail::Message object
- message.deliver # => delivers the email
+ message = Notifier.welcome("david@loudthinking.com") # => Returns a Mail::Message object
+ message.deliver # => delivers the email
Or you can just chain the methods together like:
- Notifier.welcome.deliver # Creates the email and sends it immediately
+ Notifier.welcome("david@loudthinking.com").deliver # Creates the email and sends it immediately
== Setting defaults
It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
-Note that every value you set with this method will get over written if you use the same key in your mailer method.
+Note that every value you set with this method will get overwritten if you use the same key in your mailer method.
Example:
@@ -119,8 +119,7 @@ trivial case like this:
rails runner 'Mailman.receive(STDIN.read)'
However, invoking Rails in the runner for each mail to be received is very resource intensive. A single
-instance of Rails should be run within a daemon, if it is going to be utilized to process more than just
-a limited number of email.
+instance of Rails should be run within a daemon, if it is going to process more than just a limited amount of email.
== Configuration
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 45c238d302..d1134ba2b5 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rake/packagetask'
require 'rubygems/package_task'
desc "Default Task"
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 67ec0d1097..c56b6979ef 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -21,5 +21,5 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
- s.add_dependency 'mail', '~> 2.5.3'
+ s.add_dependency 'mail', '~> 2.5.4'
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index ff74185e37..fcdd6747b8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -151,9 +151,9 @@ module ActionMailer
#
# For example, if the following templates exist:
# * signup_notification.text.erb
- # * signup_notification.text.html.erb
- # * signup_notification.text.xml.builder
- # * signup_notification.text.yaml.erb
+ # * signup_notification.html.erb
+ # * signup_notification.xml.builder
+ # * signup_notification.yaml.erb
#
# Each would be rendered and added as a separate part to the message, with the corresponding content
# type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
@@ -175,7 +175,7 @@ module ActionMailer
# end
# end
#
- # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.text.html.erb</tt>
+ # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.html.erb</tt>
# template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
# the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
@@ -334,8 +334,8 @@ module ActionMailer
# and starts to use it.
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
- # of an OpenSSL verify constant ('none', 'peer', 'client_once','fail_if_no_peer_cert') or directly the
- # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER,...).
+ # of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the
+ # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER, ...).
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
@@ -398,7 +398,7 @@ module ActionMailer
# Register an Observer which will be notified when mail is delivered.
# Either a class or a string can be passed in as the Observer. If a string is passed in
- # it will be <tt>constantize</tt>d.
+ # it will be +constantize+d.
def register_observer(observer)
delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
Mail.register_observer(delivery_observer)
@@ -412,12 +412,20 @@ module ActionMailer
Mail.register_interceptor(delivery_interceptor)
end
+ # Returns the name of current mailer. This method is also being used as a path for a view lookup.
+ # If this is an anonymous mailer, this method will return +anonymous+ instead.
def mailer_name
@mailer_name ||= anonymous? ? "anonymous" : name.underscore
end
+ # Allows to set the name of current mailer.
attr_writer :mailer_name
alias :controller_path :mailer_name
+ # Sets the defaults through app configuration:
+ #
+ # config.action_mailer.default { from: "no-reply@example.org" }
+ #
+ # Aliased by ::default_options=
def default(value = nil)
self.default_params = default_params.merge(value).freeze if value
default_params
@@ -429,13 +437,15 @@ module ActionMailer
# Receives a raw email, parses it into an email object, decodes it,
# instantiates a new mailer, and passes the email object to the mailer
- # object's +receive+ method. If you want your mailer to be able to
- # process incoming messages, you'll need to implement a +receive+
- # method that accepts the raw email string as a parameter:
+ # object's +receive+ method.
+ #
+ # If you want your mailer to be able to process incoming messages, you'll
+ # need to implement a +receive+ method that accepts the raw email string
+ # as a parameter:
#
# class MyMailer < ActionMailer::Base
# def receive(mail)
- # ...
+ # # ...
# end
# end
def receive(raw_mail)
@@ -446,10 +456,12 @@ module ActionMailer
end
end
- # Wraps an email delivery inside of Active Support Notifications instrumentation. This
- # method is actually called by the <tt>Mail::Message</tt> object itself through a callback
- # when you call <tt>:deliver</tt> on the Mail::Message, calling +deliver_mail+ directly
- # and passing a Mail::Message will do nothing except tell the logger you sent the email.
+ # Wraps an email delivery inside of ActiveSupport::Notifications instrumentation.
+ #
+ # This method is actually called by the Mail::Message object itself
+ # through a callback when you call +:deliver+ on the Mail::Message,
+ # calling +deliver_mail+ directly and passing a Mail::Message will do
+ # nothing except tell the logger you sent the email.
def deliver_mail(mail) #:nodoc:
ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
set_payload_for_mail(payload, mail)
@@ -475,7 +487,7 @@ module ActionMailer
payload[:mail] = mail.encoded
end
- def method_missing(method_name, *args)
+ def method_missing(method_name, *args) # :nodoc:
if respond_to?(method_name)
new(method_name, *args).message
else
@@ -512,17 +524,18 @@ module ActionMailer
end
end
+ # Returns the name of the mailer object.
def mailer_name
self.class.mailer_name
end
- # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt> object
- # which will add them to itself.
+ # Allows you to pass random and unusual headers to the new Mail::Message
+ # object which will add them to itself.
#
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
#
- # You can also pass a hash into headers of header field names and values, which
- # will then be set on the Mail::Message object:
+ # You can also pass a hash into headers of header field names and values,
+ # which will then be set on the Mail::Message object:
#
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
# 'In-Reply-To' => incoming.message_id
@@ -578,44 +591,46 @@ module ActionMailer
# Both methods accept a headers hash. This hash allows you to specify the most used headers
# in an email message, these are:
#
- # * <tt>:subject</tt> - The subject of the message, if this is omitted, Action Mailer will
- # ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of
+ # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will
+ # ask the Rails I18n class for a translated +:subject+ in the scope of
# <tt>[mailer_scope, action_name]</tt> or if this is missing, will translate the
- # humanized version of the <tt>action_name</tt>
- # * <tt>:to</tt> - Who the message is destined for, can be a string of addresses, or an array
+ # humanized version of the +action_name+
+ # * +:to+ - Who the message is destined for, can be a string of addresses, or an array
# of addresses.
- # * <tt>:from</tt> - Who the message is from
- # * <tt>:cc</tt> - Who you would like to Carbon-Copy on this email, can be a string of addresses,
+ # * +:from+ - Who the message is from
+ # * +:cc+ - Who you would like to Carbon-Copy on this email, can be a string of addresses,
# or an array of addresses.
- # * <tt>:bcc</tt> - Who you would like to Blind-Carbon-Copy on this email, can be a string of
+ # * +:bcc+ - Who you would like to Blind-Carbon-Copy on this email, can be a string of
# addresses, or an array of addresses.
- # * <tt>:reply_to</tt> - Who to set the Reply-To header of the email to.
- # * <tt>:date</tt> - The date to say the email was sent on.
+ # * +:reply_to+ - Who to set the Reply-To header of the email to.
+ # * +:date+ - The date to say the email was sent on.
#
- # You can set default values for any of the above headers (except :date) by using the <tt>default</tt>
- # class method:
+ # You can set default values for any of the above headers (except +:date+)
+ # by using the ::default class method:
#
# class Notifier < ActionMailer::Base
- # self.default from: 'no-reply@test.lindsaar.net',
- # bcc: 'email_logger@test.lindsaar.net',
- # reply_to: 'bounces@test.lindsaar.net'
+ # default from: 'no-reply@test.lindsaar.net',
+ # bcc: 'email_logger@test.lindsaar.net',
+ # reply_to: 'bounces@test.lindsaar.net'
# end
#
# If you need other headers not listed above, you can either pass them in
# as part of the headers hash or use the <tt>headers['name'] = value</tt>
# method.
#
- # When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
- # address for the Mail message. Setting this is useful when you want delivery notifications
- # sent to a different address than the one in <tt>:from</tt>. Mail will actually use the
- # <tt>:return_path</tt> in preference to the <tt>:sender</tt> in preference to the <tt>:from</tt>
- # field for the 'envelope from' value.
+ # When a +:return_path+ is specified as header, that value will be used as
+ # the 'envelope from' address for the Mail message. Setting this is useful
+ # when you want delivery notifications sent to a different address than the
+ # one in +:from+. Mail will actually use the +:return_path+ in preference
+ # to the +:sender+ in preference to the +:from+ field for the 'envelope
+ # from' value.
#
- # If you do not pass a block to the +mail+ method, it will find all templates in the
- # view paths using by default the mailer name and the method name that it is being
- # called from, it will then create parts for each of these templates intelligently,
- # making educated guesses on correct content type and sequence, and return a fully
- # prepared Mail::Message ready to call <tt>:deliver</tt> on to send.
+ # If you do not pass a block to the +mail+ method, it will find all
+ # templates in the view paths using by default the mailer name and the
+ # method name that it is being called from, it will then create parts for
+ # each of these templates intelligently, making educated guesses on correct
+ # content type and sequence, and return a fully prepared Mail::Message
+ # ready to call +:deliver+ on to send.
#
# For example:
#
@@ -650,8 +665,8 @@ module ActionMailer
# format.html { render text: "<h1>Hello Mikel!</h1>" }
# end
#
- # Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and
- # <tt>text/html</tt> parts.
+ # Which will render a +multipart/alternative+ email with +text/plain+ and
+ # +text/html+ parts.
#
# The block syntax also allows you to customize the part headers if desired:
#
@@ -705,6 +720,15 @@ module ActionMailer
protected
+ # Used by #mail to set the content type of the message.
+ #
+ # It will use the given +user_content_type+, or multipart if the mail
+ # message has any attachments. If the attachments are inline, the content
+ # type will be "multipart/related", otherwise "multipart/mixed".
+ #
+ # If there is no content type passed in via headers, and there are no
+ # attachments, or the message is multipart, then the default content type is
+ # used.
def set_content_type(m, user_content_type, class_default)
params = m.content_type_parameters || {}
case
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index caea3d7535..9a1a27c8ed 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -38,6 +38,7 @@ module ActionMailer
add_delivery_method :test, Mail::TestMailer
end
+ # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods.
module ClassMethods
# Provides a list of emails that have been delivered by Mail::TestMailer
delegate :deliveries, :deliveries=, to: Mail::TestMailer
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index 3fe64759ac..c108156792 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -1,5 +1,8 @@
module ActionMailer
+ # Implements the ActiveSupport::LogSubscriber for logging notifications when
+ # email is delivered and received.
class LogSubscriber < ActiveSupport::LogSubscriber
+ # An email was delivered.
def deliver(event)
return unless logger.info?
recipients = Array(event.payload[:to]).join(', ')
@@ -7,12 +10,14 @@ module ActionMailer
debug(event.payload[:mail])
end
+ # An email was received.
def receive(event)
return unless logger.info?
info("\nReceived mail (#{event.duration.round(1)}ms)")
debug(event.payload[:mail])
end
+ # Use the logger configured for ActionMailer::Base
def logger
ActionMailer::Base.logger
end
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index ec84256491..8d6e082d02 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -1,4 +1,7 @@
module ActionMailer
+ # Provides helper methods for ActionMailer::Base that can be used for easily
+ # formatting messages, accessing mailer or message instances, and the
+ # attachments list.
module MailHelper
# Take the text and format it, indented two spaces for each line, and
# wrapped at 72 columns.
@@ -46,8 +49,9 @@ module ActionMailer
end
end
+ indentation = " " * indent
sentences.map { |sentence|
- "#{" " * indent}#{sentence.join(' ')}"
+ "#{indentation}#{sentence.join(' ')}"
}.join "\n"
end
end
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 89e31c4be6..9d00091972 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,7 +1,7 @@
module ActionMailer
# Returns the version of the currently loaded ActionMailer as a Gem::Version
def self.version
- Gem::Version.new "4.0.0.beta1"
+ Gem::Version.new "4.1.0.beta"
end
module VERSION #:nodoc:
diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb
index 32ae76a7c5..00f1348a53 100644
--- a/actionmailer/test/asset_host_test.rb
+++ b/actionmailer/test/asset_host_test.rb
@@ -16,7 +16,6 @@ class AssetHostTest < ActiveSupport::TestCase
ActionMailer::Base.deliveries.clear
AssetHostMailer.configure do |c|
c.asset_host = "http://www.example.com"
- c.assets_dir = ''
end
end
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index 20b6671283..6584bf5195 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -38,7 +38,7 @@ class BaseMailer < ActionMailer::Base
end
def attachment_with_hash
- attachments['invoice.jpg'] = { data: "\312\213\254\232)b",
+ attachments['invoice.jpg'] = { data: ::Base64.encode64("\312\213\254\232)b"),
mime_type: "image/x-jpg",
transfer_encoding: "base64" }
mail
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index dcd80f46b5..589944fa69 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -41,18 +41,9 @@ class ActionMailerUrlTest < ActionMailer::TestCase
end
def setup
- set_delivery_method :test
- ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries.clear
- ActiveSupport::Deprecation.silenced = false
-
@recipient = 'test@localhost'
end
- def teardown
- restore_delivery_method
- end
-
def test_signed_up_with_url
UrlTestMailer.delivery_method = :test
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 586b15a62d..6f5027dc23 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,1136 +1,5 @@
-## Rails 4.0.0 (unreleased) ##
+* Fix an issue where partials with a number in the filename weren't being digested for cache dependencies.
-* Ensure consistent fallback to the default layout lookup for layouts set
- using symbols or procs that return nil.
+ *Bryan Ricker*
- All of the following layouts will result in the default layout lookup:
-
- layout nil
-
- layout proc { nil }
-
- layout :returns_nil
-
- def returns_nil
- nil
- end
-
- Previously symbols and procs which returned nil resulted in no layout which
- differed from the `layout nil` behavior.
-
- *Chris Nicola*
-
-* Create `UpgradeLegacySignedCookieJar` to transparently upgrade existing signed
- cookies generated by Rails 3.x to avoid invalidating them when upgrading to Rails 4.x.
-
- *Trevor Turk + Neeraj Singh*
-
-* Raise an `ArgumentError` when a clashing named route is defined.
-
- *Trevor Turk*
-
-* Allow default url options to accept host with protocol such as `http://`
-
- config.action_mailer.default_url_options = { host: "http://mydomain.com" }
-
- *Richard Schneeman*
-
-* Ensure that digest authentication responds with a 401 status when a basic
- header is received.
-
- *Brad Dunbar*
-
-* Include I18n locale fallbacks in view lookup.
- Fixes GH#3512.
-
- *Juan Barreneche*
-
-* Integration and functional tests allow headers and rack env
- variables to be passed when performing requests.
- Fixes #6513.
-
- Example:
-
- # integration test
- get "/success", {}, "HTTP_REFERER" => "http://test.com/",
- "Accepts" => "text/plain, text/html"
-
- # functional test
- @request.headers["Accepts"] = "text/plain, text/html"
-
- *Yves Senn*
-
-* Http::Headers respects headers that are not prefixed with HTTP_
-
- *Yves Senn*
-
-* Fix incorrectly appended square brackets to a multiple select box
- if an explicit name has been given and it already ends with "[]"
-
- Before:
-
- select(:category, [], {}, multiple: true, name: "post[category][]")
- # => <select name="post[category][][]" ...>
-
- After:
-
- select(:category, [], {}, multiple: true, name: "post[category][]")
- # => <select name="post[category][]" ...>
-
- *Olek Janiszewski*
-
-* Fixed regression when using `assert_template` to verify files sent using
- `render file: 'README.md'`.
- Fixes #9464.
-
- *Justin Coyne*
-
-* Fixed `ActionView::Helpers::CaptureHelper#content_for` regression when trying to use it in
- a boolean statement.
- Fixes #9360.
-
- *Nikolay Shebanov*
-
-* `format: true` does not override existing format constraints.
- Fixes #9466.
-
- Example:
-
- # This will force the .json extension.
- get '/json_only', to: ok, format: true, constraints: { format: /json/ }
-
- *Yves Senn*
-
-* Skip valid encoding checks for non-String parameters that come
- from the matched route's defaults.
- Fixes #9435.
-
- Example:
-
- root to: 'main#posts', page: 1
-
- *Yves Senn*
-
-* Don't verify Regexp requirements for non-Regexp `:constraints`.
- Fixes #9432.
-
- Example:
-
- get '/photos.:format' => 'feeds#photos', constraints: {format: 'xml'}
-
- *Yves Senn*
-
-* Make `ActionDispatch::Journey::Path::Pattern#new` raise more meaningful exception message.
-
- *Thierry Zires*
-
-
-## Rails 4.0.0.beta1 (February 25, 2013) ##
-
-* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser*
-
-* New applications use an encrypted session store by default.
-
- *Santiago Pastorino*
-
-* Determine the controller#action from only the matched path when using the
- shorthand syntax. Previously the complete path was used, which led
- to problems with nesting (scopes and namespaces).
- Fixes #7554.
-
- Example:
-
- # This will route to questions#new.
- scope ':locale' do
- get 'questions/new'
- end
-
- *Yves Senn*
-
-* Remove support for parsing XML parameters from request. If you still want to parse XML
- parameters, please install `actionpack-xml_parser' gem.
-
- *Prem Sichanugrist*
-
-* Remove support for parsing YAML parameters from request.
-
- *Aaron Patterson*
-
-* Add a message when you have no routes defined to both `rake routes` and
- GET "/rails/info/routes" that lets you know you have none defined and links
- to the Rails guide on the topic.
-
- *Steve Klabnik*
-
-* Change `image_alt` method to replace underscores/hyphens to spaces in filenames.
-
- Previously, underscored filenames became `alt="A_long_file_name_with_underscores"`
- in HTML, which is poor for accessibility. For instance, Apple's VoiceOver Utility
- pronounces each underscore. `A_long_file_name` thus would be read as `A underscore
- long underscore file underscore name.` Now underscored or hyphenated filenames
- (both of which are very popular naming conventions) read more naturally in
- screen readers by converting both hyphens and underscores to spaces.
-
- Before:
-
- image_tag('underscored_file_name.png')
- # => <img alt="Underscored_file_name" src="/assets/underscored_file_name.png" />
-
- After:
-
- image_tag('underscored_file_name.png')
- # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" />
-
- *Nick Cox*
-
-* We don't support Ruby constant notation in the `:controller` option for route
- definitions. So, this raises an `ArgumentError` now:
-
- resources :posts, controller: "Admin::Posts" # WRONG
-
- Use path notation instead:
-
- resources :posts, controller: "admin/posts" # RIGHT
-
- *Yves Senn*
-
-* `assert_template` can be used to verify the locals of partials,
- which live inside a directory.
-
- # Prefixed partials inside directories worked and still work.
- assert_template partial: 'directory/_partial', locals: {name: 'John'}
-
- # This did not work but does now.
- assert_template partial: 'directory/partial', locals: {name: 'John'}
-
- Fixes #8516.
-
- *Yves Senn*
-
-* Fix `content_tag_for` with array HTML option.
- It would embed array as string instead of joining it like `content_tag` does:
-
- content_tag(:td, class: ["foo", "bar"]){}
- # => <td class="foo bar"></td>
-
- Before:
-
- content_tag_for(:td, item, class: ["foo", "bar"])
- # => <td class="item [&quot;foo&quot;, &quot;bar&quot;]" id="item_1"></td>
-
- After:
-
- content_tag_for(:td, item, class: ["foo", "bar"])
- # => <td class="item foo bar" id="item_1"></td>
-
- *Semyon Perepelitsa*
-
-* Remove `BestStandardsSupport` middleware, !DOCTYPE html already triggers
- standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx
- and ChromeFrame header has been moved to `config.action_dispatch.default_headers`
-
- *Guillermo Iguaran*
-
-* Fix CSRF protection and `current_url?` helper to work with HEAD requests
- now that `ActionDispatch::Head` has been removed in favor of `Rack::Head`.
-
- *Michiel Sikkes*
-
-* Change `asset_path` to not include `SCRIPT_NAME` when it's used
- from a mounted engine. Fixes #8119.
-
- *Piotr Sarnacki*
-
-* Add JavaScript based routing path matcher to `/rails/info/routes`.
- Routes can now be filtered by whether or not they match a path.
-
- *Richard Schneeman*
-
-* Change the behavior of route defaults so that explicit defaults are no longer
- required where the key is not part of the path. For example:
-
- resources :posts, bucket_type: 'posts'
-
- will be required whenever constructing the url from a hash such as a functional
- test or using `url_for` directly. However using the explicit form alters the
- behavior so it's not required:
-
- resources :projects, defaults: { bucket_type: 'projects' }
-
- This changes existing behavior slightly in that any routes which only differ
- in their defaults will match the first route rather than the closest match.
-
- *Andrew White*
-
-* Add support for routing constraints other than Regexp and String.
- For example this now allows the use of arrays like this:
-
- get '/foo/:action', to: 'foo', constraints: { subdomain: %w[www admin] }
-
- or constraints where the request method returns an Fixnum like this:
-
- get '/foo', to: 'foo#index', constraints: { port: 8080 }
-
- Note that this only applies to constraints on the request - path constraints
- still need to be specified as Regexps as the various constraints are compiled
- into a single Regexp.
-
- *Andrew White*
-
-* Fix a bug in integration tests where setting the port via a url passed to
- the process method was ignored when constructing the request environment.
-
- *Andrew White*
-
-* Allow `:selected` to be set on `date_select` tag helper.
-
- *Colin Burn-Murdoch*
-
-* Fixed JSON params parsing regression for non-object JSON content.
-
- *Dylan Smith*
-
-* Extract `ActionDispatch::PerformanceTest` into https://github.com/rails/rails-perftest
- You can add the gem to your Gemfile to keep using performance tests.
-
- gem 'rails-perftest'
-
- *Yves Senn*
-
-* Added view_cache_dependency API for declaring dependencies that affect
- cache digest computation.
-
- *Jamis Buck*
-
-* `image_submit_tag` will set `alt` attribute from image source if not
- specified.
-
- *Nihad Abbasov*
-
-* Do not generate local variables for partials without object or collection.
- Previously rendering a partial without giving `:object` or `:collection`
- would generate a local variable with the partial name by default.
-
- *Carlos Antonio da Silva*
-
-* Return the last valid, non-private IP address from the X-Forwarded-For,
- Client-IP and Remote-Addr headers, in that order. Document the rationale
- for that decision, and describe the options that can be passed to the
- RemoteIp middleware to change it.
- Fixes #7979.
-
- *André Arko*, *Steve Klabnik*, *Alexey Gaziev*
-
-* Do not append second slash to `root_url` when using `trailing_slash: true`
- Fixes #8700.
-
- Before:
-
- root_url(trailing_slash: true) # => http://test.host//
-
- After:
-
- root_url(trailing_slash: true) # => http://test.host/
-
- *Yves Senn*
-
-* Allow to toggle dumps on error pages.
-
- *Gosha Arinich*
-
-* Fix a bug in `content_tag_for` that prevents it from working without a block.
-
- *Jasl*
-
-* Change the stylesheet of exception pages for development mode.
- Additionally display also the line of code and fragment that raised
- the exception in all exceptions pages.
-
- *Guillermo Iguaran + Jorge Cuadrado*
-
-* Do not append `charset=` parameter when `head` is called with a
- `:content_type` option.
- Fixes #8661.
-
- *Yves Senn*
-
-* Added `Mime::NullType` class. This allows to use `html?`, `xml?`, `json?`, etc.
- when the format of the request is unknown, without raising an exception.
-
- *Angelo Capilleri*
-
-* Integrate the Journey gem into Action Dispatch so that the global namespace
- is not polluted with names that may be used as models.
-
- *Andrew White*
-
-* Extract support for email address obfuscation via `:encode`, `:replace_at`, and `replace_dot`
- options from the `mail_to` helper into the `actionview-encoded_mail_to` gem.
-
- *Nick Reed + DHH*
-
-* Handle `:protocol` option in `stylesheet_link_tag` and `javascript_include_tag`
-
- *Vasiliy Ermolovich*
-
-* Clear url helper methods when routes are reloaded. *Andrew White*
-
-* Fix a bug in `ActionDispatch::Request#raw_post` that caused `env['rack.input']`
- to be read but not rewound.
-
- *Matt Venables*
-
-* Prevent raising `EOFError` on multipart GET request (IE issue). *Adam Stankiewicz*
-
-* Rename all action callbacks from *_filter to *_action to avoid the misconception that these
- callbacks are only suited for transforming or halting the response. With the new style,
- it's more inviting to use them as they were intended, like setting shared ivars for views.
-
- Example:
-
- class PeopleController < ActionController::Base
- before_action :set_person, except: [:index, :new, :create]
- before_action :ensure_permission, only: [:edit, :update]
-
- ...
-
- private
- def set_person
- @person = current_account.people.find(params[:id])
- end
-
- def ensure_permission
- current_person.can_change?(@person)
- end
- end
-
- The old *_filter methods still work with no deprecation notice.
-
- *DHH*
-
-* Add `cache_if` and `cache_unless` for conditional fragment caching:
-
- Example:
-
- <%= cache_if condition, project do %>
- <b>All the topics on this project</b>
- <%= render project.topics %>
- <% end %>
-
- # and
-
- <%= cache_unless condition, project do %>
- <b>All the topics on this project</b>
- <%= render project.topics %>
- <% end %>
-
- *Stephen Ausman + Fabrizio Regini + Angelo Capilleri*
-
-* Add logging filter capability for redirect URLs:
-
- config.filter_redirect << 'http://please.hide.it/'
-
- *Fabrizio Regini*
-
-* Fixed a bug that ignores constraints on a glob route. This was caused because the constraint
- regular expression is overwritten when the `routes.rb` file is processed. Fixes #7924
-
- *Maura Fitzgerald*
-
-* More descriptive error messages when calling `render :partial` with
- an invalid `:layout` argument.
-
- Fixes #8376.
-
- render partial: 'partial', layout: true
-
- # results in ActionView::MissingTemplate: Missing partial /true
-
- *Yves Senn*
-
-* Sweepers was extracted from Action Controller as `rails-observers` gem.
-
- *Rafael Mendonça França*
-
-* Add option flag to `CacheHelper#cache` to manually bypass automatic template digests:
-
- <% cache project, skip_digest: true do %>
- ...
- <% end %>
-
- *Drew Ulmer*
-
-* Do not sort Hash options in `grouped_options_for_select`. *Sergey Kojin*
-
-* Accept symbols as `send_data :disposition` value *Elia Schito*
-
-* Add i18n scope to `distance_of_time_in_words`. *Steve Klabnik*
-
-* `assert_template`:
- - is no more passing with empty string.
- - is now validating option keys. It accepts: `:layout`, `:partial`, `:locals` and `:count`.
-
- *Roberto Soares*
-
-* Allow setting a symbol as path in scope on routes. This is now allowed:
-
- scope :api do
- resources :users
- end
-
- It is also possible to pass multiple symbols to scope to shorten multiple nested scopes:
-
- scope :api do
- scope :v1 do
- resources :users
- end
- end
-
- can be rewritten as:
-
- scope :api, :v1 do
- resources :users
- end
-
- *Guillermo Iguaran + Amparo Luna*
-
-* Fix error when using a non-hash query argument named "params" in `url_for`.
-
- Before:
-
- url_for(params: "") # => undefined method `reject!' for "":String
-
- After:
-
- url_for(params: "") # => http://www.example.com?params=
-
- *tumayun + Carlos Antonio da Silva*
-
-* Render every partial with a new `ActionView::PartialRenderer`. This resolves
- issues when rendering nested partials.
- Fixes #8197.
-
- *Yves Senn*
-
-* Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list
- of mime types where template text is not html escaped by default. It prevents `Jack & Joe`
- from rendering as `Jack &amp; Joe` for the whitelisted mime types. The default whitelist
- contains `text/plain`.
- Fixes #7976.
-
- *Joost Baaij*
-
-* Fix input name when `multiple: true` and `:index` are set.
-
- Before:
-
- check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
- # => <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" />
-
- After:
-
- check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
- # => <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
-
- Fixes #8108.
-
- *Daniel Fox, Grant Hutchins & Trace Wax*
-
-* `date_select` helper accepts `with_css_classes: true` to add css classes similar with type
- of generated select tags.
-
- *Pavel Nikitin*
-
-* Only non-js/css under `app/assets` path will be included in default `config.assets.precompile`.
-
- *Josh Peek*
-
-* Remove support for the `RAILS_ASSET_ID` environment configuration
- (no longer needed now that we have the asset pipeline).
-
- *Josh Peek*
-
-* Remove old `asset_path` configuration (no longer needed now that we have the asset pipeline).
-
- *Josh Peek*
-
-* `assert_template` can be used to assert on the same template with different locals
- Fixes #3675.
-
- *Yves Senn*
-
-* Remove old asset tag concatenation (no longer needed now that we have the asset pipeline).
-
- *Josh Peek*
-
-* Accept `:remote` as symbolic option for `link_to` helper. *Riley Lynch*
-
-* Warn when the `:locals` option is passed to `assert_template` outside of a view test case
- Fixes #3415.
-
- *Yves Senn*
-
-* The `Rack::Cache` middleware is now disabled by default. To enable it,
- set `config.action_dispatch.rack_cache = true` and add `gem rack-cache` to your Gemfile.
-
- *Guillermo Iguaran*
-
-* `ActionController::Base.page_cache_extension` option is deprecated
- in favour of `ActionController::Base.default_static_extension`.
-
- *Francesco Rodriguez*
-
-* Action and Page caching has been extracted from Action Dispatch
- as `actionpack-action_caching` and `actionpack-page_caching` gems.
- Please read the `README.md` file on both gems for the usage.
-
- *Francesco Rodriguez*
-
-* Failsafe exception returns `text/plain`. *Steve Klabnik*
-
-* Rename internal variables on `ActionController::TemplateAssertions` to prevent
- naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore.
- Fixes #7459.
-
- *Yves Senn*
-
-* `resource` and `resources` don't modify the passed options hash.
- Fixes #7777.
-
- *Yves Senn*
-
-* Precompiled assets include aliases from `foo.js` to `foo/index.js` and vice versa.
-
- # Precompiles phone-<digest>.css and aliases phone/index.css to phone.css.
- config.assets.precompile = [ 'phone.css' ]
-
- # Precompiles phone/index-<digest>.css and aliases phone.css to phone/index.css.
- config.assets.precompile = [ 'phone/index.css' ]
-
- # Both of these work with either precompile thanks to their aliases.
- <%= stylesheet_link_tag 'phone', media: 'all' %>
- <%= stylesheet_link_tag 'phone/index', media: 'all' %>
-
- *Jeremy Kemper*
-
-* `assert_template` is no more passing with what ever string that matches
- with the template name.
-
- Before when we have a template `/layout/hello.html.erb`, `assert_template`
- was passing with any string that matches. This behavior allowed false
- positive like:
-
- assert_template "layout"
- assert_template "out/hello"
-
- Now it only passes with:
-
- assert_template "layout/hello"
- assert_template "hello"
-
- Fixes #3849.
-
- *Hugolnx*
-
-* `image_tag` will set the same width and height for image if numerical value
- passed to `size` option.
-
- *Nihad Abbasov*
-
-* Deprecate `Mime::Type#verify_request?` and `Mime::Type.browser_generated_types`,
- since they are no longer used inside of Rails, they will be removed in Rails 4.1.
-
- *Michael Grosser*
-
-* `ActionDispatch::Http::UploadedFile` now delegates `close` to its tempfile. *Sergio Gil*
-
-* Add `ActionController::StrongParameters`, this module converts `params` hash into
- an instance of ActionController::Parameters that allows whitelisting of permitted
- parameters. Non-permitted parameters are forbidden to be used in Active Model by default
- For more details check the documentation of the module or the
- [strong_parameters gem](https://github.com/rails/strong_parameters)
-
- *DHH + Guillermo Iguaran*
-
-* Remove Integration between `attr_accessible`/`attr_protected` and
- `ActionController::ParamsWrapper`. ParamWrapper now wraps all the parameters returned
- by the class method `attribute_names`.
-
- *Guillermo Iguaran*
-
-* Log now displays the correct status code when an exception is raised.
- Fixes #7646.
-
- *Yves Senn*
-
-* Allow pass couple extensions to `ActionView::Template.register_template_handler` call.
-
- *Tima Maslyuchenko*
-
-* Sprockets integration has been extracted from Action Pack to the `sprockets-rails`
- gem. `rails` gem is depending on `sprockets-rails` by default.
-
- *Guillermo Iguaran*
-
-* `ActionDispatch::Session::MemCacheStore` now uses `dalli` instead of the deprecated
- `memcache-client` gem.
-
- *Arun Agrawal + Guillermo Iguaran*
-
-* Support multiple etags in If-None-Match header. *Travis Warlick*
-
-* Allow to configure how unverified request will be handled using `:with`
- option in `protect_from_forgery` method.
-
- Valid unverified request handling methods are:
-
- - `:exception` - Raises ActionController::InvalidAuthenticityToken exception.
- - `:reset_session` - Resets the session.
- - `:null_session` - Provides an empty session during request but doesn't
- reset it completely. Used as default if `:with` option is not specified.
-
- New applications are generated with:
-
- protect_from_forgery with: :exception
-
- *Sergey Nartimov*
-
-* Add `.ruby` template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran*
-
-* Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`:
-
- excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
- # => ...a very beautiful...
-
- *Guirec Corbel*
-
-* Added controller-level etag additions that will be part of the action etag computation *Jeremy Kemper/DHH*
-
- class InvoicesController < ApplicationController
- etag { current_user.try :id }
-
- def show
- # Etag will differ even for the same invoice when it's viewed by a different current_user
- @invoice = Invoice.find(params[:id])
- fresh_when(@invoice)
- end
- end
-
-* Add automatic template digests to all `CacheHelper#cache` calls (originally spiked in the `cache_digests` plugin) *DHH*
-
-* When building a URL fails, add missing keys provided by Journey. Failed URL
- generation now returns a 500 status instead of a 404.
-
- *Richard Schneeman*
-
-* Deprecate availability of `ActionView::RecordIdentifier` in controllers by default.
- It's view specific and can be easily included in controllers manually if someone
- really needs it. Also deprecate calling `ActionController::RecordIdentifier.dom_id` and
- `dom_class` directly, in favor of `ActionView::RecordIdentifier.dom_id` and `dom_class`.
- `RecordIdentifier` will be removed from `ActionController::Base` in Rails 4.1.
-
- *Piotr Sarnacki*
-
-* Fix `ActionView::RecordIdentifier` to work as a singleton. *Piotr Sarnacki*
-
-* Deprecate `Template#mime_type`, it will be removed in Rails 4.1 in favor of `#type`.
- *Piotr Sarnacki*
-
-* Move vendored html-scanner from `action_controller` to `action_view` directory. If you
- require it directly, please use 'action_view/vendor/html-scanner', reference to
- 'action_controller/vendor/html-scanner' will be removed in Rails 4.1. *Piot Sarnacki*
-
-* Fix handling of date selects when using both disabled and discard options.
- Fixes #7431.
-
- *Vasiliy Ermolovich*
-
-* `ActiveRecord::SessionStore` is extracted out of Rails into a gem `activerecord-session_store`.
- Setting `config.session_store` to `:active_record_store` will no longer work and will break
- if the `activerecord-session_store` gem isn't available. *Prem Sichanugrist*
-
-* Fix `select_tag` when `option_tags` is nil.
- Fixes #7404.
-
- *Sandeep Ravichandran*
-
-* Add `Request#formats=(extensions)` that lets you set multiple formats directly in a prioritized order.
-
- Example of using this for custom iphone views with an HTML fallback:
-
- class ApplicationController < ActionController::Base
- before_filter :adjust_format_for_iphone_with_html_fallback
-
- private
- def adjust_format_for_iphone_with_html_fallback
- request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
- end
- end
-
- *DHH*
-
-* Add Routing Concerns to declare common routes that can be reused inside
- others resources and routes.
-
- Code before:
-
- resources :messages do
- resources :comments
- end
-
- resources :posts do
- resources :comments
- resources :images, only: :index
- end
-
- Code after:
-
- concern :commentable do
- resources :comments
- end
-
- concern :image_attachable do
- resources :images, only: :index
- end
-
- resources :messages, concerns: :commentable
-
- resources :posts, concerns: [:commentable, :image_attachable]
-
- *DHH + Rafael Mendonça França*
-
-* Add `start_hour` and `end_hour` options to the `select_hour` helper. *Evan Tann*
-
-* Raises an `ArgumentError` when the first argument in `form_for` contain `nil`
- or is empty.
-
- *Richard Schneeman*
-
-* Add 'X-Frame-Options' => 'SAMEORIGIN'
- 'X-XSS-Protection' => '1; mode=block' and
- 'X-Content-Type-Options' => 'nosniff'
- as default headers.
-
- *Egor Homakov*
-
-* Allow data attributes to be set as a first-level option for `form_for`, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH*
-
-* Deprecate `button_to_function` and `link_to_function` helpers.
-
- We recommend the use of Unobtrusive JavaScript instead. For example:
-
- link_to "Greeting", "#", class: "nav_link"
-
- $(function() {
- $('.nav_link').click(function() {
- // Some complex code
-
- return false;
- });
- });
-
- or
-
- link_to "Greeting", '#', onclick: "alert('Hello world!'); return false", class: "nav_link"
-
- for simple cases.
-
- *Rafael Mendonça França*
-
-* `javascript_include_tag :all` will now not include `application.js` if the file does not exists. *Prem Sichanugrist*
-
-* Send an empty response body when call `head` with status between 100 and 199, 204, 205 or 304.
-
- *Armand du Plessis*
-
-* Fixed issue with where digest authentication would not work behind a proxy. *Arthur Smith*
-
-* Added `ActionController::Live`. Mix it in to your controller and you can
- stream data to the client live. For example:
-
- class FooController < ActionController::Base
- include ActionController::Live
-
- def index
- 100.times {
- # Client will see this as it's written
- response.stream.write "hello world\n"
- sleep 1
- }
- response.stream.close
- end
- end
-
- *Aaron Patterson*
-
-* Remove `ActionDispatch::Head` middleware in favor of `Rack::Head`. *Santiago Pastorino*
-
-* Deprecate `:confirm` in favor of `data: { confirm: "Text" }` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers.
-
- *Carlos Galdino + Rafael Mendonça França*
-
-* Show routes in exception page while debugging a `RoutingError` in development.
-
- *Richard Schneeman + Mattt Thompson + Yves Senn*
-
-* Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.:
-
- class ApplicationController
- add_flash_types :error, :warning
- end
-
- If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, error: 'message'` in a controller.
-
- *kennyj*
-
-* Remove Active Model dependency from Action Pack. *Guillermo Iguaran*
-
-* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping:
-
- get Rack::Utils.escape('こんにちは') => 'home#index'
-
- You just have to write the unicode route:
-
- get 'こんにちは' => 'home#index'
-
- *kennyj*
-
-* Return proper format on exceptions. *Santiago Pastorino*
-
-* Allow to use `mounted_helpers` (helpers for accessing mounted engines) in `ActionView::TestCase`. *Piotr Sarnacki*
-
-* Include `mounted_helpers` (helpers for accessing mounted engines) in `ActionDispatch::IntegrationTest` by default. *Piotr Sarnacki*
-
-* Extracted redirect logic from `ActionController::ForceSSL::ClassMethods.force_ssl` into `ActionController::ForceSSL#force_ssl_redirect`
-
- *Jeremy Friesen*
-
-* Make possible to use a block in `button_to` if the button text is hard
- to fit into the name parameter, e.g.:
-
- <%= button_to [:make_happy, @user] do %>
- Make happy <strong><%= @user.name %></strong>
- <% end %>
- # => "<form method="post" action="/users/1/make_happy" class="button_to">
- # <div>
- # <button type="submit">
- # Make happy <strong>Name</strong>
- # </button>
- # </div>
- # </form>"
-
- *Sergey Nartimov*
-
-* Change a way of ordering helpers from several directories. Previously,
- when loading helpers from multiple paths, all of the helpers files were
- gathered into one array an then they were sorted. Helpers from different
- directories should not be mixed before loading them to make loading more
- predictable. The most common use case for such behavior is loading helpers
- from engines. When you load helpers from application and engine Foo, in
- that order, first rails will load all of the helpers from application,
- sorted alphabetically and then it will do the same for Foo engine.
-
- *Piotr Sarnacki*
-
-* `truncate` now always returns an escaped HTML-safe string. The option `:escape` can be used as
- false to not escape the result.
-
- *Li Ellis Gallardo + Rafael Mendonça França*
-
-* `truncate` now accepts a block to show extra content when the text is truncated. *Li Ellis Gallardo*
-
-* Add `week_field`, `week_field_tag`, `month_field`, `month_field_tag`, `datetime_local_field`,
- `datetime_local_field_tag`, `datetime_field` and `datetime_field_tag` helpers. *Carlos Galdino*
-
-* Add `color_field` and `color_field_tag` helpers. *Carlos Galdino*
-
-* `assert_generates`, `assert_recognizes`, and `assert_routing` all raise
- `Assertion` instead of `RoutingError` *David Chelimsky*
-
-* URL path parameters with invalid encoding now raise `ActionController::BadRequest`. *Andrew White*
-
-* Malformed query and request parameter hashes now raise `ActionController::BadRequest`. *Andrew White*
-
-* 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 from `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*
-
-* Deprecate `:disable_with` in favor of `data: { disable_with: "Text" }` 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*
-
-* Make current object and counter (when it applies) variables accessible when
- rendering templates with :object / :collection. *Carlos Antonio da Silva*
-
-* JSONP now uses mimetype `text/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`
- to `false`. This change breaks remote forms that need to work also without JavaScript,
- so if you need such behavior, you can either set it to `true` or explicitly pass
- `authenticity_token: true` in form options.
-
-* Added `ActionDispatch::SSL` middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França*
-
-* Add `include_hidden` option to select tag. With `include_hidden: false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich*
-
-* Removed default `size` option from the `text_field`, `search_field`, `telephone_field`, `url_field`, `email_field` helpers. *Philip Arndt*
-
-* Removed default `cols` and `rows` options from the `text_area` helper. *Philip Arndt*
-
-* Adds support for layouts when rendering a partial with a given collection. *serabe*
-
-* Allows the route helper `root` to take a string argument. For example, `root 'pages#main'`. *bcardarella*
-
-* Forms of persisted records use always PATCH (via the `_method` hack). *fxn*
-
-* For resources, both PATCH and PUT are routed to the `update` action. *fxn*
-
-* Don't ignore `force_ssl` in development. This is a change of behavior - use a `:if` condition to recreate the old behavior.
-
- class AccountsController < ApplicationController
- force_ssl if: :ssl_configured?
-
- def ssl_configured?
- !Rails.env.development?
- end
- end
-
- *Pat Allan*
-
-* Adds support for the PATCH verb:
- * Request objects respond to `patch?`.
- * Routes have a new `patch` method, and understand `:patch` in the
- existing places where a verb is configured, like `:via`.
- * New method `patch` available in functional tests.
- * If `:patch` is the default verb for updates, edits are
- tunneled as PATCH rather than as PUT, and routing acts accordingly.
- * New method `patch_via_redirect` available in integration tests.
-
- *dlee*
-
-* Integration tests support the `OPTIONS` method. *Jeremy Kemper*
-
-* `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate"
- is added to the Cache-Control header. *fxn*
-
-* Add `date_field` and `date_field_tag` helpers which render an `input[type="date"]` tag *Olek Janiszewski*
-
-* Adds `image_url`, `javascript_url`, `stylesheet_url`, `audio_url`, `video_url`, and `font_url`
- to assets tag helper. These URL helpers will return the full path to your assets. This is useful
- when you are going to reference this asset from external host. *Prem Sichanugrist*
-
-* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist*
-
-* Allow `value_method` and `text_method` arguments from `collection_select` and
- `options_from_collection_for_select` to receive an object that responds to `:call`,
- such as a `proc`, to evaluate the option in the current element context. This works
- the same way with `collection_radio_buttons` and `collection_check_boxes`.
-
- *Carlos Antonio da Silva + Rafael Mendonça França*
-
-* Add `collection_check_boxes` form helper, similar to `collection_select`:
- Example:
-
- collection_check_boxes :post, :author_ids, Author.all, :id, :name
- # Outputs something like:
- <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
- <label for="post_author_ids_1">D. Heinemeier Hansson</label>
- <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
- <label for="post_author_ids_2">D. Thomas</label>
- <input name="post[author_ids][]" type="hidden" value="" />
-
- The label/check_box pairs can be customized with a block.
-
- *Carlos Antonio da Silva + Rafael Mendonça França*
-
-* Add `collection_radio_buttons` form helper, similar to `collection_select`:
- Example:
-
- collection_radio_buttons :post, :author_id, Author.all, :id, :name
- # Outputs something like:
- <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
- <label for="post_author_id_1">D. Heinemeier Hansson</label>
- <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
- <label for="post_author_id_2">D. Thomas</label>
-
- The label/radio_button pairs can be customized with a block.
-
- *Carlos Antonio da Silva + Rafael Mendonça França*
-
-* `check_box` with `:form` html5 attribute will now replicate the `:form`
- attribute to the hidden field as well. *Carlos Antonio da Silva*
-
-* `label` form helper accepts `for: nil` to not generate the attribute. *Carlos Antonio da Silva*
-
-* Add `:format` option to `number_to_percentage`. *Rodrigo Flores*
-
-* Add `config.action_view.logger` to configure logger for Action View. *Rafael Mendonça França*
-
-* Deprecated `ActionController::Integration` in favour of `ActionDispatch::Integration`.
-
-* Deprecated `ActionController::IntegrationTest` in favour of `ActionDispatch::IntegrationTest`.
-
-* Deprecated `ActionController::PerformanceTest` in favour of `ActionDispatch::PerformanceTest`.
-
-* Deprecated `ActionController::AbstractRequest` in favour of `ActionDispatch::Request`.
-
-* Deprecated `ActionController::Request` in favour of `ActionDispatch::Request`.
-
-* Deprecated `ActionController::AbstractResponse` in favour of `ActionDispatch::Response`.
-
-* Deprecated `ActionController::Response` in favour of `ActionDispatch::Response`.
-
-* Deprecated `ActionController::Routing` in favour of `ActionDispatch::Routing`.
-
-* `check_box helper` with `disabled: true` will generate a disabled
- hidden field to conform with the HTML convention where disabled fields are
- not submitted with the form. This is a behavior change, previously the hidden
- tag had a value of the disabled checkbox. *Tadas Tamosauskas*
-
-* `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton*
-
-* `ActionView::Helpers::TextHelper#highlight` now defaults to the
- HTML5 `mark` element. *Brian Cardarella*
-
-Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index ba7956c3ab..56fc92963a 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rake/packagetask'
require 'rubygems/package_task'
desc "Default Task"
@@ -22,11 +21,15 @@ Rake::TestTask.new(:test_action_pack) do |t|
end
namespace :test do
- Rake::TestTask.new(:isolated) do |t|
- t.libs << 'test'
- t.pattern = 'test/ts_isolated.rb'
+ task :isolated do
+ ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
+ Dir.glob("test/{abstract,controller,dispatch,template}/**/*_test.rb").all? do |file|
+ sh(ruby, '-Ilib:test', file)
+ end or raise "Failures"
end
+end
+namespace :test do
Rake::TestTask.new(:template) do |t|
t.libs << 'test'
t.pattern = 'test/template/**/*.rb'
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 56dc9ab7a1..af5de815bb 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -35,7 +35,7 @@ module AbstractController
end
def inherited(klass) # :nodoc:
- # define the abstract ivar on subclasses so that we don't get
+ # Define the abstract ivar on subclasses so that we don't get
# uninitialized ivar warnings
unless klass.instance_variable_defined?(:@abstract)
klass.instance_variable_set(:@abstract, false)
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 599fff81c2..19cfd7dae1 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -8,7 +8,9 @@ module AbstractController
include ActiveSupport::Callbacks
included do
- define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true
+ define_callbacks :process_action,
+ terminator: ->(controller,_) { controller.response_body },
+ skip_after_callbacks_if_terminated: true
end
# Override AbstractController::Base's process_action to run the
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 812a35735f..5ae8c6c3b0 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -29,7 +29,7 @@ module AbstractController
# helper_method :current_user, :logged_in?
#
# def current_user
- # @current_user ||= User.find_by_id(session[:user])
+ # @current_user ||= User.find_by(id: session[:user])
# end
#
# def logged_in?
@@ -59,7 +59,7 @@ module AbstractController
# The +helper+ class method can take a series of helper module names, a block, or both.
#
# ==== Options
- # * <tt>*args</tt> - Module, Symbol, String, :all
+ # * <tt>*args</tt> - Module, Symbol, String
# * <tt>block</tt> - A block defining helper methods
#
# When the argument is a module it will be included directly in the template class.
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index bac994496e..8e7bdf620e 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -309,6 +309,7 @@ module AbstractController
RUBY
when Proc
define_method :_layout_from_proc, &_layout
+ protected :_layout_from_proc
<<-RUBY
result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
return #{default_behavior} if result.nil?
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 07f5c2586c..3f34add790 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -106,7 +106,7 @@ module AbstractController
# If a component extends the semantics of response_body
# (as Action Controller extends it to be anything that
# responds to the method each), this method needs to be
- # overriden in order to still return a string.
+ # overridden in order to still return a string.
# :api: plugin
def render_to_string(*args, &block)
options = _normalize_render(*args, &block)
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 9cacb3862b..9fdad63b45 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -46,10 +46,6 @@ module ActionController
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
- eager_autoload do
- autoload :RecordIdentifier
- end
-
def self.eager_load!
super
ActionController::Caching.eager_load!
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 971c4189c8..d7b09b67c0 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -223,7 +223,6 @@ module ActionController
ForceSSL,
Streaming,
DataStreaming,
- RecordIdentifier,
HttpAuthentication::Basic::ControllerMethods,
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 7318c8b7ec..9279d8bcea 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -33,7 +33,7 @@ module ActionController
end
def halted_callback(event)
- info("Filter chain halted as #{event.payload[:filter]} rendered or redirected")
+ info("Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected")
end
def send_file(event)
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 143b3e0cbd..b84c9e78c3 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -36,8 +36,7 @@ module ActionController
raise "MiddlewareStack#build requires an app" unless app
middlewares.reverse.inject(app) do |a, middleware|
- middleware.valid?(action) ?
- middleware.build(a) : a
+ middleware.valid?(action) ? middleware.build(a) : a
end
end
end
@@ -57,7 +56,7 @@ module ActionController
# And then to route requests to your metal controller, you would add
# something like this to <tt>config/routes.rb</tt>:
#
- # match 'hello', to: HelloController.action(:index)
+ # get 'hello', to: HelloController.action(:index)
#
# The +action+ method returns a valid Rack application for the \Rails
# router to dispatch to.
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index f1e8714a86..b8afce42c9 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+
module ActionController
# This module provides a method which will redirect browser to use HTTPS
# protocol. This will ensure that user's sensitive information will be
@@ -14,6 +17,10 @@ module ActionController
extend ActiveSupport::Concern
include AbstractController::Callbacks
+ ACTION_OPTIONS = [:only, :except, :if, :unless]
+ URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path]
+ REDIRECT_OPTIONS = [:status, :flash, :alert, :notice]
+
module ClassMethods
# Force the request to this particular controller or specified actions to be
# under HTTPS protocol.
@@ -29,18 +36,34 @@ module ActionController
# end
# end
#
- # ==== Options
- # * <tt>host</tt> - Redirect to a different host name
- # * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except</tt> - The callback should be run for all actions except this action
- # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a true value.
- # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a false value.
+ # ==== URL Options
+ # You can pass any of the following options to affect the redirect url
+ # * <tt>host</tt> - Redirect to a different host name
+ # * <tt>subdomain</tt> - Redirect to a different subdomain
+ # * <tt>domain</tt> - Redirect to a different domain
+ # * <tt>port</tt> - Redirect to a non-standard port
+ # * <tt>path</tt> - Redirect to a different path
+ #
+ # ==== Redirect Options
+ # You can pass any of the following options to affect the redirect status and response
+ # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently)
+ # * <tt>flash</tt> - Set a flash message when redirecting
+ # * <tt>alert</tt> - Set a alert message when redirecting
+ # * <tt>notice</tt> - Set a notice message when redirecting
+ #
+ # ==== Action Options
+ # You can pass any of the following options to affect the before_action callback
+ # * <tt>only</tt> - The callback should be run only for this action
+ # * <tt>except</tt> - The callback should be run for all actions except this action
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a false value.
def force_ssl(options = {})
- host = options.delete(:host)
- before_action(options) do
- force_ssl_redirect(host)
+ action_options = options.slice(*ACTION_OPTIONS)
+ redirect_options = options.except(*ACTION_OPTIONS)
+ before_action(action_options) do
+ force_ssl_redirect(redirect_options)
end
end
end
@@ -48,14 +71,26 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host</tt> - Redirect to a different host name
- def force_ssl_redirect(host = nil)
+ # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
+ # available to the <tt>force_ssl</tt> method.
+ def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
- redirect_options = {:protocol => 'https://', :status => :moved_permanently}
- redirect_options.merge!(:host => host) if host
- redirect_options.merge!(:params => request.query_parameters)
+ options = {
+ :protocol => 'https://',
+ :host => request.host,
+ :path => request.fullpath,
+ :status => :moved_permanently
+ }
+
+ if host_or_options.is_a?(Hash)
+ options.merge!(host_or_options)
+ elsif host_or_options
+ options.merge!(:host => host_or_options)
+ end
+
+ secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
flash.keep if respond_to?(:flash)
- redirect_to redirect_options
+ redirect_to secure_url, options.slice(*REDIRECT_OPTIONS)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 35facd13c8..243fd40a7e 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -94,7 +94,6 @@ module ActionController
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
names.sort!
- names
end
helpers.uniq!
helpers
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index c7bb2dd147..158d552ec7 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -29,7 +29,7 @@ module ActionController
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
@@ -345,7 +345,7 @@ module ActionController
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index fb664a69dd..8092fd639f 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -34,6 +34,7 @@ module ActionController
module Live
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
def initialize(response)
+ @error_callback = nil
super(response, SizedQueue.new(10))
end
@@ -56,6 +57,14 @@ module ActionController
super
@buf.push nil
end
+
+ def on_error(&block)
+ @error_callback = block
+ end
+
+ def call_on_error
+ @error_callback.call
+ end
end
class Response < ActionDispatch::Response #:nodoc: all
@@ -121,6 +130,16 @@ module ActionController
begin
super(name)
+ rescue => e
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
ensure
@_response.commit!
end
@@ -129,6 +148,16 @@ module ActionController
@_response.await_commit
end
+ def log_error(exception)
+ logger = ActionController::Base.logger
+ return unless logger
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+
def response_body=(body)
super
response.stream.close if response
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index c5e7d4e357..bea6b88f91 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -6,7 +6,7 @@ module ActionController
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
- self.formats = request.formats.map { |x| x.ref }
+ self.formats = request.formats.map(&:ref).compact
super
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index d275a854fd..573c739da4 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -199,6 +199,7 @@ module ActionController #:nodoc:
params[request_forgery_protection_token]
end
+ # Checks if the controller allows forgery protection.
def protect_against_forgery?
allow_forgery_protection
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 23d70c9ea2..44703221f3 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -230,7 +230,7 @@ module ActionController
# params = ActionController::Parameters.new({
# person: {
# contact: {
- # email: 'none@test.com'
+ # email: 'none@test.com',
# phone: '555-1234'
# }
# }
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 505f3b4e61..754249cbc8 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -32,7 +32,8 @@ module ActionController
if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
(script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['SCRIPT_NAME'])
+ (original_script_name = env['ORIGINAL_SCRIPT_NAME'])
+
@_url_options.dup.tap do |options|
if original_script_name
options[:original_script_name] = original_script_name
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
deleted file mode 100644
index d598bac467..0000000000
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'action_view/record_identifier'
-
-module ActionController
- module RecordIdentifier
- MODULE_MESSAGE = 'Calling ActionController::RecordIdentifier.%s is deprecated and ' \
- 'will be removed in Rails 4.1, please call using ActionView::RecordIdentifier instead.'
- INSTANCE_MESSAGE = '%s method will no longer be included by default in controllers ' \
- 'since Rails 4.1. If you would like to use it in controllers, please include ' \
- 'ActionView::RecordIdentifier module.'
-
- def dom_id(record, prefix = nil)
- ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_id')
- ActionView::RecordIdentifier.dom_id(record, prefix)
- end
-
- def dom_class(record, prefix = nil)
- ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_class')
- ActionView::RecordIdentifier.dom_class(record, prefix)
- end
-
- def self.dom_id(record, prefix = nil)
- ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_id')
- ActionView::RecordIdentifier.dom_id(record, prefix)
- end
-
- def self.dom_class(record, prefix = nil)
- ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_class')
- ActionView::RecordIdentifier.dom_class(record, prefix)
- end
- end
-end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 41b5228872..0cbbbbe1fd 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -18,7 +18,7 @@ module ActionController
@_layouts = Hash.new(0)
@_files = Hash.new(0)
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:layout]
if path
@_layouts[path] += 1
@@ -28,7 +28,7 @@ module ActionController
end
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
@@ -41,7 +41,7 @@ module ActionController
@_templates[path] += 1
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
next if payload[:virtual_path] # files don't have virtual path
path = payload[:identifier]
@@ -316,7 +316,7 @@ module ActionController
# assert_response :found
#
# # Assert that the controller really put the book in the database.
- # assert_not_nil Book.find_by_title("Love Hina")
+ # assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
#
@@ -455,13 +455,14 @@ module ActionController
#
# - +action+: The controller action to call.
# - +parameters+: The HTTP parameters that you want to pass. This may
- # be +nil+, a Hash, or a String that is appropriately encoded
+ # be +nil+, a hash, or a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
- # - +session+: A Hash of parameters to store in the session. This may be +nil+.
- # - +flash+: A Hash of parameters to store in the flash. This may be +nil+.
+ # - +session+: A hash of parameters to store in the session. This may be +nil+.
+ # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
+ #
+ # You can also simulate POST, PATCH, PUT, DELETE, HEAD, and OPTIONS requests with
+ # +post+, +patch+, +put+, +delete+, +head+, and +options+.
#
- # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
- # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
@@ -469,41 +470,35 @@ module ActionController
end
# Simulate a POST request with the given parameters and set/volley the response.
- # See +#get+ for more details.
+ # See +get+ for more details.
def post(action, *args)
process(action, "POST", *args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
- # See +#get+ for more details.
+ # See +get+ for more details.
def patch(action, *args)
process(action, "PATCH", *args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
- # See +#get+ for more details.
+ # See +get+ for more details.
def put(action, *args)
process(action, "PUT", *args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
- # See +#get+ for more details.
+ # See +get+ for more details.
def delete(action, *args)
process(action, "DELETE", *args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
- # See +#get+ for more details.
+ # See +get+ for more details.
def head(action, *args)
process(action, "HEAD", *args)
end
- # Simulate a OPTIONS request with the given parameters and set/volley the response.
- # See +#get+ for more details.
- def options(action, *args)
- process(action, "OPTIONS", *args)
- end
-
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
@@ -557,7 +552,7 @@ module ActionController
parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
"anonymous" :
- @controller.class.name.underscore.sub(/_controller$/, '')
+ @controller.class.controller_path
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb
deleted file mode 100644
index 896208bc05..0000000000
--- a/actionpack/lib/action_controller/vendor/html-scanner.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'action_view/vendor/html-scanner'
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn 'Vendored html-scanner was moved to action_view, please require "action_view/vendor/html-scanner" instead. ' +
- 'This file will be removed in Rails 4.1'
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 618e2f3033..24a3d4741e 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -82,12 +82,10 @@ module ActionDispatch
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :UpgradeSignatureToEncryptionCookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 912da741b7..61dbf2b96c 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -53,10 +53,6 @@ module Mime
@@html_types = Set.new [:html, :all]
cattr_reader :html_types
- # These are the content types which browsers can generate without using ajax, flash, etc
- # i.e. following a link, getting an image or posting a form. CSRF protection
- # only needs to protect against these types.
- @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
attr_reader :symbol
@register_callbacks = []
@@ -223,8 +219,8 @@ module Mime
Mime.instance_eval { remove_const(symbol) }
SET.delete_if { |v| v.eql?(mime) }
- LOOKUP.delete_if { |k,v| v.eql?(mime) }
- EXTENSION_LOOKUP.delete_if { |k,v| v.eql?(mime) }
+ LOOKUP.delete_if { |_,v| v.eql?(mime) }
+ EXTENSION_LOOKUP.delete_if { |_,v| v.eql?(mime) }
end
end
@@ -272,18 +268,6 @@ module Mime
end
end
- # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
- # ActionController::RequestForgeryProtection.
- def verify_request?
- ActiveSupport::Deprecation.warn "Mime::Type#verify_request? is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types.include?(to_sym)
- end
-
- def self.browser_generated_types
- ActiveSupport::Deprecation.warn "Mime::Type.browser_generated_types is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types
- end
-
def html?
@@html_types.include?(to_sym) || @string =~ /html/
end
@@ -306,12 +290,20 @@ module Mime
method.to_s.ends_with? '?'
end
end
-
+
class NullType
def nil?
true
end
+ def ref
+ nil
+ end
+
+ def respond_to_missing?(method, include_private = false)
+ method.to_s.ends_with? '?'
+ end
+
private
def method_missing(method, *args)
false if method.to_s.ends_with? '?'
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 246d9c121a..8e992070f1 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -64,17 +64,13 @@ module ActionDispatch
end
new_hash = {}
- params.each do |k, v|
- new_key = k.is_a?(String) ? k.dup.force_encoding("UTF-8").encode! : k
- new_hash[new_key] =
- case v
- when Hash
- normalize_encode_params(v)
- when Array
- v.map! {|el| normalize_encode_params(el) }
- else
- normalize_encode_params(v)
- end
+ params.each do |key, val|
+ new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key
+ new_hash[new_key] = if val.is_a?(Array)
+ val.map! { |el| normalize_encode_params(el) }
+ else
+ normalize_encode_params(val)
+ end
end
new_hash.with_indifferent_access
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index ebd87c40b5..4ca1d35489 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -22,6 +22,7 @@ module ActionDispatch
include ActionDispatch::Http::URL
autoload :Session, 'action_dispatch/request/session'
+ autoload :Utils, 'action_dispatch/request/utils'
LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
@@ -299,26 +300,10 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
- # Remove nils from the params hash
- def deep_munge(hash)
- hash.each do |k, v|
- case v
- when Array
- v.grep(Hash) { |x| deep_munge(x) }
- v.compact!
- hash[k] = nil if v.empty?
- when Hash
- deep_munge(v)
- end
- end
-
- hash
- end
-
protected
def parse_query(qs)
- deep_munge(super)
+ Utils.deep_munge(super)
end
private
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 06e936cdb0..5697282791 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -31,10 +31,17 @@ module ActionDispatch # :nodoc:
# end
# end
class Response
- attr_accessor :request, :header
+ # The request that the response is responding to.
+ attr_accessor :request
+
+ # The HTTP status code.
attr_reader :status
+
attr_writer :sending_file
+ # Get and set headers for this response.
+ attr_accessor :header
+
alias_method :headers=, :header=
alias_method :headers, :header
@@ -49,12 +56,16 @@ module ActionDispatch # :nodoc:
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
- attr_accessor :charset
attr_reader :content_type
+ # The charset of the response. HTML wants to know the encoding of the
+ # content you're giving them, so we need to send that along.
+ attr_accessor :charset
+
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze
+ NO_CONTENT_CODES = [204, 304]
cattr_accessor(:default_charset) { "utf-8" }
cattr_accessor(:default_headers)
@@ -92,6 +103,7 @@ module ActionDispatch # :nodoc:
end
end
+ # The underlying body, as a streamable object.
attr_reader :stream
def initialize(status = 200, header = {}, body = [])
@@ -141,6 +153,7 @@ module ActionDispatch # :nodoc:
@status = Rack::Utils.status_code(status)
end
+ # Sets the HTTP content type.
def content_type=(content_type)
@content_type = content_type.to_s
end
@@ -215,11 +228,13 @@ module ActionDispatch # :nodoc:
::Rack::Utils.delete_cookie_header!(header, key, value)
end
+ # The location header we'll be responding with.
def location
headers[LOCATION]
end
alias_method :redirect_url, :location
+ # Sets the location header we'll be responding with.
def location=(url)
headers[LOCATION] = url
end
@@ -228,11 +243,13 @@ module ActionDispatch # :nodoc:
stream.close if stream.respond_to?(:close)
end
+ # Turns the Response into a Rack-compatible array of the status, headers,
+ # and body.
def to_a
rack_response @status, @header.to_hash
end
alias prepare! to_a
- alias to_ary to_a # For implicit splat on 1.9.2
+ alias to_ary to_a
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
@@ -289,7 +306,7 @@ module ActionDispatch # :nodoc:
header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
- if [204, 304].include?(@status)
+ if NO_CONTENT_CODES.include?(@status)
header.delete CONTENT_TYPE
[status, header, []]
else
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 319d0197d1..b57c84dec8 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -6,7 +6,7 @@ module ActionDispatch
# of its interface is available directly for convenience.
#
# Uploaded files are temporary files whose lifespan is one request. When
- # the object is finalized Ruby unlinks the file, so there is not need to
+ # the object is finalized Ruby unlinks the file, so there is no need to
# clean them with a separate maintenance task.
class UploadedFile
# The basename of the file in the client.
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index ab5399c8ea..6f5a52c568 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -4,7 +4,9 @@ require 'active_support/core_ext/hash/slice'
module ActionDispatch
module Http
module URL
- IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
+ IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
+ HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/
+ PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
mattr_accessor :tld_length
self.tld_length = 1
@@ -28,6 +30,7 @@ module ActionDispatch
end
def url_for(options = {})
+ options = options.dup
path = options.delete(:script_name).to_s.chomp("/")
path << options.delete(:path).to_s
@@ -59,15 +62,20 @@ module ActionDispatch
result = ""
unless options[:only_path]
- protocol = extract_protocol(options)
- unless options[:protocol] == false
- result << protocol
- result << ":" unless result.match(%r{:|//})
+ if match = options[:host].match(HOST_REGEXP)
+ options[:protocol] ||= match[1] unless options[:protocol] == false
+ options[:host] = match[2]
+ options[:port] = match[3] unless options.key?(:port)
end
- result << "//" unless result.match("//")
+
+ options[:protocol] = normalize_protocol(options)
+ options[:host] = normalize_host(options)
+ options[:port] = normalize_port(options)
+
+ result << options[:protocol]
result << rewrite_authentication(options)
- result << host_or_subdomain_and_domain(options)
- result << ":#{options.delete(:port)}" if options[:port]
+ result << options[:host]
+ result << ":#{options[:port]}" if options[:port]
end
result
end
@@ -76,6 +84,10 @@ module ActionDispatch
host && IP_HOST_REGEXP !~ host
end
+ def same_host?(options)
+ (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil?
+ end
+
def rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
@@ -84,29 +96,47 @@ module ActionDispatch
end
end
- # Extracts protocol http:// or https:// from options[:host]
- # needs to be called whether the :protocol is being used or not
- def extract_protocol(options)
- if options[:host] && match = options[:host].match(/(^.*:\/\/)(.*)/)
- options[:protocol] ||= match[1]
- options[:host] = match[2]
+ def normalize_protocol(options)
+ case options[:protocol]
+ when nil
+ "http://"
+ when false, "//"
+ "//"
+ when PROTOCOL_REGEXP
+ "#{$1}://"
+ else
+ raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}"
end
- options[:protocol] || "http"
end
- def host_or_subdomain_and_domain(options)
- return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
+ def normalize_host(options)
+ return options[:host] if !named_host?(options[:host]) || same_host?(options)
tld_length = options[:tld_length] || @@tld_length
host = ""
- unless options[:subdomain] == false
- host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
- host << "."
+ if options[:subdomain] == true || !options.key?(:subdomain)
+ host << extract_subdomain(options[:host], tld_length).to_param
+ elsif options[:subdomain].present?
+ host << options[:subdomain].to_param
end
+ host << "." unless host.empty?
host << (options[:domain] || extract_domain(options[:host], tld_length))
host
end
+
+ def normalize_port(options)
+ return nil if options[:port].nil? || options[:port] == false
+
+ case options[:protocol]
+ when "//"
+ nil
+ when "https://"
+ options[:port].to_i == 443 ? nil : options[:port]
+ else
+ options[:port].to_i == 80 ? nil : options[:port]
+ end
+ end
end
def initialize(env)
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 82c55660ea..e288f026c7 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -3,7 +3,7 @@ require 'action_controller/metal/exceptions'
module ActionDispatch
module Journey
# The Formatter class is used for formatting URLs. For example, parameters
- # passed to +url_for+ in rails will eventually call Formatter#generate.
+ # passed to +url_for+ in Rails will eventually call Formatter#generate.
class Formatter # :nodoc:
attr_reader :routes
@@ -58,7 +58,7 @@ module ActionDispatch
end
end
- parameterized_parts.keep_if { |_, v| v }
+ parameterized_parts.keep_if { |_, v| v }
parameterized_parts
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
index a2e1afed32..040f8d5922 100644
--- a/actionpack/lib/action_dispatch/journey/parser.y
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -36,6 +36,7 @@ rule
;
literal
: LITERAL { result = Literal.new(val.first) }
+ ;
dot
: DOT { result = Dot.new(val.first) }
;
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 6fda085681..50e1853094 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -102,6 +102,10 @@ module ActionDispatch
value === request.send(method).to_s
when Array
value.include?(request.send(method))
+ when TrueClass
+ request.send(method).present?
+ when FalseClass
+ request.send(method).blank?
else
value === request.send(method)
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 31868b1814..419e665d12 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -38,7 +38,9 @@ module ActionDispatch
env['REMOTE_ADDR']
end
- def [](k); env[k]; end
+ def [](k)
+ env[k]
+ end
end
attr_reader :request_class, :formatter
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index f21d1d4ee5..d055acb296 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -31,7 +31,7 @@ module ActionDispatch
#
# # Sets a signed cookie, which prevents users from tampering with its value.
# # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
- # # It can be read using the signed method <tt>cookies.signed[:key]</tt>
+ # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -53,13 +53,13 @@ module ActionDispatch
#
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
#
- # cookies[:key] = {
+ # cookies[:name] = {
# value: 'a yummy cookie',
# expires: 1.year.from_now,
# domain: 'domain.com'
# }
#
- # cookies.delete(:key, domain: 'domain.com')
+ # cookies.delete(:name, domain: 'domain.com')
#
# The option symbols for setting cookies are:
#
@@ -70,14 +70,14 @@ module ActionDispatch
# restrict to the domain level. If you use a schema like www.example.com
# and want to share session with user.example.com set <tt>:domain</tt>
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
- # <tt>:all</tt> again when deleting keys.
+ # <tt>:all</tt> again when deleting cookies.
#
# domain: nil # Does not sets cookie domain. (default)
# domain: :all # Allow the cookie for the top most level
# domain and subdomains.
#
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
- # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
+ # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
@@ -117,6 +117,9 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
#
# Example:
@@ -126,23 +129,20 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||= begin
- if @options[:upgrade_legacy_signed_cookie_jar]
+ @signed ||=
+ if @options[:upgrade_legacy_signed_cookies]
UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
else
SignedCookieJar.new(self, @key_generator, @options)
end
- end
- end
-
- # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
- def signed_using_old_secret #:nodoc:
- @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:secret_token]), @options)
end
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
# This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
#
# Example:
@@ -152,7 +152,38 @@ module ActionDispatch
#
# cookies.encrypted[:discount] # => 45
def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ @encrypted ||=
+ if @options[:upgrade_legacy_signed_cookies]
+ UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
+ else
+ EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+
+ # Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set.
+ # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
+ def signed_or_encrypted
+ @signed_or_encrypted ||=
+ if @options[:secret_key_base].present?
+ encrypted
+ else
+ signed
+ end
+ end
+ end
+
+ module VerifyAndUpgradeLegacySignedMessage
+ def initialize(*args)
+ super
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
+ end
+
+ def verify_and_upgrade_legacy_signed_message(name, signed_message)
+ @legacy_verifier.verify(signed_message).tap do |value|
+ self[name] = value
+ end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
end
end
@@ -179,7 +210,7 @@ module ActionDispatch
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
- upgrade_legacy_signed_cookie_jar: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
}
end
@@ -249,7 +280,7 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -260,10 +291,10 @@ module ActionDispatch
handle_options(options)
- 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)
+ if @cookies[name.to_s] != value or options[:expires]
+ @cookies[name.to_s] = value
+ @set_cookies[name.to_s] = options
+ @delete_cookies.delete(name.to_s)
end
value
@@ -272,24 +303,24 @@ module ActionDispatch
# Removes the cookie on the client machine by setting the value to an empty string
# and the expiration date in 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 = {})
- return unless @cookies.has_key? key.to_s
+ def delete(name, options = {})
+ return unless @cookies.has_key? name.to_s
options.symbolize_keys!
handle_options(options)
- value = @cookies.delete(key.to_s)
- @delete_cookies[key.to_s] = options
+ value = @cookies.delete(name.to_s)
+ @delete_cookies[name.to_s] = options
value
end
# Whether the given cookie is to be deleted by this CookieJar.
# Like <tt>[]=</tt>, you can pass in an options hash to test if a
# deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
- def deleted?(key, options = {})
+ def deleted?(name, options = {})
options.symbolize_keys!
handle_options(options)
- @delete_cookies[key.to_s] == options
+ @delete_cookies[name.to_s] == options
end
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
@@ -311,7 +342,6 @@ module ActionDispatch
self.always_write_cookie = false
private
-
def write_cookie?(cookie)
@secure || !cookie[:secure] || always_write_cookie
end
@@ -326,11 +356,11 @@ module ActionDispatch
@options = options
end
- def [](key)
+ def [](name)
@parent_jar[name.to_s]
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -338,7 +368,7 @@ module ActionDispatch
end
options[:expires] = 20.years.from_now
- @parent_jar[key] = options
+ @parent_jar[name] = options
end
end
@@ -354,13 +384,11 @@ module ActionDispatch
def [](name)
if signed_message = @parent_jar[name]
- @verifier.verify(signed_message)
+ verify(signed_message)
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -369,8 +397,15 @@ module ActionDispatch
end
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
+ @parent_jar[name] = options
end
+
+ private
+ def verify(signed_message)
+ @verifier.verify(signed_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
+ end
end
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
@@ -378,39 +413,22 @@ module ActionDispatch
# legacy cookies signed with the old dummy key generator and re-saves
# them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
- def initialize(*args)
- super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
- end
+ include VerifyAndUpgradeLegacySignedMessage
def [](name)
if signed_message = @parent_jar[name]
- verify_signed_message(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
end
end
-
- def verify_signed_message(signed_message)
- @verifier.verify(signed_message)
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
- end
-
- def verify_and_upgrade_legacy_signed_message(name, signed_message)
- @legacy_verifier.verify(signed_message).tap do |value|
- self[name] = value
- end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
- end
end
class EncryptedCookieJar #:nodoc:
include ChainedCookieJars
def initialize(parent_jar, key_generator, options = {})
- if ActiveSupport::DummyKeyGenerator === key_generator
- raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
- "Set config.secret_key_base in config/initializers/secret_token.rb"
+ if ActiveSupport::LegacyKeyGenerator === key_generator
+ raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
+ "Read the upgrade documentation to learn more about this new config option."
end
@parent_jar = parent_jar
@@ -420,16 +438,13 @@ module ActionDispatch
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
end
- def [](key)
- if encrypted_message = @parent_jar[key]
- @encryptor.decrypt_and_verify(encrypted_message)
+ def [](name)
+ if encrypted_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_message)
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature,
- ActiveSupport::MessageEncryptor::InvalidMessage
- nil
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -438,7 +453,28 @@ module ActionDispatch
options[:value] = @encryptor.encrypt_and_sign(options[:value])
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
+ @parent_jar[name] = options
+ end
+
+ private
+ def decrypt_and_verify(encrypted_message)
+ @encryptor.decrypt_and_verify(encrypted_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
+ nil
+ end
+ end
+
+ # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
+ # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
+ # are both set. It reads legacy cookies signed with the old dummy key generator and
+ # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
+ class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
+ include VerifyAndUpgradeLegacySignedMessage
+
+ def [](name)
+ if encrypted_or_signed_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 7489ce8028..1de3d14530 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -9,9 +9,11 @@ module ActionDispatch
'ActionController::RoutingError' => :not_found,
'AbstractController::ActionNotFound' => :not_found,
'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::UnknownHttpMethod' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::UnknownFormat' => :not_acceptable,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
+ 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
'ActionController::BadRequest' => :bad_request,
'ActionController::ParameterMissing' => :bad_request
)
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 7e56feb90a..89003e7a5e 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -243,19 +243,13 @@ module ActionDispatch
session = Request::Session.find(env) || {}
flash_hash = env[KEY]
- if flash_hash
- if !flash_hash.empty? || session.key?('flash')
- session["flash"] = flash_hash.to_session_value
- new_hash = flash_hash.dup
- else
- new_hash = flash_hash
- end
-
- env[KEY] = new_hash
+ if flash_hash && (flash_hash.present? || session.key?('flash'))
+ session["flash"] = flash_hash.to_session_value
+ env[KEY] = flash_hash.dup
end
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
- session.key?('flash') && session['flash'].nil?
+ session.key?('flash') && session['flash'].nil?
session.delete('flash')
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 0fa1e9b859..fb70b60ef6 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -43,7 +43,7 @@ module ActionDispatch
when :json
data = ActiveSupport::JSON.decode(request.body)
data = {:_json => data} unless data.is_a?(Hash)
- request.deep_munge(data).with_indifferent_access
+ Request::Utils.deep_munge(data).with_indifferent_access
else
false
end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 53bedaa40a..cbb2d475b1 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -7,11 +7,10 @@ module ActionDispatch
end
def call(env)
- exception = env["action_dispatch.exception"]
status = env["PATH_INFO"][1..-1]
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
- body = { :status => status, :error => exception.message }
+ body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) }
render(status, content_type, body)
end
@@ -19,7 +18,7 @@ module ActionDispatch
private
def render(status, content_type, body)
- format = content_type && "to_#{content_type.to_sym}"
+ format = "to_#{content_type.to_sym}" if content_type
if format && body.respond_to?(format)
render_format(status, content_type, body.public_send(format))
else
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 44290445d4..5d1740d0d4 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -18,7 +18,7 @@ module ActionDispatch
def call(env)
env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
- @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
+ @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 1e6ed624b0..b9eb8036e9 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -4,36 +4,51 @@ require 'rack/session/cookie'
module ActionDispatch
module Session
- # This cookie-based session store is the Rails default. Sessions typically
- # contain at most a user_id and flash message; both fit within the 4K cookie
- # size limit. Cookie-based sessions are dramatically faster than the
- # alternatives.
+ # This cookie-based session store is the Rails default. It is
+ # dramatically faster than the alternatives.
#
- # If you have more than 4K of session data or don't want your data to be
- # visible to the user, pick another session store.
+ # Sessions typically contain at most a user_id and flash message; both fit
+ # within the 4K cookie size limit. A CookieOverflow exception is raised if
+ # you attempt to store more than 4K of data.
#
- # CookieOverflow is raised if you attempt to store more than 4K of data.
+ # The cookie jar used for storage is automatically configured to be the
+ # best possible option given your application's configuration.
#
- # A message digest is included with the cookie to ensure data integrity:
- # a user cannot alter his +user_id+ without knowing the secret key
- # included in the hash. New apps are generated with a pregenerated secret
- # in config/environment.rb. Set your own for old apps you're upgrading.
+ # If you only have secret_token set, your cookies will be signed, but
+ # not encrypted. This means a user cannot alter his +user_id+ without
+ # knowing your app's secret key, but can easily read his +user_id+. This
+ # was the default for Rails 3 apps.
#
- # Session options:
+ # If you have secret_key_base set, your cookies will be encrypted. This
+ # goes a step further than signed cookies in that encrypted cookies cannot
+ # be altered or read by users. This is the default starting in Rails 4.
#
- # * <tt>:secret</tt>: An application-wide key string. 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.
+ # If you have both secret_token and secret_key base set, your cookies will
+ # be encrypted, and signed cookies generated by Rails 3 will be
+ # transparently read and encrypted to provide a smooth upgrade path.
#
- # secret: '449fe2e7daee471bffae2fd8dc02313d'
+ # Configure your session store in config/initializers/session_store.rb:
#
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
- # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
+ # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
#
- # To generate a secret key for an existing application, run
- # "rake secret" and set the key in config/initializers/secret_token.rb.
+ # Configure your secret key in config/initializers/secret_token.rb:
+ #
+ # Myapp::Application.config.secret_key_base 'secret key'
+ #
+ # To generate a secret key for an existing application, run `rake secret`.
+ #
+ # If you are upgrading an existing Rails 3 app, you should leave your
+ # existing secret_token in place and simply add the new secret_key_base.
+ # Note that you should wait to set secret_key_base until you have 100% of
+ # your userbase on Rails 4 and are reasonably sure you will not need to
+ # rollback to Rails 3. This is because cookies signed based on the new
+ # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
+ # You are free to leave your existing secret_token in place, not set the
+ # new secret_key_base, and ignore the deprecation warnings until you are
+ # reasonably sure that your upgrade is otherwise complete. Additionally,
+ # you should take care to make sure you are not relying on the ability to
+ # decode signed cookies generated by your app in external applications or
+ # Javascript before upgrading.
#
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore < Rack::Session::Abstract::ID
@@ -100,42 +115,7 @@ module ActionDispatch
def cookie_jar(env)
request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed
- end
- end
-
- class EncryptedCookieStore < CookieStore
-
- private
-
- def cookie_jar(env)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar.encrypted
- end
- end
-
- # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
- # To use this CookieStore set
- #
- # Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
- #
- # in your config/initializers/session_store.rb
- #
- # You will also need to add
- #
- # Myapp::Application.config.secret_key_base = 'some secret'
- #
- # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
- class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
- private
-
- def get_cookie(env)
- signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
- end
-
- def signed_using_old_secret_cookie_jar(env)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed_using_old_secret
+ request.cookie_jar.signed_or_encrypted
end
end
end
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 550f4dbd0d..db219c8fa9 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
@@ -13,7 +13,7 @@
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\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")
+ object.to_hash.sort_by { |k, _| 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/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
index 9d947aea40..b181909bff 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
@@ -1,10 +1,8 @@
<%
- traces = [
- ["Application Trace", @application_trace],
- ["Framework Trace", @framework_trace],
- ["Full Trace", @full_trace]
- ]
- names = traces.collect {|name, trace| name}
+ traces = { "Application Trace" => @application_trace,
+ "Framework Trace" => @framework_trace,
+ "Full Trace" => @full_trace }
+ names = traces.keys
%>
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index 891c87ac27..bc5d03dc10 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -34,6 +34,12 @@
padding: 0.5em 1.5em;
}
+ h1 {
+ margin: 0.2em 0;
+ line-height: 1.1em;
+ font-size: 2em;
+ }
+
h2 {
color: #C52F24;
line-height: 25px;
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
index 63216ef7c5..31f46ee340 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
@@ -2,7 +2,7 @@
<header>
<h1>
<%= @exception.original_exception.class.to_s %> in
- <%= @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%= @request.parameters["action"] %>
+ <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
</h1>
</header>
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
new file mode 100644
index 0000000000..8b43cdada8
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -0,0 +1,24 @@
+module ActionDispatch
+ class Request < Rack::Request
+ class Utils # :nodoc:
+ class << self
+ # Remove nils from the params hash
+ def deep_munge(hash)
+ hash.each do |k, v|
+ case v
+ when Array
+ v.grep(Hash) { |x| deep_munge(x) }
+ v.compact!
+ hash[k] = nil if v.empty?
+ when Hash
+ deep_munge(v)
+ end
+ end
+
+ hash
+ end
+ end
+ end
+ end
+end
+
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index d55eb8109a..a9ac2bce1d 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -69,6 +69,22 @@ module ActionDispatch
# <tt>Routing::Mapper::Scoping#namespace</tt>, and
# <tt>Routing::Mapper::Scoping#scope</tt>.
#
+ # == Non-resourceful routes
+ #
+ # For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
+ #
+ # get 'post/:id' => 'posts#show'
+ # post 'post/:id' => 'posts#create_comment'
+ #
+ # If your route needs to respond to more than one HTTP method (or all methods) then using the
+ # <tt>:via</tt> option on <tt>match</tt> is preferable.
+ #
+ # match 'post/:id' => 'posts#show', via: [:get, :post]
+ #
+ # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
+ # URL will route to the <tt>show</tt> action.
+ #
# == Named routes
#
# Routes can be named by passing an <tt>:as</tt> option,
@@ -78,7 +94,7 @@ module ActionDispatch
# Example:
#
# # In routes.rb
- # match '/login' => 'accounts#login', as: 'login'
+ # get '/login' => 'accounts#login', as: 'login'
#
# # With render, redirect_to, tests, etc.
# redirect_to login_url
@@ -104,9 +120,9 @@ module ActionDispatch
#
# # In routes.rb
# controller :blog do
- # match 'blog/show' => :list
- # match 'blog/delete' => :delete
- # match 'blog/edit/:id' => :edit
+ # get 'blog/show' => :list
+ # get 'blog/delete' => :delete
+ # get 'blog/edit/:id' => :edit
# end
#
# # provides named routes for show, delete, and edit
@@ -116,7 +132,7 @@ module ActionDispatch
#
# Routes can generate pretty URLs. For example:
#
- # match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
+ # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
# year: /\d{4}/,
# month: /\d{1,2}/,
# day: /\d{1,2}/
@@ -131,7 +147,7 @@ module ActionDispatch
# You can specify a regular expression to define a format for a parameter.
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /\d{5}(-\d{4})?/
# }
#
@@ -139,13 +155,13 @@ module ActionDispatch
# expression modifiers:
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /hx\d\d\s\d[a-z]{2}/i
# }
# end
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /# Postcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
@@ -153,73 +169,21 @@ module ActionDispatch
# }
# end
#
- # Using the multiline match modifier will raise an +ArgumentError+.
+ # Using the multiline modifier will raise an +ArgumentError+.
# Encoding regular expression modifiers are silently ignored. The
# match will always use the default encoding or ASCII.
#
- # == Default route
- #
- # Consider the following route, which you will find commented out at the
- # bottom of your generated <tt>config/routes.rb</tt>:
- #
- # match ':controller(/:action(/:id))(.:format)'
- #
- # This route states that it expects requests to consist of a
- # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
- # turn is followed optionally by an <tt>:id</tt>, which in turn is followed
- # optionally by a <tt>:format</tt>.
- #
- # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
- # up with:
- #
- # params = { controller: 'blog',
- # action: 'edit',
- # id: '22'
- # }
- #
- # By not relying on default routes, you improve the security of your
- # application since not all controller actions, which includes actions you
- # might add at a later time, are exposed by default.
- #
- # == HTTP Methods
- #
- # Using the <tt>:via</tt> option when specifying a route allows you to
- # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
- # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
- # <tt>:any</tt>. If your route needs to respond to more than one method you
- # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
- # <tt>:any</tt> which means that the route will respond to any of the HTTP
- # methods.
- #
- # match 'post/:id' => 'posts#show', via: :get
- # match 'post/:id' => 'posts#create_comment', via: :post
- #
- # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
- # URL will route to the <tt>show</tt> action.
- #
- # === HTTP helper methods
- #
- # An alternative method of specifying which HTTP method a route should respond to is to use the helper
- # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
- #
- # get 'post/:id' => 'posts#show'
- # post 'post/:id' => 'posts#create_comment'
- #
- # This syntax is less verbose and the intention is more apparent to someone else reading your code,
- # however if your route needs to respond to more than one HTTP method (or all methods) then using the
- # <tt>:via</tt> option on <tt>match</tt> is preferable.
- #
# == External redirects
#
# You can redirect any path to another path using the redirect helper in your router:
#
- # match "/stories" => redirect("/posts")
+ # get "/stories" => redirect("/posts")
#
# == Unicode character routes
#
# You can specify unicode character routes in your router:
#
- # match "こんにちは" => "welcome#index"
+ # get "こんにちは" => "welcome#index"
#
# == Routing to Rack Applications
#
@@ -227,7 +191,7 @@ module ActionDispatch
# index action in the PostsController, you can specify any Rack application
# as the endpoint for a matcher:
#
- # match "/application.js" => Sprockets
+ # get "/application.js" => Sprockets
#
# == Reloading routes
#
@@ -282,11 +246,13 @@ module ActionDispatch
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
#
module Routing
- autoload :Mapper, 'action_dispatch/routing/mapper'
- autoload :RouteSet, 'action_dispatch/routing/route_set'
- autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
- autoload :UrlFor, 'action_dispatch/routing/url_for'
- autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
+ extend ActiveSupport::Autoload
+
+ autoload :Mapper
+ autoload :RouteSet
+ autoload :RoutesProxy
+ autoload :UrlFor
+ autoload :PolymorphicRoutes
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index d251de33df..cffb814e1e 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -69,7 +69,7 @@ module ActionDispatch
end
def internal?
- controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
+ controller.to_s =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
end
def engine?
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index c5f2b33602..3c58a2cfc3 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -10,6 +10,9 @@ module ActionDispatch
module Routing
class Mapper
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
+ SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
+ :controller, :path_names, :constraints, :defaults,
+ :shallow, :blocks, :options]
class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request)
@@ -58,8 +61,8 @@ module ActionDispatch
@set, @scope, @path, @options = set, scope, path, options
@requirements, @conditions, @defaults = {}, {}, {}
- normalize_path!
normalize_options!
+ normalize_path!
normalize_requirements!
normalize_conditions!
normalize_defaults!
@@ -296,7 +299,7 @@ module ActionDispatch
end
end
- # Invokes Rack::Mount::Utils.normalize path and ensure that
+ # Invokes Journey::Router::Utils.normalize_path and ensure that
# (:locale) becomes (/:locale) instead of /(:locale). Except
# for root cases, where the latter is the correct one.
def self.normalize_path(path)
@@ -486,7 +489,7 @@ module ActionDispatch
end
options = app
- app, path = options.find { |k, v| k.respond_to?(:call) }
+ app, path = options.find { |k, _| k.respond_to?(:call) }
options.delete(app) if app
end
@@ -512,6 +515,11 @@ module ActionDispatch
end
end
+ # Query if the following named route was already defined.
+ def has_named_route?(name)
+ @set.named_routes.routes[name.to_sym]
+ end
+
private
def app_name(app)
return unless app.respond_to?(:routes)
@@ -589,8 +597,7 @@ module ActionDispatch
private
def map_method(method, args, &block)
options = args.extract_options!
- options[:via] = method
- options[:path] ||= args.first if args.first.is_a?(String)
+ options[:via] = method
match(*args, options, &block)
self
end
@@ -698,19 +705,21 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
- scope_options.each do |option|
- if value = options.delete(option)
+ SCOPE_OPTIONS.each do |option|
+ if option == :blocks
+ value = block
+ elsif option == :options
+ value = options
+ else
+ value = options.delete(option)
+ end
+
+ if value
recover[option] = @scope[option]
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
- recover[:blocks] = @scope[:blocks]
- @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
-
- recover[:options] = @scope[:options]
- @scope[:options] = merge_options_scope(@scope[:options], options)
-
yield
self
ensure
@@ -841,10 +850,6 @@ module ActionDispatch
end
private
- def scope_options #:nodoc:
- @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
- end
-
def merge_path_scope(parent, child) #:nodoc:
Mapper.normalize_path("#{parent}/#{child}")
end
@@ -945,6 +950,8 @@ module ActionDispatch
VALID_ON_OPTIONS = [:new, :collection, :member]
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS = %w(index create new show update destroy)
+ RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
+ RESOURCE_SCOPES = [:resource, :resources]
class Resource #:nodoc:
attr_reader :controller, :path, :options, :param
@@ -1361,7 +1368,7 @@ module ActionDispatch
def match(path, *rest)
if rest.empty? && Hash === path
options = path
- path, to = options.find { |name, value| name.is_a?(String) }
+ path, to = options.find { |name, _value| name.is_a?(String) }
options[:to] = to
options.delete(path)
paths = [path]
@@ -1370,18 +1377,23 @@ module ActionDispatch
paths = [path] + rest
end
- path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
- if using_match_shorthand?(path_without_format, options)
- options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
- end
-
options[:anchor] = true unless options.key?(:anchor)
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
- paths.each { |_path| decomposed_match(_path, options.dup) }
+ paths.each do |_path|
+ route_options = options.dup
+ route_options[:path] ||= _path if _path.is_a?(String)
+
+ path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
+ if using_match_shorthand?(path_without_format, route_options)
+ route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ end
+
+ decomposed_match(_path, route_options)
+ end
self
end
@@ -1494,11 +1506,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- [:resource, :resources].include? @scope[:scope_level]
+ RESOURCE_SCOPES.include? @scope[:scope_level]
end
def resource_method_scope? #:nodoc:
- [:collection, :member, :new].include? @scope[:scope_level]
+ RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
end
def with_exclusive_scope
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 7fb4719fa0..3ae9f92c0b 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -170,9 +170,10 @@ module ActionDispatch
def call(t, args)
if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
- @options.merge!(t.url_options) if t.respond_to?(:url_options)
- @options[:path] = optimized_helper(args)
- ActionDispatch::Http::URL.url_for(@options)
+ options = @options.dup
+ options.merge!(t.url_options) if t.respond_to?(:url_options)
+ options[:path] = optimized_helper(args)
+ ActionDispatch::Http::URL.url_for(options)
else
super
end
@@ -217,6 +218,7 @@ module ActionDispatch
keys -= t.url_options.keys if t.respond_to?(:url_options)
keys -= options.keys
end
+ keys -= inner_options.keys
result.merge!(Hash[keys.zip(args)])
end
@@ -405,7 +407,7 @@ module ActionDispatch
if name && named_routes[name]
raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \
- "You may have defined two routes with the same name using the `:as` option, or "
+ "You may have defined two routes with the same name using the `:as` option, or " \
"you may be overriding a route already defined by a resource with the same naming. " \
"For the latter, you can restrict the routes created with `resources` as explained here: \n" \
"http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
@@ -665,7 +667,7 @@ module ActionDispatch
end
req = @request_class.new(env)
- @router.recognize(req) do |route, matches, params|
+ @router.recognize(req) do |route, _matches, params|
params.merge!(extras)
params.each do |key, value|
if value.is_a?(String)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 9210bffd1d..496682e8bd 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -81,7 +81,7 @@ module ActionDispatch
# Load routes.rb if it hasn't been loaded.
generated_path, extra_keys = @routes.generate_extras(options, defaults)
- found_extras = options.reject {|k, v| ! extra_keys.include? k}
+ found_extras = options.reject { |k, _| ! extra_keys.include? k }
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg)
@@ -120,7 +120,7 @@ module ActionDispatch
options[:controller] = "/#{controller}"
end
- generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) }
+ generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) }
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index e481f3b245..3253a3d424 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -377,8 +377,8 @@ module ActionDispatch
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
end
- selected = elements.map do |_element|
- text = _element.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
+ selected = elements.map do |elem|
+ text = elem.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
css_select(root, "encoded:root", &block)[0]
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 56c31255f3..1f899a434c 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -3,7 +3,7 @@ require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/try'
require 'rack/test'
-require 'minitest/unit'
+require 'minitest'
module ActionDispatch
module Integration #:nodoc:
@@ -62,12 +62,6 @@ module ActionDispatch
process :head, path, parameters, headers_or_env
end
- # Performs a OPTIONS request with the given parameters. See +#get+ for
- # more details.
- def options(path, parameters = nil, headers_or_env = nil)
- process :options, path, parameters, headers_or_env
- end
-
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
@@ -342,7 +336,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
- %w(get post patch put head delete options cookies assigns
+ %w(get post patch put head delete cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index e657283cec..630e6a9b78 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -6,7 +6,7 @@ module ActionDispatch
module TestProcess
def assigns(key = nil)
assigns = {}.with_indifferent_access
- @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
+ @controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
key.nil? ? assigns : assigns[key]
end
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index b5e47d78d1..fd08f392aa 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,7 +1,7 @@
module ActionPack
# Returns the version of the currently loaded ActionPack as a Gem::Version
def self.version
- Gem::Version.new "4.0.0.beta1"
+ Gem::Version.new "4.1.0.beta"
end
module VERSION #:nodoc:
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
index 2372d3c433..361a0dccbe 100644
--- a/actionpack/lib/action_view/buffers.rb
+++ b/actionpack/lib/action_view/buffers.rb
@@ -8,9 +8,15 @@ module ActionView
end
def <<(value)
+ return self if value.nil?
super(value.to_s)
end
alias :append= :<<
+
+ def safe_concat(value)
+ return self if value.nil?
+ super(value.to_s)
+ end
alias :safe_append= :safe_concat
end
diff --git a/actionpack/lib/action_view/dependency_tracker.rb b/actionpack/lib/action_view/dependency_tracker.rb
index a2a555dfcb..b2e8334077 100644
--- a/actionpack/lib/action_view/dependency_tracker.rb
+++ b/actionpack/lib/action_view/dependency_tracker.rb
@@ -39,7 +39,7 @@ module ActionView
render\s* # render, followed by optional whitespace
\(? # start an optional parenthesis for the render call
(partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture
- ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture
+ ([@a-z"'][@\w\/\."']+) # the template name itself -- 2nd capture
/x
def self.call(name, template)
@@ -74,7 +74,7 @@ module ActionView
# render(@topic) => render("topics/topic")
# render(topics) => render("topics/topic")
# render(message.topics) => render("topics/topic")
- collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }.
+ collect { |name| name.sub(/\A@?([a-z_]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }.
# render("headline") => render("message/headline")
collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }.
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 31e37893c6..2b3a3c6a29 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -14,7 +14,6 @@ module ActionView
# # => <img alt="Rails" src="/assets/rails.png" />
# stylesheet_link_tag("application")
# # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
- #
module AssetTagHelper
extend ActiveSupport::Concern
@@ -50,7 +49,6 @@ module ActionView
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
# # => <script src="http://www.example.com/xmlhr.js"></script>
- #
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!('protocol').symbolize_keys
@@ -58,7 +56,7 @@ module ActionView
sources.uniq.map { |source|
tag_options = {
"src" => path_to_javascript(source, path_options)
- }.merge(options)
+ }.merge!(options)
content_tag(:script, "", tag_options)
}.join("\n").html_safe
end
@@ -67,7 +65,7 @@ module ActionView
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
# You can modify the link attributes by passing a hash as the last argument.
# For historical reasons, the 'media' attribute will always be present and defaults
- # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
+ # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
# apply to all media types.
#
# stylesheet_link_tag "style"
@@ -88,7 +86,6 @@ module ActionView
# stylesheet_link_tag "random.styles", "/css/stylish"
# # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
- #
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!('protocol').symbolize_keys
@@ -98,7 +95,7 @@ module ActionView
"rel" => "stylesheet",
"media" => "screen",
"href" => path_to_stylesheet(source, path_options)
- }.merge(options)
+ }.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
end
@@ -109,10 +106,13 @@ module ActionView
# +url_options+. You can modify the LINK tag itself in +tag_options+.
#
# ==== Options
+ #
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
# * <tt>:type</tt> - Override the auto-generated mime type
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
#
+ # ==== Examples
+ #
# auto_discovery_link_tag
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
# auto_discovery_link_tag(:atom)
@@ -127,11 +127,7 @@ module ActionView
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
if !(type == :rss || type == :atom) && tag_options[:type].blank?
- message = "You have passed type other than :rss or :atom to auto_discovery_link_tag and haven't supplied " +
- "the :type option key. This behavior is deprecated and will be remove in Rails 4.1. You should pass " +
- ":type option explicitly if you want to use other types, for example: " +
- "auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml')"
- ActiveSupport::Deprecation.warn message
+ raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :atom.")
end
tag(
@@ -148,11 +144,14 @@ module ActionView
# you can override "rel" and "type".
#
# ==== Options
+ #
# * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon'
# * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon'
#
+ # ==== Examples
+ #
# favicon_link_tag '/myicon.ico'
- # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
# Mobile Safari looks for a different <link> tag, pointing to an image that
# will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
@@ -160,19 +159,19 @@ module ActionView
#
# favicon_link_tag '/mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
- #
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
:type => 'image/vnd.microsoft.icon',
:href => path_to_image(source)
- }.merge(options.symbolize_keys))
+ }.merge!(options.symbolize_keys))
end
# Returns an HTML image tag for the +source+. The +source+ can be a full
# path or a file.
#
# ==== Options
+ #
# You can add HTML attributes using the +options+. The +options+ supports
# three additional keys for convenience and conformance:
#
@@ -250,6 +249,8 @@ 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")
diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb
index 71b78cf0b5..b5f2df76ab 100644
--- a/actionpack/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_url_helper.rb
@@ -193,7 +193,6 @@ module ActionView
request = self.request if respond_to?(:request)
host = config.asset_host if defined? config.asset_host
host ||= request.base_url if request && options[:protocol] == :request
- return unless host
if host.respond_to?(:call)
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
@@ -204,6 +203,8 @@ module ActionView
host = host % (Zlib.crc32(source) % 4)
end
+ return unless host
+
if host =~ URI_REGEXP
host
else
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index d3953c26b7..8fb5eb1548 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -808,7 +808,7 @@ module ActionView
options[:max_years_allowed] = @options[:max_years_allowed] || 1000
if (options[:end] - options[:start]).abs > options[:max_years_allowed]
- raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter"
+ raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter."
end
build_options_and_select(:year, val, options)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6b6a7edc1d..36dedf0676 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -655,14 +655,6 @@ module ActionView
# ...
# <% end %>
#
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the <tt>:allow_destroy</tt>
# option for +accepts_nested_attributes_for+:
@@ -1160,12 +1152,65 @@ module ActionView
end
end
+ # A +FormBuilder+ object is associated with a particular model object and
+ # allows you to generate fields associated with the model object. The
+ # +FormBuilder+ object is yielded when using +form_for+ or +fields_for+.
+ # For example:
+ #
+ # <%= form_for @person do |person_form| %>
+ # Name: <%= person_form.text_field :name %>
+ # Admin: <%= person_form.check_box :admin %>
+ # <% end %>
+ #
+ # In the above block, the a +FormBuilder+ object is yielded as the
+ # +person_form+ variable. This allows you to generate the +text_field+
+ # and +check_box+ fields by specifying their eponymous methods, which
+ # modify the underlying template and associates the +@person+ model object
+ # with the form.
+ #
+ # The +FormBuilder+ object can be thought of as serving as a proxy for the
+ # methods in the +FormHelper+ module. This class, however, allows you to
+ # call methods with the model object you are building the form for.
+ #
+ # You can create your own custom FormBuilder templates by subclasses this
+ # class. For example:
+ #
+ # class MyFormBuilder < ActionView::Helpers::FormBuilder
+ # def div_radio_button(method, tag_value, options = {})
+ # @template.content_tag(:div,
+ # @template.radio_button(
+ # @object_name, method, tag_value, objectify_options(options)
+ # )
+ # )
+ # end
+ #
+ # The above code creates a new method +div_radio_button+ which wraps a div
+ # around the a new radio button. Note that when options are passed in, you
+ # must called +objectify_options+ in order for the model object to get
+ # correctly passed to the method. If +objectify_options+ is not called,
+ # then the newly created helper will not be linked back to the model.
+ #
+ # The +div_radio_button+ code from above can now be used as follows:
+ #
+ # <%= form_for @person, :builder => MyFormBuilder do |f| %>
+ # I am a child: <%= f.div_radio_button(:admin, "child") %>
+ # I am an adult: <%= f.div_radio_button(:admin, "adult") %>
+ # <% end -%>
+ #
+ # The standard set of helper methods for form building are located in the
+ # +field_helpers+ class attribute.
class FormBuilder
include ModelNaming
# The methods which wrap a form helper call.
class_attribute :field_helpers
- self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model, :model_name_from_record_or_class]
+ self.field_helpers = [:fields_for, :label, :text_field, :password_field,
+ :hidden_field, :file_field, :text_area, :check_box,
+ :radio_button, :color_field, :search_field,
+ :telephone_field, :phone_field, :date_field,
+ :time_field, :datetime_field, :datetime_local_field,
+ :month_field, :week_field, :url_field, :email_field,
+ :number_field, :range_field]
attr_accessor :object_name, :object, :options
@@ -1247,7 +1292,7 @@ module ActionView
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
#
- # <%= f.submit %>
+ # <%= person_form.submit %>
# <% end %>
#
# In this case, the checkbox field will be represented by an HTML +input+
@@ -1425,14 +1470,6 @@ module ActionView
# ...
# <% end %>
#
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the <tt>:allow_destroy</tt>
# option for +accepts_nested_attributes_for+:
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 377819a80c..ad26505086 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -380,7 +380,7 @@ module ActionView
# should produce the desired results.
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
options = collection.map do |element|
- [value_for_collection(element, text_method), value_for_collection(element, value_method)]
+ [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)]
end
selected, disabled = extract_selected_and_disabled(selected)
select_deselect = {
@@ -515,7 +515,6 @@ module ActionView
divider = options[:divider]
else
prompt = options
- options = {}
message = "Passing the prompt to grouped_options_for_select as an argument is deprecated. " \
"Please use an options hash like `{ prompt: #{prompt.inspect} }`."
ActiveSupport::Deprecation.warn message
@@ -565,7 +564,7 @@ module ActionView
if priority_zones
if priority_zones.is_a?(Regexp)
- priority_zones = zones.grep(priority_zones)
+ priority_zones = zones.select { |z| z =~ priority_zones }
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
@@ -752,7 +751,7 @@ module ActionView
end
def prompt_text(prompt)
- prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 8abd5d6e9c..3fa7696b83 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -81,7 +81,7 @@ module ActionView
# ==== Options
# * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * <tt>:include_blank</tt> - If set to true, an empty option will be create
+ # * <tt>:include_blank</tt> - If set to true, an empty option will be created.
# * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something
# * Any other key creates standard HTML attributes for the tag.
#
@@ -426,22 +426,6 @@ module ActionView
def submit_tag(value = "Save changes", options = {})
options = options.stringify_keys
- if disable_with = options.delete("disable_with")
- message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
- "Use 'data: { disable_with: \'Text\' }' instead."
- ActiveSupport::Deprecation.warn message
-
- options["data-disable-with"] = disable_with
- end
-
- if confirm = options.delete("confirm")
- message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
- "Use 'data: { confirm: \'Text\' }' instead'."
- ActiveSupport::Deprecation.warn message
-
- options["data-confirm"] = confirm
- end
-
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
end
@@ -488,22 +472,6 @@ module ActionView
options ||= {}
options = options.stringify_keys
- if disable_with = options.delete("disable_with")
- message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
- "Use 'data: { disable_with: \'Text\' }' instead."
- ActiveSupport::Deprecation.warn message
-
- options["data-disable-with"] = disable_with
- end
-
- if confirm = options.delete("confirm")
- message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
- "Use 'data: { confirm: \'Text\' }' instead'."
- ActiveSupport::Deprecation.warn message
-
- options["data-confirm"] = confirm
- end
-
options.reverse_merge! 'name' => 'button', 'type' => 'submit'
content_tag :button, content_or_options || 'Button', options, &block
@@ -541,15 +509,6 @@ module ActionView
# # => <input alt="Save" src="/images/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
-
- if confirm = options.delete("confirm")
- message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
- "Use 'data: { confirm: \'Text\' }' instead'."
- ActiveSupport::Deprecation.warn message
-
- options["data-confirm"] = confirm
- end
-
tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options)
end
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 878d3e0eda..e475d5b018 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -70,48 +70,6 @@ module ActionView
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={})
- message = "button_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
- "See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
- ActiveSupport::Deprecation.warn message
-
- 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={})
- message = "link_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
- "See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
- ActiveSupport::Deprecation.warn message
-
- 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 9e1be65b1a..fda7038a5d 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -28,6 +28,8 @@ module ActionView
# 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 "-").
@@ -38,6 +40,8 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
+ # ==== Examples
+ #
# number_to_phone(5551234) # => 555-1234
# number_to_phone("5551234") # => 555-1234
# number_to_phone(1235551234) # => 123-555-1234
@@ -61,6 +65,8 @@ module ActionView
# 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
@@ -82,6 +88,8 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
+ # ==== Examples
+ #
# number_to_currency(1234567890.50) # => $1,234,567,890.50
# number_to_currency(1234567890.506) # => $1,234,567,890.51
# number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
@@ -108,6 +116,7 @@ module ActionView
# 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).
@@ -128,6 +137,8 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
+ # ==== Examples
+ #
# number_to_percentage(100) # => 100.000%
# number_to_percentage("98") # => 98.000%
# number_to_percentage(100, precision: 0) # => 100%
@@ -151,6 +162,8 @@ module ActionView
# (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
@@ -160,6 +173,8 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
+ # ==== Examples
+ #
# number_with_delimiter(12345678) # => 12,345,678
# number_with_delimiter("123456") # => 123,456
# number_with_delimiter(12345678.05) # => 12,345,678.05
@@ -185,6 +200,8 @@ module ActionView
# +: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
@@ -202,6 +219,8 @@ module ActionView
# * <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
@@ -233,6 +252,8 @@ module ActionView
# 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
@@ -252,6 +273,8 @@ module ActionView
# * <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
@@ -324,6 +347,8 @@ module ActionView
# * <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"
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 8c7524e795..3939e4737b 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -148,7 +148,7 @@ module ActionView
attrs << tag_option(key, value, escape)
end
end
- " #{attrs.sort * ' '}".html_safe unless attrs.empty?
+ " #{attrs.sort! * ' '}".html_safe unless attrs.empty?
end
def data_tag_option(key, value, escape)
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
index aef1572290..3fe3f4e9df 100644
--- a/actionpack/lib/action_view/helpers/tags/base.rb
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class Base #:nodoc:
+ module Tags # :nodoc:
+ class Base # :nodoc:
include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
include FormOptionsHelper
@@ -73,27 +73,26 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
- options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) }
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
options.delete("index")
elsif defined?(@auto_index)
- options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
- options["name"] ||= options.fetch("name"){ tag_name }
+ options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id }
end
- options["name"] += "[]" if options["multiple"] && !options["name"].ends_with?("[]")
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
end
- def tag_name
- "#{@object_name}[#{sanitized_method_name}]"
+ def tag_name(multiple = false)
+ "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
end
- def tag_name_with_index(index)
- "#{@object_name}[#{index}][#{sanitized_method_name}]"
+ def tag_name_with_index(index, multiple = false)
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
end
def tag_id
diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb
index e21cc07746..6d51f2629a 100644
--- a/actionpack/lib/action_view/helpers/tags/check_box.rb
+++ b/actionpack/lib/action_view/helpers/tags/check_box.rb
@@ -2,7 +2,7 @@ require 'action_view/helpers/tags/checkable'
module ActionView
module Helpers
- module Tags
+ module Tags # :nodoc:
class CheckBox < Base #:nodoc:
include Checkable
diff --git a/actionpack/lib/action_view/helpers/tags/checkable.rb b/actionpack/lib/action_view/helpers/tags/checkable.rb
index b97c0c68d7..052e9df662 100644
--- a/actionpack/lib/action_view/helpers/tags/checkable.rb
+++ b/actionpack/lib/action_view/helpers/tags/checkable.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- module Checkable
+ module Tags # :nodoc:
+ module Checkable # :nodoc:
def input_checked?(object, options)
if options.has_key?("checked")
checked = options.delete "checked"
diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
index 9655008fe2..52006d856b 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -2,11 +2,11 @@ require 'action_view/helpers/tags/collection_helpers'
module ActionView
module Helpers
- module Tags
- class CollectionCheckBoxes < Base
+ module Tags # :nodoc:
+ class CollectionCheckBoxes < Base # :nodoc:
include CollectionHelpers
- class CheckBoxBuilder < Builder
+ class CheckBoxBuilder < Builder # :nodoc:
def check_box(extra_html_options={})
html_options = extra_html_options.merge(@input_html_options)
@template_object.check_box(@object_name, @method_name, html_options, @value, nil)
diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
index e92a318c73..cd12ddaf65 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -1,8 +1,8 @@
module ActionView
module Helpers
- module Tags
- module CollectionHelpers
- class Builder
+ module Tags # :nodoc:
+ module CollectionHelpers # :nodoc:
+ class Builder # :nodoc:
attr_reader :object, :text, :value
def initialize(template_object, object_name, method_name, object,
diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
index 893f4411e7..20be34c1f2 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -2,11 +2,11 @@ require 'action_view/helpers/tags/collection_helpers'
module ActionView
module Helpers
- module Tags
- class CollectionRadioButtons < Base
+ module Tags # :nodoc:
+ class CollectionRadioButtons < Base # :nodoc:
include CollectionHelpers
- class RadioButtonBuilder < Builder
+ class RadioButtonBuilder < Builder # :nodoc:
def radio_button(extra_html_options={})
html_options = extra_html_options.merge(@input_html_options)
@template_object.radio_button(@object_name, @method_name, @value, html_options)
diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionpack/lib/action_view/helpers/tags/collection_select.rb
index ec78e6e5f9..6cb2b2e0d3 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_select.rb
@@ -1,6 +1,6 @@
module ActionView
module Helpers
- module Tags
+ module Tags # :nodoc:
class CollectionSelect < Base #:nodoc:
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
@collection = collection
diff --git a/actionpack/lib/action_view/helpers/tags/color_field.rb b/actionpack/lib/action_view/helpers/tags/color_field.rb
index 6f08f8483a..d8fc797035 100644
--- a/actionpack/lib/action_view/helpers/tags/color_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/color_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class ColorField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class ColorField < TextField # :nodoc:
def render
options = @options.stringify_keys
options["value"] = @options.fetch("value") { validate_color_string(value(object)) }
diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb
index 64c29dea3d..c22be0db29 100644
--- a/actionpack/lib/action_view/helpers/tags/date_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/date_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class DateField < DatetimeField #:nodoc:
+ module Tags # :nodoc:
+ class DateField < DatetimeField # :nodoc:
private
def format_date(value)
diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb
index 734591394b..0c4ac40070 100644
--- a/actionpack/lib/action_view/helpers/tags/date_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/date_select.rb
@@ -2,8 +2,8 @@ require 'active_support/core_ext/time/calculations'
module ActionView
module Helpers
- module Tags
- class DateSelect < Base #:nodoc:
+ module Tags # :nodoc:
+ class DateSelect < Base # :nodoc:
def initialize(object_name, method_name, template_object, options, html_options)
@html_options = html_options
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_field.rb
index e407146e96..9a2279c611 100644
--- a/actionpack/lib/action_view/helpers/tags/datetime_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/datetime_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class DatetimeField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class DatetimeField < TextField # :nodoc:
def render
options = @options.stringify_keys
options["value"] = @options.fetch("value") { format_date(value(object)) }
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
index 6668d6d718..b4a74185d1 100644
--- a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class DatetimeLocalField < DatetimeField #:nodoc:
+ module Tags # :nodoc:
+ class DatetimeLocalField < DatetimeField # :nodoc:
class << self
def field_type
@field_type ||= "datetime-local"
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_select.rb b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
index a32c840bce..563de1840e 100644
--- a/actionpack/lib/action_view/helpers/tags/datetime_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class DatetimeSelect < DateSelect #:nodoc:
+ module Tags # :nodoc:
+ class DatetimeSelect < DateSelect # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/email_field.rb b/actionpack/lib/action_view/helpers/tags/email_field.rb
index 45cde507d7..7ce3ccb9bf 100644
--- a/actionpack/lib/action_view/helpers/tags/email_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/email_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class EmailField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class EmailField < TextField # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb
index 59f2ff71b4..476b820d84 100644
--- a/actionpack/lib/action_view/helpers/tags/file_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/file_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class FileField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class FileField < TextField # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
index 507ba8835f..2ed4712dac 100644
--- a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class GroupedCollectionSelect < Base #:nodoc:
+ module Tags # :nodoc:
+ class GroupedCollectionSelect < Base # :nodoc:
def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
@collection = collection
@group_method = group_method
diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
index a8d13dc1b1..c3757c2461 100644
--- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class HiddenField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class HiddenField < TextField # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb
index 16135fcd5a..35d3ba8434 100644
--- a/actionpack/lib/action_view/helpers/tags/label.rb
+++ b/actionpack/lib/action_view/helpers/tags/label.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class Label < Base #:nodoc:
+ module Tags # :nodoc:
+ class Label < Base # :nodoc:
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
options ||= {}
diff --git a/actionpack/lib/action_view/helpers/tags/month_field.rb b/actionpack/lib/action_view/helpers/tags/month_field.rb
index 3d3c32d847..4c0fb846ee 100644
--- a/actionpack/lib/action_view/helpers/tags/month_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/month_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class MonthField < DatetimeField #:nodoc:
+ module Tags # :nodoc:
+ class MonthField < DatetimeField # :nodoc:
private
def format_date(value)
diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb
index 9cd04434f0..4f95b1b4de 100644
--- a/actionpack/lib/action_view/helpers/tags/number_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/number_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class NumberField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class NumberField < TextField # :nodoc:
def render
options = @options.stringify_keys
diff --git a/actionpack/lib/action_view/helpers/tags/password_field.rb b/actionpack/lib/action_view/helpers/tags/password_field.rb
index 6e7a4d3c36..6099fa6f19 100644
--- a/actionpack/lib/action_view/helpers/tags/password_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/password_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class PasswordField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class PasswordField < TextField # :nodoc:
def render
@options = {:value => nil}.merge!(@options)
super
diff --git a/actionpack/lib/action_view/helpers/tags/radio_button.rb b/actionpack/lib/action_view/helpers/tags/radio_button.rb
index 8a0421f061..4849c537a5 100644
--- a/actionpack/lib/action_view/helpers/tags/radio_button.rb
+++ b/actionpack/lib/action_view/helpers/tags/radio_button.rb
@@ -2,8 +2,8 @@ require 'action_view/helpers/tags/checkable'
module ActionView
module Helpers
- module Tags
- class RadioButton < Base #:nodoc:
+ module Tags # :nodoc:
+ class RadioButton < Base # :nodoc:
include Checkable
def initialize(object_name, method_name, template_object, tag_value, options)
diff --git a/actionpack/lib/action_view/helpers/tags/range_field.rb b/actionpack/lib/action_view/helpers/tags/range_field.rb
index 47db4680e7..f98ae88043 100644
--- a/actionpack/lib/action_view/helpers/tags/range_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/range_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class RangeField < NumberField #:nodoc:
+ module Tags # :nodoc:
+ class RangeField < NumberField # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/search_field.rb b/actionpack/lib/action_view/helpers/tags/search_field.rb
index 818fd4b887..c09e2f1be7 100644
--- a/actionpack/lib/action_view/helpers/tags/search_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/search_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class SearchField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class SearchField < TextField # :nodoc:
def render
options = @options.stringify_keys
diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb
index 53a108b7e6..d64e2f68ef 100644
--- a/actionpack/lib/action_view/helpers/tags/select.rb
+++ b/actionpack/lib/action_view/helpers/tags/select.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class Select < Base #:nodoc:
+ module Tags # :nodoc:
+ class Select < Base # :nodoc:
def initialize(object_name, method_name, template_object, choices, options, html_options)
@choices = choices
@choices = @choices.to_a if @choices.is_a?(Range)
@@ -31,7 +31,6 @@ module ActionView
#
# [nil, []]
# { nil => [] }
- #
def grouped_choices?
!@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
end
diff --git a/actionpack/lib/action_view/helpers/tags/tel_field.rb b/actionpack/lib/action_view/helpers/tags/tel_field.rb
index 87c1f6b6b6..987bb9e67a 100644
--- a/actionpack/lib/action_view/helpers/tags/tel_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/tel_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class TelField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class TelField < TextField # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb
index f74652c5e7..c81156c0c8 100644
--- a/actionpack/lib/action_view/helpers/tags/text_area.rb
+++ b/actionpack/lib/action_view/helpers/tags/text_area.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class TextArea < Base #:nodoc:
+ module Tags # :nodoc:
+ class TextArea < Base # :nodoc:
def render
options = @options.stringify_keys
add_default_name_and_id(options)
diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb
index 024a1a8af2..baa5ff768e 100644
--- a/actionpack/lib/action_view/helpers/tags/text_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/text_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class TextField < Base #:nodoc:
+ module Tags # :nodoc:
+ class TextField < Base # :nodoc:
def render
options = @options.stringify_keys
options["size"] = options["maxlength"] unless options.key?("size")
diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb
index a3941860c9..0e90a3aed7 100644
--- a/actionpack/lib/action_view/helpers/tags/time_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/time_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class TimeField < DatetimeField #:nodoc:
+ module Tags # :nodoc:
+ class TimeField < DatetimeField # :nodoc:
private
def format_date(value)
diff --git a/actionpack/lib/action_view/helpers/tags/time_select.rb b/actionpack/lib/action_view/helpers/tags/time_select.rb
index 9e97deb706..0b06311d25 100644
--- a/actionpack/lib/action_view/helpers/tags/time_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/time_select.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class TimeSelect < DateSelect #:nodoc:
+ module Tags # :nodoc:
+ class TimeSelect < DateSelect # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
index 0a176157c3..80d165ec7e 100644
--- a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class TimeZoneSelect < Base #:nodoc:
+ module Tags # :nodoc:
+ class TimeZoneSelect < Base # :nodoc:
def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
@priority_zones = priority_zones
@html_options = html_options
diff --git a/actionpack/lib/action_view/helpers/tags/url_field.rb b/actionpack/lib/action_view/helpers/tags/url_field.rb
index 1ffdfe0b3c..d76340178d 100644
--- a/actionpack/lib/action_view/helpers/tags/url_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/url_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class UrlField < TextField #:nodoc:
+ module Tags # :nodoc:
+ class UrlField < TextField # :nodoc:
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionpack/lib/action_view/helpers/tags/week_field.rb
index 1e13939a0a..5b3d0494e9 100644
--- a/actionpack/lib/action_view/helpers/tags/week_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/week_field.rb
@@ -1,7 +1,7 @@
module ActionView
module Helpers
- module Tags
- class WeekField < DatetimeField #:nodoc:
+ module Tags # :nodoc:
+ class WeekField < DatetimeField # :nodoc:
private
def format_date(value)
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 2e124cf085..147f9fd8ed 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -126,8 +126,8 @@ module ActionView
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
# The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
- # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The
- # <tt>:separator</tt> enable to choose the delimation. The resulting string will be stripped in any case. If the +phrase+
+ # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
+ # <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
# isn't found, nil is returned.
#
# excerpt('This is an example', 'an', radius: 5)
@@ -145,7 +145,7 @@ module ActionView
# excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
# # => <chop> is also an example
#
- # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
+ # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
# # => ...a very beautiful...
def excerpt(text, phrase, options = {})
return unless text && phrase
@@ -250,8 +250,11 @@ module ActionView
# simple_format("Look ma! A class!", class: 'description')
# # => "<p class='description'>Look ma! A class!</p>"
#
- # simple_format("<span>I'm allowed!</span> It's true.", {}, sanitize: false)
- # # => "<p><span>I'm allowed!</span> It's true.</p>"
+ # simple_format("<blink>Unblinkable.</blink>")
+ # # => "<p>Unblinkable.</p>"
+ #
+ # simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false)
+ # # => "<p><blink>Blinkable!</span> It's true.</p>"
def simple_format(text, html_options = {}, options = {})
wrapper_tag = options.fetch(:wrapper_tag, :p)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 775d93ed39..8a83f6f356 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -425,8 +425,8 @@ module ActionView
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
# ==== Obfuscation
- # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
- # in order to hinder email harvesters. To take advantage of these options,
+ # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
+ # in order to hinder email harvesters. To take advantage of these options,
# install the +actionview-encoded_mail_to+ gem.
#
# ==== Examples
@@ -439,18 +439,30 @@ module ActionView
# mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
# subject: "This is an example email"
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
- def mail_to(email_address, name = nil, html_options = {})
+ #
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
+ #
+ # <%= mail_to "me@domain.com" do %>
+ # <strong>Email me:</strong> <span>me@domain.com</span>
+ # <% end %>
+ # # => <a href="mailto:me@domain.com">
+ # <strong>Email me:</strong> <span>me@domain.com</span>
+ # </a>
+ def mail_to(email_address, name = nil, html_options = {}, &block)
email_address = ERB::Util.html_escape(email_address)
- html_options.stringify_keys!
+ html_options, name = name, nil if block_given?
+ html_options = (html_options || {}).stringify_keys
extras = %w{ cc bcc body subject }.map { |item|
option = html_options.delete(item) || next
"#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
-
- content_tag "a", name || email_address.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
+
+ html_options["href"] = "mailto:#{email_address}#{extras}".html_safe
+
+ content_tag(:a, name || email_address.html_safe, html_options, &block)
end
# True if the current request URI was generated by the given +options+.
@@ -536,28 +548,10 @@ 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')
- if confirm
- message = ":confirm option is deprecated and will be removed from Rails 4.1. " \
- "Use 'data: { confirm: \'Text\' }' instead."
- ActiveSupport::Deprecation.warn message
-
- html_options["data-confirm"] = confirm
- end
-
add_method_to_attributes!(html_options, method) if method
- if disable_with
- message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \
- "Use 'data: { disable_with: \'Text\' }' instead."
- ActiveSupport::Deprecation.warn message
-
- html_options["data-disable-with"] = disable_with
- end
-
html_options
else
link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index d61cc0f304..f9d5b97fe3 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -1,5 +1,6 @@
require 'thread_safe'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
# = Action View Lookup Context
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index d9c76366f8..91ee2ea8f5 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -1,5 +1,11 @@
module ActionView #:nodoc:
# = Action View PathSet
+ #
+ # This class is used to store and access paths in Action View. A number of
+ # operations are defined so that you can search among the paths in this
+ # set and also perform operations on other +PathSet+ objects.
+ #
+ # A +LookupContext+ will use a +PathSet+ to store the paths in its context.
class PathSet #:nodoc:
include Enumerable
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index 6fb8cbb46c..73c19a0ae2 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -1,4 +1,19 @@
module ActionView
+ # This class defines the interface for a renderer. Each class that
+ # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
+ # render a specific type of object.
+ #
+ # The base +Renderer+ class uses its +render+ method to delegate to the
+ # renderers. These currently consist of
+ #
+ # PartialRenderer - Used for rendering partials
+ # TemplateRenderer - Used for rendering other types of templates
+ # StreamingTemplateRenderer - Used for streaming
+ #
+ # Whenever the +render+ method is called on the base +Renderer+ class, a new
+ # renderer object of the correct type is created, and the +render+ method on
+ # that new object is called in turn. This abstracts the setup and rendering
+ # into a separate classes for partials and templates.
class AbstractRenderer #:nodoc:
delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 43a88b0623..821026268a 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -313,6 +313,13 @@ module ActionView
private
+ # Sets up instance variables needed for rendering a partial. This method
+ # finds the options and details and extracts them. The method also contains
+ # logic that handles the type of object passed in as the partial.
+ #
+ # If +options[:partial]+ is a string, then the +@path+ instance variable is
+ # set to that string. Otherwise, the +options[:partial]+ object must
+ # respond to +to_partial_path+ in order to setup the path.
def setup(context, options, block)
@view = context
partial = options[:partial]
@@ -413,6 +420,13 @@ module ActionView
end
end
+ # Obtains the path to where the object's partial is located. If the object
+ # responds to +to_partial_path+, then +to_partial_path+ will be called and
+ # will provide the path. If the object does not respond to +to_partial_path+,
+ # then an +ArgumentError+ is raised.
+ #
+ # If +prefix_partial_path_with_controller_namespace+ is true, then this
+ # method will prefix the partial paths with a namespace.
def partial_path(object = @object)
object = object.to_model if object.respond_to?(:to_model)
diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb
index 30a0c4be70..964b18337e 100644
--- a/actionpack/lib/action_view/renderer/renderer.rb
+++ b/actionpack/lib/action_view/renderer/renderer.rb
@@ -2,6 +2,12 @@ module ActionView
# This is the main entry point for rendering. It basically delegates
# to other objects like TemplateRenderer and PartialRenderer which
# actually renders the template.
+ #
+ # The Renderer will parse the options from the +render+ or +render_body+
+ # method and render a partial or a template based on the options. The
+ # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
+ # the setup and logic necessary to render a view and a new object is created
+ # each time +render+ is called.
class Renderer
attr_accessor :lookup_context
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 946db1df79..e2c50fec47 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -138,7 +138,7 @@ module ActionView
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer=nil, &block)
- ActiveSupport::Notifications.instrument("!render_template.action_view", virtual_path: @virtual_path, identifier: @identifier) do
+ instrument("!render_template") do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
@@ -146,12 +146,6 @@ module ActionView
handle_render_error(view, e)
end
- def mime_type
- message = 'Template#mime_type is deprecated and will be removed in Rails 4.1. Please use type method instead.'
- ActiveSupport::Deprecation.warn message
- @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
- end
-
def type
@type ||= Types[@formats.first] if @formats.first
end
@@ -245,7 +239,9 @@ module ActionView
mod = view.singleton_class
end
- compile(view, mod)
+ instrument("!compile_template") do
+ compile(view, mod)
+ end
# Just discard the source if we have a virtual path. This
# means we can get the template back.
@@ -271,7 +267,7 @@ module ActionView
method_name = self.method_name
code = @handler.call(self)
- # Make sure that the resulting String to be evalled is in the
+ # Make sure that the resulting String to be eval'd is in the
# encoding of the code
source = <<-end_src
def #{method_name}(local_assigns, output_buffer)
@@ -335,5 +331,10 @@ module ActionView
def identifier_method_name #:nodoc:
inspect.gsub(/[^a-z_]/, '_')
end
+
+ def instrument(action, &block)
+ payload = { virtual_path: @virtual_path, identifier: @identifier }
+ ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block)
+ end
end
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 5aaafc15c1..7d7a7af51d 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -6,12 +6,23 @@ module ActionView
module Handlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
+ @newline_pending = 0
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
return if text.empty?
- src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
+
+ if text == "\n"
+ @newline_pending += 1
+ else
+ src << "@output_buffer.safe_append='"
+ src << "\n" * @newline_pending if @newline_pending > 0
+ src << escape_text(text)
+ src << "';"
+
+ @newline_pending = 0
+ end
end
# Erubis toggles <%= and <%== behavior when escaping is enabled.
@@ -28,24 +39,39 @@ module ActionView
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
+ flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
src << '@output_buffer.append= ' << code
else
- src << '@output_buffer.append= (' << code << ');'
+ src << '@output_buffer.append=(' << code << ');'
end
end
def add_expr_escaped(src, code)
+ flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
src << "@output_buffer.safe_append= " << code
else
- src << "@output_buffer.safe_concat((" << code << ").to_s);"
+ src << "@output_buffer.safe_append=(" << code << ");"
end
end
+ def add_stmt(src, code)
+ flush_newline_if_pending(src)
+ super
+ end
+
def add_postamble(src)
+ flush_newline_if_pending(src)
src << '@output_buffer.to_s'
end
+
+ def flush_newline_if_pending(src)
+ if @newline_pending > 0
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
+ @newline_pending = 0
+ end
+ end
end
class ERB
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 47bd011d5e..3304605c1a 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -47,7 +47,7 @@ module ActionView
NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
- # usually a majority of template look ups return nothing, use this canonical preallocated array to safe memory
+ # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
NO_TEMPLATES = [].freeze
def initialize
@@ -255,7 +255,7 @@ module ActionView
#
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
#
- # This one allows you to keep files with different formats in seperated subdirectories,
+ # This one allows you to keep files with different formats in separate subdirectories,
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
#
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 10b487f37a..3145446114 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -211,7 +211,9 @@ module ActionView
alias_method :_view, :view
INTERNAL_IVARS = [
- :@__name__,
+ :@NAME,
+ :@failures,
+ :@assertions,
:@__io__,
:@_assertion_wrapped,
:@_assertions,
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
index 60b6783b19..7f8609c408 100644
--- a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
+++ b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
@@ -537,7 +537,7 @@ module HTML
# Get identifier, class, attribute name, pseudo or negation.
while true
# Element identifier.
- next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
+ next if statement.sub!(/^#(\?|[\w\-]+)/) do
id = $1
if id == "?"
id = values.shift
@@ -549,7 +549,7 @@ module HTML
end
# Class name.
- next if statement.sub!(/^\.([\w\-]+)/) do |match|
+ next if statement.sub!(/^\.([\w\-]+)/) do
class_name = $1
@source << ".#{class_name}"
class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
@@ -558,7 +558,7 @@ module HTML
end
# Attribute value.
- next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
+ next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do
name, equality, value = $1, $2, $3
if value == "?"
value = values.shift
@@ -575,7 +575,7 @@ module HTML
end
# Root element only.
- next if statement.sub!(/^:root/) do |match|
+ next if statement.sub!(/^:root/) do
pseudo << lambda do |element|
element.parent.nil? || !element.parent.tag?
end
@@ -611,7 +611,7 @@ module HTML
"" # Remove
end
# First/last child (of type).
- next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
+ next if statement.sub!(/^:(first|last)-(child|of-type)/) do
reverse = $1 == "last"
of_type = $2 == "of-type"
pseudo << nth_child(0, 1, of_type, reverse)
@@ -619,7 +619,7 @@ module HTML
"" # Remove
end
# Only child (of type).
- next if statement.sub!(/^:only-(child|of-type)/) do |match|
+ next if statement.sub!(/^:only-(child|of-type)/) do
of_type = $1 == "of-type"
pseudo << only_child(of_type)
@source << ":only-#{$1}"
@@ -628,7 +628,7 @@ module HTML
# Empty: no child elements or meaningful content (whitespaces
# are ignored).
- next if statement.sub!(/^:empty/) do |match|
+ next if statement.sub!(/^:empty/) do
pseudo << lambda do |element|
empty = true
for child in element.children
@@ -644,7 +644,7 @@ module HTML
end
# Content: match the text content of the element, stripping
# leading and trailing spaces.
- next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
+ next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do
content = $1
if content == "?"
content = values.shift
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index 92baad4523..4a05c00f8b 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -8,6 +8,8 @@ module AbstractControllerTests
include AbstractController::Rendering
include AbstractController::Layouts
+ abstract!
+
self.view_paths = [ActionView::FixtureResolver.new(
"layouts/hello.erb" => "With String <%= yield %>",
"layouts/hello_override.erb" => "With Override <%= yield %>",
@@ -251,6 +253,10 @@ module AbstractControllerTests
assert_equal "Hello nil!", controller.response_body
end
+ test "when layout is specified as a proc, do not leak any methods into controller's action_methods" do
+ assert_equal Set.new(['index']), WithProc.action_methods
+ end
+
test "when layout is specified as a proc, call it and use the layout returned" do
controller = WithProc.new
controller.process(:index)
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 7157bccfb3..8213997f4e 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -332,7 +332,7 @@ end
module ActionDispatch
module RoutingVerbs
- def get(uri_or_host, path = nil, port = nil)
+ def get(uri_or_host, path = nil)
host = uri_or_host.host unless path
path ||= uri_or_host.path
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index d4f18d55a6..b2bfdae174 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -61,10 +61,7 @@ class UrlOptionsController < ActionController::Base
end
end
-class RecordIdentifierController < ActionController::Base
-end
-
-class RecordIdentifierWithoutDeprecationController < ActionController::Base
+class RecordIdentifierIncludedController < ActionController::Base
include ActionView::RecordIdentifier
end
@@ -88,43 +85,20 @@ class ControllerClassTests < ActiveSupport::TestCase
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
end
- def test_record_identifier
- assert_respond_to RecordIdentifierController.new, :dom_id
- assert_respond_to RecordIdentifierController.new, :dom_class
- end
-
- def test_record_identifier_is_deprecated
- record = Comment.new
- record.save
-
- dom_id = nil
- assert_deprecated 'dom_id method will no longer' do
- dom_id = RecordIdentifierController.new.dom_id(record)
- end
-
- assert_equal 'comment_1', dom_id
-
- dom_class = nil
- assert_deprecated 'dom_class method will no longer' do
- dom_class = RecordIdentifierController.new.dom_class(record)
- end
- assert_equal 'comment', dom_class
- end
-
def test_no_deprecation_when_action_view_record_identifier_is_included
record = Comment.new
record.save
dom_id = nil
assert_not_deprecated do
- dom_id = RecordIdentifierWithoutDeprecationController.new.dom_id(record)
+ dom_id = RecordIdentifierIncludedController.new.dom_id(record)
end
assert_equal 'comment_1', dom_id
dom_class = nil
assert_not_deprecated do
- dom_class = RecordIdentifierWithoutDeprecationController.new.dom_class(record)
+ dom_class = RecordIdentifierIncludedController.new.dom_class(record)
end
assert_equal 'comment', dom_class
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 3b79161ad3..4c82625e8e 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -10,7 +10,7 @@ class ActionController::Base
def before_filters
filters = _process_action_callbacks.select { |c| c.kind == :before }
- filters.map! { |c| c.instance_variable_get(:@raw_filter) }
+ filters.map! { |c| c.raw_filter }
end
end
@@ -213,6 +213,14 @@ class FilterTest < ActionController::TestCase
before_filter :clean_up_tmp, :if => Proc.new { |c| false }
end
+ class ConditionalOptionsSkipFilter < ConditionalFilterController
+ before_filter :ensure_login
+ before_filter :clean_up_tmp
+
+ skip_before_filter :ensure_login, if: -> { false }
+ skip_before_filter :clean_up_tmp, if: -> { true }
+ end
+
class PrependingController < TestController
prepend_before_filter :wonderful_life
# skip_before_filter :fire_flash
@@ -593,6 +601,11 @@ class FilterTest < ActionController::TestCase
assert_equal %w( ensure_login ), assigns["ran_filter"]
end
+ def test_running_conditional_skip_options
+ test_process(ConditionalOptionsSkipFilter)
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ end
+
def test_running_collection_condition_filters
test_process(ConditionalCollectionFilterController)
assert_equal %w( ensure_login ), assigns["ran_filter"]
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 9d4356f546..3b874a739a 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class FlashTest < ActionController::TestCase
@@ -219,7 +218,7 @@ end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
- Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
+ Generator = ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
class TestController < ActionController::Base
add_flash_types :bar
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 6758668b7a..3655b90e32 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -14,8 +14,42 @@ class ForceSSLControllerLevel < ForceSSLController
force_ssl
end
-class ForceSSLCustomDomain < ForceSSLController
- force_ssl :host => "secure.test.host"
+class ForceSSLCustomOptions < ForceSSLController
+ force_ssl :host => "secure.example.com", :only => :redirect_host
+ force_ssl :port => 8443, :only => :redirect_port
+ force_ssl :subdomain => 'secure', :only => :redirect_subdomain
+ force_ssl :domain => 'secure.com', :only => :redirect_domain
+ force_ssl :path => '/foo', :only => :redirect_path
+ force_ssl :status => :found, :only => :redirect_status
+ force_ssl :flash => { :message => 'Foo, Bar!' }, :only => :redirect_flash
+ force_ssl :alert => 'Foo, Bar!', :only => :redirect_alert
+ force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice
+
+ def force_ssl_action
+ render :text => action_name
+ end
+
+ alias_method :redirect_host, :force_ssl_action
+ alias_method :redirect_port, :force_ssl_action
+ alias_method :redirect_subdomain, :force_ssl_action
+ alias_method :redirect_domain, :force_ssl_action
+ alias_method :redirect_path, :force_ssl_action
+ alias_method :redirect_status, :force_ssl_action
+ alias_method :redirect_flash, :force_ssl_action
+ alias_method :redirect_alert, :force_ssl_action
+ alias_method :redirect_notice, :force_ssl_action
+
+ def use_flash
+ render :text => flash[:message]
+ end
+
+ def use_alert
+ render :text => flash[:alert]
+ end
+
+ def use_notice
+ render :text => flash[:notice]
+ end
end
class ForceSSLOnlyAction < ForceSSLController
@@ -80,19 +114,77 @@ class ForceSSLControllerLevelTest < ActionController::TestCase
end
end
-class ForceSSLCustomDomainTest < ActionController::TestCase
- tests ForceSSLCustomDomain
+class ForceSSLCustomOptionsTest < ActionController::TestCase
+ tests ForceSSLCustomOptions
- def test_banana_redirects_to_https_with_custom_host
- get :banana
+ def setup
+ @request.env['HTTP_HOST'] = 'www.example.com:80'
+ end
+
+ def test_redirect_to_custom_host
+ get :redirect_host
assert_response 301
- assert_equal "https://secure.test.host/force_ssl_custom_domain/banana", redirect_to_url
+ assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_host", redirect_to_url
end
- def test_cheeseburger_redirects_to_https_with_custom_host
- get :cheeseburger
+ def test_redirect_to_custom_port
+ get :redirect_port
+ assert_response 301
+ assert_equal "https://www.example.com:8443/force_ssl_custom_options/redirect_port", redirect_to_url
+ end
+
+ def test_redirect_to_custom_subdomain
+ get :redirect_subdomain
assert_response 301
- assert_equal "https://secure.test.host/force_ssl_custom_domain/cheeseburger", redirect_to_url
+ assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_subdomain", redirect_to_url
+ end
+
+ def test_redirect_to_custom_domain
+ get :redirect_domain
+ assert_response 301
+ assert_equal "https://www.secure.com/force_ssl_custom_options/redirect_domain", redirect_to_url
+ end
+
+ def test_redirect_to_custom_path
+ get :redirect_path
+ assert_response 301
+ assert_equal "https://www.example.com/foo", redirect_to_url
+ end
+
+ def test_redirect_to_custom_status
+ get :redirect_status
+ assert_response 302
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_status", redirect_to_url
+ end
+
+ def test_redirect_to_custom_flash
+ get :redirect_flash
+ assert_response 301
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_flash", redirect_to_url
+
+ get :use_flash
+ assert_response 200
+ assert_equal "Foo, Bar!", @response.body
+ end
+
+ def test_redirect_to_custom_alert
+ get :redirect_alert
+ assert_response 301
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_alert", redirect_to_url
+
+ get :use_alert
+ assert_response 200
+ assert_equal "Foo, Bar!", @response.body
+ end
+
+ def test_redirect_to_custom_notice
+ get :redirect_notice
+ assert_response 301
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_notice", redirect_to_url
+
+ get :use_notice
+ assert_response 200
+ assert_equal "Foo, Bar!", @response.body
end
end
@@ -149,16 +241,79 @@ class ForceSSLFlashTest < ActionController::TestCase
assert_response 302
assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url
+ # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists
+ @request.env.delete('PATH_INFO')
+
get :cheeseburger
assert_response 301
assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url
+ # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists
+ @request.env.delete('PATH_INFO')
+
get :use_flash
assert_equal "hello", assigns["flash_copy"]["that"]
assert_equal "hello", assigns["flashy"]
end
end
+class ForceSSLDuplicateRoutesTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_force_ssl_redirects_to_same_path
+ with_routing do |set|
+ set.draw do
+ get '/foo', :to => 'force_ssl_controller_level#banana'
+ get '/bar', :to => 'force_ssl_controller_level#banana'
+ end
+
+ @request.env['PATH_INFO'] = '/bar'
+
+ get :banana
+ assert_response 301
+ assert_equal 'https://test.host/bar', redirect_to_url
+ end
+ end
+end
+
+class ForceSSLFormatTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_force_ssl_redirects_to_same_format
+ with_routing do |set|
+ set.draw do
+ get '/foo', :to => 'force_ssl_controller_level#banana'
+ end
+
+ get :banana, :format => :json
+ assert_response 301
+ assert_equal 'https://test.host/foo.json', redirect_to_url
+ end
+ end
+end
+
+class ForceSSLOptionalSegmentsTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_force_ssl_redirects_to_same_format
+ with_routing do |set|
+ set.draw do
+ scope '(:locale)' do
+ defaults :locale => 'en' do
+ get '/foo', :to => 'force_ssl_controller_level#banana'
+ end
+ end
+ end
+
+ @request.env['PATH_INFO'] = '/en/foo'
+ get :banana, :locale => 'en'
+ assert_equal 'en', @controller.params[:locale]
+ assert_response 301
+ assert_equal 'https://test.host/en/foo', redirect_to_url
+ end
+ end
+end
+
class RedirectToSSLTest < ActionController::TestCase
tests RedirectToSSL
def test_banana_redirects_to_https_if_not_https
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 4287856550..9f1c168209 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class HttpDigestAuthenticationTest < ActionController::TestCase
@@ -43,7 +42,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
@secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(@secret)
end
teardown do
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index c3bdf74d93..f7ec6d71b3 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -117,12 +117,6 @@ class SessionTest < ActiveSupport::TestCase
@session.head(path,params,headers)
end
- def test_options
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:options,path,params,headers)
- @session.options(path,params,headers)
- end
-
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -183,16 +177,6 @@ class SessionTest < ActiveSupport::TestCase
@session.xml_http_request(:head,path,params,headers)
end
- def test_xml_http_request_options
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:options,path,params,headers_after_xhr)
- @session.xml_http_request(:options,path,params,headers)
- end
-
def test_xml_http_request_override_accept
path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
headers_after_xhr = headers.merge(
@@ -250,7 +234,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
@integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process)
- %w( get post head patch put delete options ).each do |verb|
+ %w( get post head patch put delete ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 5755444a65..34164a19f0 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -52,6 +52,29 @@ module ActionController
def with_stale
render :text => 'stale' if stale?(:etag => "123")
end
+
+ def exception_in_view
+ render 'doesntexist'
+ end
+
+ def exception_with_callback
+ response.headers['Content-Type'] = 'text/event-stream'
+
+ response.stream.on_error do
+ response.stream.write %(data: "500 Internal Server Error"\n\n)
+ response.stream.close
+ end
+
+ raise 'An exception occurred...'
+ end
+
+ def exception_in_exception_callback
+ response.headers['Content-Type'] = 'text/event-stream'
+ response.stream.on_error do
+ raise 'We need to go deeper.'
+ end
+ response.stream.write params[:widget][:didnt_check_for_nil]
+ end
end
tests TestController
@@ -66,6 +89,21 @@ module ActionController
TestResponse.new
end
+ def assert_stream_closed
+ assert response.stream.closed?, 'stream should be closed'
+ end
+
+ def capture_log_output
+ output = StringIO.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(output)
+
+ begin
+ yield output
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+
def test_set_response!
@controller.set_response!(@request)
assert_kind_of(Live::Response, @controller.response)
@@ -119,7 +157,43 @@ module ActionController
def test_render_text
get :render_text
assert_equal 'zomg', response.body
- assert response.stream.closed?, 'stream should be closed'
+ assert_stream_closed
+ end
+
+ def test_exception_handling_html
+ capture_log_output do |output|
+ get :exception_in_view
+ assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
+ assert_match 'Missing template test/doesntexist', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exception_handling_plain_text
+ capture_log_output do |output|
+ get :exception_in_view, format: :json
+ assert_equal '', response.body
+ assert_match 'Missing template test/doesntexist', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exception_callback
+ capture_log_output do |output|
+ get :exception_with_callback, format: 'text/event-stream'
+ assert_equal %(data: "500 Internal Server Error"\n\n), response.body
+ assert_match 'An exception occurred...', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exceptions_raised_handling_exceptions
+ capture_log_output do |output|
+ get :exception_in_exception_callback, format: 'text/event-stream'
+ assert_equal '', response.body
+ assert_match 'We need to go deeper', output.rewind && output.read
+ assert_stream_closed
+ end
end
def test_stale_without_etag
diff --git a/actionpack/test/controller/new_base/render_partial_test.rb b/actionpack/test/controller/new_base/render_partial_test.rb
index 2f1aa22208..9e5022c9f4 100644
--- a/actionpack/test/controller/new_base/render_partial_test.rb
+++ b/actionpack/test/controller/new_base/render_partial_test.rb
@@ -5,14 +5,14 @@ module RenderPartial
class BasicController < ActionController::Base
self.view_paths = [ActionView::FixtureResolver.new(
- "render_partial/basic/_basic.html.erb" => "BasicPartial!",
- "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
- "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
- "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
- "render_partial/basic/_final.json.erb" => "{ final: json }",
- "render_partial/basic/overriden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overriden' %><%= @test_unchanged %>",
- "render_partial/basic/_overriden.html.erb" => "ParentPartial!",
- "render_partial/child/_overriden.html.erb" => "OverridenPartial!"
+ "render_partial/basic/_basic.html.erb" => "BasicPartial!",
+ "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
+ "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
+ "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
+ "render_partial/basic/_final.json.erb" => "{ final: json }",
+ "render_partial/basic/overridden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overridden' %><%= @test_unchanged %>",
+ "render_partial/basic/_overridden.html.erb" => "ParentPartial!",
+ "render_partial/child/_overridden.html.erb" => "OverriddenPartial!"
)]
def html_with_json_inside_json
@@ -24,7 +24,7 @@ module RenderPartial
render :action => "basic"
end
- def overriden
+ def overridden
@test_unchanged = 'hello'
end
end
@@ -55,8 +55,8 @@ module RenderPartial
end
test "partial from child controller gets picked" do
- get :overriden
- assert_response("goodbyeOverridenPartial!goodbye")
+ get :overridden
+ assert_response("goodbyeOverriddenPartial!goodbye")
end
end
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index cc7f12ac6d..5635e16234 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -7,10 +7,10 @@ module Render
"render/blank_render/access_request.html.erb" => "The request: <%= request.method.to_s.upcase %>",
"render/blank_render/access_action_name.html.erb" => "Action Name: <%= action_name %>",
"render/blank_render/access_controller_name.html.erb" => "Controller Name: <%= controller_name %>",
- "render/blank_render/overriden_with_own_view_paths_appended.html.erb" => "parent content",
- "render/blank_render/overriden_with_own_view_paths_prepended.html.erb" => "parent content",
- "render/blank_render/overriden.html.erb" => "parent content",
- "render/child_render/overriden.html.erb" => "child content"
+ "render/blank_render/overridden_with_own_view_paths_appended.html.erb" => "parent content",
+ "render/blank_render/overridden_with_own_view_paths_prepended.html.erb" => "parent content",
+ "render/blank_render/overridden.html.erb" => "parent content",
+ "render/child_render/overridden.html.erb" => "child content"
)]
def index
@@ -25,13 +25,13 @@ module Render
render :action => "access_action_name"
end
- def overriden_with_own_view_paths_appended
+ def overridden_with_own_view_paths_appended
end
- def overriden_with_own_view_paths_prepended
+ def overridden_with_own_view_paths_prepended
end
- def overriden
+ def overridden
end
private
@@ -49,8 +49,8 @@ module Render
end
class ChildRenderController < BlankRenderController
- append_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_appended.html.erb" => "child content")
- prepend_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_prepended.html.erb" => "child content")
+ append_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_appended.html.erb" => "child content")
+ prepend_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_prepended.html.erb" => "child content")
end
class RenderTest < Rack::TestCase
@@ -114,17 +114,17 @@ module Render
class TestViewInheritance < Rack::TestCase
test "Template from child controller gets picked over parent one" do
- get "/render/child_render/overriden"
+ get "/render/child_render/overridden"
assert_body "child content"
end
test "Template from child controller with custom view_paths prepended gets picked over parent one" do
- get "/render/child_render/overriden_with_own_view_paths_prepended"
+ get "/render/child_render/overridden_with_own_view_paths_prepended"
assert_body "child content"
end
test "Template from child controller with custom view_paths appended gets picked over parent one" do
- get "/render/child_render/overriden_with_own_view_paths_appended"
+ get "/render/child_render/overridden_with_own_view_paths_appended"
assert_body "child content"
end
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
deleted file mode 100644
index ff5d7fd3bd..0000000000
--- a/actionpack/test/controller/record_identifier_test.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'abstract_unit'
-require 'controller/fake_models'
-
-class ControllerRecordIdentifierTest < ActiveSupport::TestCase
- include ActionController::RecordIdentifier
-
- def setup
- @record = Comment.new
- end
-
- def test_dom_id_deprecation
- assert_deprecated(/dom_id method will no longer be included by default in controllers/) do
- dom_id(@record)
- end
- end
-
- def test_dom_class_deprecation
- assert_deprecated(/dom_class method will no longer be included by default in controllers/) do
- dom_class(@record)
- end
- end
-
- def test_dom_id_from_module_deprecation
- assert_deprecated(/Calling ActionController::RecordIdentifier.dom_id is deprecated/) do
- ActionController::RecordIdentifier.dom_id(@record)
- end
- end
-
- def test_dom_class_from_module_deprecation
- assert_deprecated(/Calling ActionController::RecordIdentifier.dom_class is deprecated/) do
- ActionController::RecordIdentifier.dom_class(@record)
- end
- end
-end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 0e5bad7482..72411ec900 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -32,6 +32,10 @@ class TestControllerWithExtraEtags < ActionController::Base
def fresh
render text: "stale" if stale?(etag: '123')
end
+
+ def array
+ render text: "stale" if stale?(etag: %w(1 2 3))
+ end
end
class TestController < ActionController::Base
@@ -1649,7 +1653,6 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_equal @last_modified, @response.headers['Last-Modified']
end
-
def test_request_with_bang_gets_last_modified
get :conditional_hello_with_bangs
assert_equal @last_modified, @response.headers['Last-Modified']
@@ -1678,7 +1681,7 @@ class EtagRenderTest < ActionController::TestCase
end
def test_multiple_etags
- @request.if_none_match = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key([ "123", 'ab', :cde, [:f] ]))}")
+ @request.if_none_match = etag(["123", 'ab', :cde, [:f]])
get :fresh
assert_response :not_modified
@@ -1686,6 +1689,20 @@ class EtagRenderTest < ActionController::TestCase
get :fresh
assert_response :success
end
+
+ def test_array
+ @request.if_none_match = etag([%w(1 2 3), 'ab', :cde, [:f]])
+ get :array
+ assert_response :not_modified
+
+ @request.if_none_match = %("nomatch")
+ get :array
+ assert_response :success
+ end
+
+ def etag(record)
+ Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record)).inspect
+ end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 978c5aa7ac..f735564305 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -715,17 +715,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def setup_request_method_routes_for(method)
rs.draw do
- match '/match' => 'books#get', :via => :get
- match '/match' => 'books#post', :via => :post
- match '/match' => 'books#put', :via => :put
- match '/match' => 'books#patch', :via => :patch
- match '/match' => 'books#delete', :via => :delete
+ match '/match' => "books##{method}", :via => method.to_sym
end
end
%w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
- setup_request_method_routes_for(request_method)
+ setup_request_method_routes_for(request_method.downcase)
params = rs.recognize_path("/match", :method => request_method)
assert_equal request_method.downcase, params[:action]
end
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 888791b874..ff23b22040 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -47,7 +47,7 @@ module ShowExceptions
end
end
- class ShowExceptionsOverridenController < ShowExceptionsController
+ class ShowExceptionsOverriddenController < ShowExceptionsController
private
def show_detailed_exceptions?
@@ -55,15 +55,15 @@ module ShowExceptions
end
end
- class ShowExceptionsOverridenTest < ActionDispatch::IntegrationTest
+ class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest
test 'show error page' do
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get '/', {'detailed' => '0'}
assert_equal "500 error fixture\n", body
end
test 'show diagnostics message' do
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get '/', {'detailed' => '1'}
assert_match(/boom/, body)
end
@@ -71,23 +71,23 @@ module ShowExceptions
class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
def test_render_json_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'application/json'
assert_response :internal_server_error
assert_equal 'application/json', response.content_type.to_s
- assert_equal({ :status => '500', :error => 'boom!' }.to_json, response.body)
+ assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_json, response.body)
end
def test_render_xml_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
assert_response :internal_server_error
assert_equal 'application/xml', response.content_type.to_s
- assert_equal({ :status => '500', :error => 'boom!' }.to_xml, response.body)
+ assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_xml, response.body)
end
def test_render_fallback_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
assert_response :internal_server_error
assert_equal 'text/html', response.content_type.to_s
@@ -96,7 +96,7 @@ module ShowExceptions
class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest
def test_render_failsafe_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
@exceptions_app = @app.instance_variable_get(:@exceptions_app)
@app.instance_variable_set(:@exceptions_app, nil)
$stderr = StringIO.new
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 38b9794b4d..7c27458f46 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -201,11 +201,6 @@ XML
assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
end
- def test_options
- options :test_params
- assert_equal 200, @response.status
- end
-
def test_process_without_flash
process :set_flash
assert_equal '><', flash['test']
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index ba24e7fac5..088ad73f2f 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -89,6 +89,13 @@ module AbstractController
)
end
+ def test_subdomain_may_be_removed_with_blank_string
+ W.default_url_options[:host] = 'api.basecamphq.com'
+ assert_equal('http://basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => '', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
def test_multiple_subdomains_may_be_removed
W.default_url_options[:host] = 'mobile.www.api.basecamphq.com'
assert_equal('http://basecamphq.com/c/a/i',
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index c532e0b8cc..91ac13e7c6 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -7,7 +7,6 @@ rescue LoadError, NameError
$stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
else
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
require 'active_support/message_verifier'
@@ -86,6 +85,11 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def get_encrypted_cookie
+ cookies.encrypted[:foo]
+ head :ok
+ end
+
def set_invalid_encrypted_cookie
cookies[:invalid_cookie] = 'invalid--9170e00a57cfc27083363b5c75b835e477bd90cf'
head :ok
@@ -344,12 +348,17 @@ class CookiesTest < ActionController::TestCase
assert response.headers["Set-Cookie"] =~ /user_name=david/
end
- def test_permanent_cookie
+ def test_set_permanent_cookie
get :set_permanent_cookie
assert_match(/Jamie/, @response.headers["Set-Cookie"])
assert_match(%r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"])
end
+ def test_read_permanent_cookie
+ get :set_permanent_cookie
+ assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
+ end
+
def test_signed_cookie
get :set_signed_cookie
assert_equal 45, @controller.send(:cookies).signed[:user_id]
@@ -408,29 +417,29 @@ class CookiesTest < ActionController::TestCase
def test_raises_argument_error_if_missing_secret
assert_raise(ArgumentError, nil.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(nil)
get :set_signed_cookie
}
assert_raise(ArgumentError, ''.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("")
get :set_signed_cookie
}
end
def test_raises_argument_error_if_secret_is_probably_insecure
assert_raise(ArgumentError, "password".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("password")
get :set_signed_cookie
}
assert_raise(ArgumentError, "secret".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("secret")
get :set_signed_cookie
}
assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("12345678901234567890123456789")
get :set_signed_cookie
}
end
@@ -456,7 +465,42 @@ class CookiesTest < ActionController::TestCase
assert_kind_of ActionDispatch::Cookies::UpgradeLegacySignedCookieJar, cookies.signed
end
- def test_legacy_signed_cookie_is_read_and_transparently_upgraded_if_both_secret_token_and_secret_key_base_are_set
+ def test_signed_or_encrypted_uses_signed_cookie_jar_if_only_secret_token_is_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = nil
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
@@ -473,7 +517,27 @@ class CookiesTest < ActionController::TestCase
assert_equal 45, verifier.verify(@response.cookies["user_id"])
end
- def test_legacy_signed_cookie_is_nil_if_tampered
+ def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
@@ -484,6 +548,17 @@ class CookiesTest < ActionController::TestCase
assert_equal nil, @response.cookies["user_id"]
end
+ def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "foo=baz"
+ get :get_encrypted_cookie
+
+ assert_equal nil, @controller.send(:cookies).encrypted[:foo]
+ assert_equal nil, @response.cookies["foo"]
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 6035f0361e..ff0baccd76 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -29,6 +29,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
raise RuntimeError
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
+ when "/unknown_http_method"
+ raise ActionController::UnknownHttpMethod
when "/not_implemented"
raise ActionController::NotImplemented
when "/unprocessable_entity"
@@ -113,6 +115,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_response 405
assert_match(/ActionController::MethodNotAllowed/, body)
+ get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_match(/ActionController::UnknownHttpMethod/, body)
+
get "/bad_request", {}, {'action_dispatch.show_exceptions' => true}
assert_response 400
assert_match(/ActionController::BadRequest/, body)
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index e2a9ba782d..8a19129695 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -75,7 +75,7 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal expect, Mime::Type.parse(accept)
end
- test "parse arbitarry media type parameters" do
+ test "parse arbitrary media type parameters" do
accept = 'multipart/form-data; boundary="simple boundary"'
expect = [Mime::MULTIPART_FORM]
assert_equal expect, Mime::Type.parse(accept)
@@ -185,11 +185,6 @@ class MimeTypeTest < ActiveSupport::TestCase
all_types.uniq!
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
all_types.delete_if { |type| !Mime.const_defined?(type.upcase) }
- assert_deprecated do
- verified, unverified = all_types.partition { |type| Mime::Type.browser_generated_types.include? type }
- assert verified.each { |type| assert Mime.const_get(type.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" }
- assert unverified.each { |type| assert !Mime.const_get(type.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" }
- end
end
test "references gives preference to symbols before strings" do
diff --git a/actionpack/test/dispatch/rack_test.rb b/actionpack/test/dispatch/rack_test.rb
index 6d239d0a0c..42067854ee 100644
--- a/actionpack/test/dispatch/rack_test.rb
+++ b/actionpack/test/dispatch/rack_test.rb
@@ -174,22 +174,6 @@ class RackRequestParamsParsingTest < BaseRackTest
end
end
-class RackRequestContentTypeTest < BaseRackTest
- test "html content type verification" do
- assert_deprecated do
- @request.env['CONTENT_TYPE'] = Mime::HTML.to_s
- assert @request.content_mime_type.verify_request?
- end
- end
-
- test "xml content type verification" do
- assert_deprecated do
- @request.env['CONTENT_TYPE'] = Mime::XML.to_s
- assert !@request.content_mime_type.verify_request?
- end
- end
-end
-
class RackRequestNeedsRewoundTest < BaseRackTest
test "body should be rewound" do
data = 'foo'
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index 7d3fc84089..b62ed6a8b2 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -50,7 +50,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
output = StringIO.new
json = "[\"person]\": {\"name\": \"David\"}}"
post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output)}
- assert_response :error
+ assert_response :bad_request
output.rewind && err = output.read
assert err =~ /Error occurred while parsing request parameters/
end
@@ -62,7 +62,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
$stderr = StringIO.new # suppress the log
json = "[\"person]\": {\"name\": \"David\"}}"
exception = assert_raise(ActionDispatch::ParamsParser::ParseError) { post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => false} }
- assert_equal MultiJson::DecodeError, exception.original_exception.class
+ assert_equal JSON::ParserError, exception.original_exception.class
assert_equal exception.original_exception.message, exception.message
ensure
$stderr = STDERR
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 234ae5764f..4f97d28d2b 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -234,6 +234,15 @@ module ActionDispatch
" PUT /posts/:id(.:format) posts#update",
" DELETE /posts/:id(.:format) posts#destroy"], output
end
+
+ def test_regression_route_with_controller_regexp
+ output = draw do
+ get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
+ end
+
+ assert_equal ["Prefix Verb URI Pattern Controller#Action",
+ " GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output
+ end
end
end
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index d57b1a5637..0e488d2b88 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -69,6 +69,17 @@ module ActionDispatch
end
end
+ test "explicit keys win over implicit keys" do
+ draw do
+ resources :foo do
+ resources :bar, to: SimpleApp.new('foo#show')
+ end
+ end
+
+ assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(1, 2)
+ assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1)
+ end
+
private
def clear!
@set.clear!
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index df359ba77d..16ba746b9c 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1102,6 +1102,57 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#index', @response.body
end
+ def test_scope_with_format_option
+ draw do
+ get "direct/index", as: :no_format_direct, format: false
+
+ scope format: false do
+ get "scoped/index", as: :no_format_scoped
+ end
+ end
+
+ assert_equal "/direct/index", no_format_direct_path
+ assert_equal "/direct/index?format=html", no_format_direct_path(format: "html")
+
+ assert_equal "/scoped/index", no_format_scoped_path
+ assert_equal "/scoped/index?format=html", no_format_scoped_path(format: "html")
+
+ get '/scoped/index'
+ assert_equal "scoped#index", @response.body
+
+ get '/scoped/index.html'
+ assert_equal "Not Found", @response.body
+ end
+
+ def test_resources_with_format_false_from_scope
+ draw do
+ scope format: false do
+ resources :posts
+ resource :user
+ end
+ end
+
+ get "/posts"
+ assert_response :success
+ assert_equal "posts#index", @response.body
+ assert_equal "/posts", posts_path
+
+ get "/posts.html"
+ assert_response :not_found
+ assert_equal "Not Found", @response.body
+ assert_equal "/posts?format=html", posts_path(format: "html")
+
+ get "/user"
+ assert_response :success
+ assert_equal "users#show", @response.body
+ assert_equal "/user", user_path
+
+ get "/user.html"
+ assert_response :not_found
+ assert_equal "Not Found", @response.body
+ assert_equal "/user?format=html", user_path(format: "html")
+ end
+
def test_index
draw do
get '/info' => 'projects#info', :as => 'info'
@@ -1112,6 +1163,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#info', @response.body
end
+ def test_match_with_many_paths_containing_a_slash
+ draw do
+ get 'get/first', 'get/second', 'get/third', :to => 'get#show'
+ end
+
+ get '/get/first'
+ assert_equal 'get#show', @response.body
+
+ get '/get/second'
+ assert_equal 'get#show', @response.body
+
+ get '/get/third'
+ assert_equal 'get#show', @response.body
+ end
+
def test_match_shorthand_with_no_scope
draw do
get 'account/overview'
@@ -1134,6 +1200,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'account#shorthand', @response.body
end
+ def test_match_shorthand_with_multiple_paths_inside_namespace
+ draw do
+ namespace :proposals do
+ put 'activate', 'inactivate'
+ end
+ end
+
+ put '/proposals/activate'
+ assert_equal 'proposals#activate', @response.body
+
+ put '/proposals/inactivate'
+ assert_equal 'proposals#inactivate', @response.body
+ end
+
def test_match_shorthand_inside_namespace_with_controller
draw do
namespace :api do
@@ -2582,6 +2662,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_raises(ArgumentError) { routes.redirect Object.new }
end
+ def test_named_route_check
+ before, after = nil
+
+ draw do
+ before = has_named_route?(:hello)
+ get "/hello", as: :hello, to: "hello#world"
+ after = has_named_route?(:hello)
+ end
+
+ assert !before, "expected to not have named route :hello before route definition"
+ assert after, "expected to have named route :hello after route definition"
+ end
+
def test_explicitly_avoiding_the_named_route
draw do
scope :as => "routes" do
@@ -3271,6 +3364,10 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest
end
get '/' => ok, :as => :alternate_root, :constraints => { :port => 8080 }
+
+ get '/search' => ok, :constraints => { :subdomain => false }
+
+ get '/logs' => ok, :constraints => { :subdomain => true }
end
end
@@ -3297,6 +3394,24 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest
get 'http://www.example.com:8080/'
assert_response :success
end
+
+ test "false constraint expressions check for absence of values" do
+ get 'http://example.com/search'
+ assert_response :success
+ assert_equal 'http://example.com/search', search_url
+
+ get 'http://api.example.com/search'
+ assert_response :not_found
+ end
+
+ test "true constraint expressions check for presence of values" do
+ get 'http://api.example.com/logs'
+ assert_response :success
+ assert_equal 'http://api.example.com/logs', logs_url
+
+ get 'http://example.com/logs'
+ assert_response :not_found
+ end
end
class TestInvalidUrls < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index d8bf22dec8..e99ff46edf 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -1,12 +1,11 @@
require 'abstract_unit'
require 'stringio'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
- Generator = ActiveSupport::DummyKeyGenerator.new(SessionSecret)
+ Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 45f8fc11b3..38bd234f37 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -8,8 +8,12 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
case req.path
when "/not_found"
raise AbstractController::ActionNotFound
+ when "/bad_params"
+ raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new)
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
+ when "/unknown_http_method"
+ raise ActionController::UnknownHttpMethod
when "/not_found_original_exception"
raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
else
@@ -33,6 +37,10 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_response 500
assert_equal "500 error fixture\n", body
+
+ get "/bad_params", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 400
+ assert_equal "400 error fixture\n", body
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
@@ -41,6 +49,10 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
assert_response 405
assert_equal "", body
+
+ get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_equal "", body
end
test "localize rescue error page" do
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index 4123529092..f919592d24 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -56,6 +56,47 @@ module TestUrlGeneration
test "formatting host when protocol is present" do
assert_equal "http://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: "http://")
end
+
+ test "default ports are removed from the host" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:80", protocol: "http://")
+ assert_equal "https://www.example.com/foo", foo_url(host: "www.example.com:443", protocol: "https://")
+ end
+
+ test "port is extracted from the host" do
+ assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "http://")
+ end
+
+ test "port option overides the host" do
+ assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: 8080)
+ end
+
+ test "port option disables the host when set to nil" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: nil)
+ end
+
+ test "port option disables the host when set to false" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: false)
+ end
+
+ test "keep subdomain when key is true" do
+ assert_equal "http://www.example.com/foo", foo_url(subdomain: true)
+ end
+
+ test "keep subdomain when key is missing" do
+ assert_equal "http://www.example.com/foo", foo_url
+ end
+
+ test "omit subdomain when key is nil" do
+ assert_equal "http://example.com/foo", foo_url(subdomain: nil)
+ end
+
+ test "omit subdomain when key is false" do
+ assert_equal "http://example.com/foo", foo_url(subdomain: false)
+ end
+
+ test "omit subdomain when key is blank" do
+ assert_equal "http://example.com/foo", foo_url(subdomain: "")
+ end
end
end
diff --git a/actionpack/test/fixtures/digestor/messages/show.html.erb b/actionpack/test/fixtures/digestor/messages/show.html.erb
index 51b3b61e8e..130e67109e 100644
--- a/actionpack/test/fixtures/digestor/messages/show.html.erb
+++ b/actionpack/test/fixtures/digestor/messages/show.html.erb
@@ -7,7 +7,8 @@
<%= render @message.history.events %>
<%# render "something_missing" %>
+<%# render "something_missing_1" %>
<%
# Template Dependency: messages/form
-%> \ No newline at end of file
+%>
diff --git a/actionpack/test/fixtures/public/400.html b/actionpack/test/fixtures/public/400.html
new file mode 100644
index 0000000000..03be6bedaf
--- /dev/null
+++ b/actionpack/test/fixtures/public/400.html
@@ -0,0 +1 @@
+400 error fixture
diff --git a/actionpack/test/fixtures/public/images/rails.png b/actionpack/test/fixtures/public/images/rails.png
deleted file mode 100644
index b8441f182e..0000000000
--- a/actionpack/test/fixtures/public/images/rails.png
+++ /dev/null
Binary files differ
diff --git a/actionpack/test/fixtures/test/change_priorty.html.erb b/actionpack/test/fixtures/test/change_priority.html.erb
index 5618977d05..5618977d05 100644
--- a/actionpack/test/fixtures/test/change_priorty.html.erb
+++ b/actionpack/test/fixtures/test/change_priority.html.erb
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 11614a45dc..8d0ab7fd81 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -298,13 +298,15 @@ class AssetTagHelperTest < ActionView::TestCase
%(font_path("font.ttf?123")) => %(/fonts/font.ttf?123)
}
- def test_autodiscovery_link_tag_deprecated_types
- result = nil
- assert_deprecated do
- result = auto_discovery_link_tag(:xml)
+ def test_autodiscovery_link_tag_with_unknown_type_but_not_pass_type_option_key
+ assert_raise(ArgumentError) do
+ auto_discovery_link_tag(:xml)
end
+ end
- expected = %(<link href="http://www.example.com" rel="alternate" title="XML" type="application/xml" />)
+ def test_autodiscovery_link_tag_with_unknown_type
+ result = auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml')
+ expected = %(<link href="/feed.xml" rel="alternate" title="XML" type="application/xml" />)
assert_equal expected, result
end
@@ -530,6 +532,17 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal copy, source
end
+ def test_image_path_with_asset_host_proc_returning_nil
+ @controller.config.asset_host = Proc.new do |source|
+ unless source.end_with?("tiff")
+ "cdn.example.com"
+ end
+ end
+
+ assert_equal "/images/file.tiff", image_path("file.tiff")
+ assert_equal "http://cdn.example.com/images/file.png", image_path("file.png")
+ end
+
def test_caching_image_path_with_caching_and_proc_asset_host_using_request
@controller.config.asset_host = Proc.new do |source, request|
if request.ssl?
diff --git a/actionpack/test/template/dependency_tracker_test.rb b/actionpack/test/template/dependency_tracker_test.rb
index 9c68afbdbd..7a9b4b26ac 100644
--- a/actionpack/test/template/dependency_tracker_test.rb
+++ b/actionpack/test/template/dependency_tracker_test.rb
@@ -1,24 +1,24 @@
require 'abstract_unit'
require 'action_view/dependency_tracker'
-class DependencyTrackerTest < ActionView::TestCase
- Neckbeard = lambda {|template| template.source }
- Bowtie = lambda {|template| template.source }
-
- class NeckbeardTracker
- def self.call(name, template)
- ["foo/#{name}"]
- end
+class NeckbeardTracker
+ def self.call(name, template)
+ ["foo/#{name}"]
end
+end
- class FakeTemplate
- attr_reader :source, :handler
+class FakeTemplate
+ attr_reader :source, :handler
- def initialize(source, handler = Neckbeard)
- @source, @handler = source, handler
- end
+ def initialize(source, handler = Neckbeard)
+ @source, @handler = source, handler
end
+end
+
+Neckbeard = lambda {|template| template.source }
+Bowtie = lambda {|template| template.source }
+class DependencyTrackerTest < ActionView::TestCase
def tracker
ActionView::DependencyTracker
end
@@ -44,3 +44,31 @@ class DependencyTrackerTest < ActionView::TestCase
assert_equal [], dependencies
end
end
+
+class ERBTrackerTest < Minitest::Test
+ def make_tracker(name, template)
+ ActionView::DependencyTracker::ERBTracker.new(name, template)
+ end
+
+ def test_dependency_of_erb_template_with_number_in_filename
+ template = FakeTemplate.new("<%# render 'messages/message123' %>", :erb)
+ tracker = make_tracker('messages/_message123', template)
+
+ assert_equal ["messages/message123"], tracker.dependencies
+ end
+
+ def test_finds_dependency_in_correct_directory
+ template = FakeTemplate.new("<%# render(message.topic) %>", :erb)
+ tracker = make_tracker('messages/_message', template)
+
+ assert_equal ["topics/topic"], tracker.dependencies
+ end
+
+ def test_finds_dependency_in_correct_directory_with_underscore
+ template = FakeTemplate.new("<%# render(message_type.messages) %>", :erb)
+ tracker = make_tracker('message_types/_message_type', template)
+
+ assert_equal ["messages/message"], tracker.dependencies
+ end
+end
+
diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb
index e29cecabc0..06735c30d3 100644
--- a/actionpack/test/template/digestor_test.rb
+++ b/actionpack/test/template/digestor_test.rb
@@ -83,6 +83,12 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_logging_of_missing_template_ending_with_number
+ assert_logged "Couldn't find template for digesting: messages/something_missing_1.html" do
+ digest("messages/show")
+ end
+ end
+
def test_nested_template_directory
assert_digest_difference("messages/show") do
change_template("messages/actions/_move")
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index dff0b8bdc2..1ff320224d 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -361,6 +361,16 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, file_field("user", "avatar")
end
+ def test_file_field_with_multiple_behavior
+ expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
+ assert_dom_equal expected, file_field("import", "file", :multiple => true)
+ end
+
+ def test_file_field_with_multiple_behavior_and_explicit_name
+ expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />'
+ assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom")
+ end
+
def test_hidden_field
assert_dom_equal(
'<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 29d63d9653..1715902927 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -100,6 +100,13 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_collection_options_with_element_attributes
+ assert_dom_equal(
+ "<option value=\"USA\" class=\"bold\">USA</option>",
+ options_from_collection_for_select([[ "USA", "USA", { :class => 'bold' } ]], :first, :second)
+ )
+ end
+
def test_string_options_for_select
options = "<option value=\"Denmark\">Denmark</option><option value=\"USA\">USA</option><option value=\"Sweden\">Sweden</option>"
assert_dom_equal(
@@ -1095,12 +1102,11 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_priority_zones_as_regexp
@firm = Firm.new("D")
- priority_zones = /A|D/
@fake_timezones.each_with_index do |tz, i|
- priority_zones.stubs(:===).with(tz).returns(i.zero? || i == 3)
+ tz.stubs(:=~).returns(i.zero? || i == 3)
end
- html = time_zone_select("firm", "time_zone", priority_zones)
+ html = time_zone_select("firm", "time_zone", /A|D/)
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"D\" selected=\"selected\">D</option>" +
@@ -1112,11 +1118,32 @@ class FormOptionsHelperTest < ActionView::TestCase
html
end
+ def test_time_zone_select_with_priority_zones_as_regexp_using_grep_finds_no_zones
+ @firm = Firm.new("D")
+
+ priority_zones = /A|D/
+ @fake_timezones.each do |tz|
+ priority_zones.stubs(:===).with(tz).raises(Exception)
+ end
+
+ html = time_zone_select("firm", "time_zone", priority_zones)
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
def test_time_zone_select_with_default_time_zone_and_nil_value
@firm = Firm.new()
@firm.time_zone = nil
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"B\" selected=\"selected\">B</option>\n" +
"<option value=\"C\">C</option>\n" +
@@ -1127,16 +1154,17 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_select_with_default_time_zone_and_value
- @firm = Firm.new('D')
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
+ @firm = Firm.new('D')
+
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
end
def test_options_for_select_with_element_attributes
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 6c6a142397..70fc6a588b 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -410,15 +410,6 @@ class FormTagHelperTest < ActionView::TestCase
)
end
- def test_submit_tag_with_deprecated_confirmation
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %(<input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />),
- submit_tag("Save", :confirm => "Are you sure?")
- )
- end
- end
-
def test_button_tag
assert_dom_equal(
%(<button name="button" type="submit">Button</button>),
@@ -477,15 +468,6 @@ class FormTagHelperTest < ActionView::TestCase
)
end
- def test_button_tag_with_deprecated_confirmation
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %(<button name="button" type="submit" data-confirm="Are you sure?">Save</button>),
- button_tag("Save", :type => "submit", :confirm => "Are you sure?")
- )
- end
- end
-
def test_image_submit_tag_with_confirmation
assert_dom_equal(
%(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />),
@@ -493,16 +475,6 @@ class FormTagHelperTest < ActionView::TestCase
)
end
- def test_image_submit_tag_with_deprecated_confirmation
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />),
- image_submit_tag("save.gif", :confirm => "Are you sure?")
- )
- end
- end
-
-
def test_color_field_tag
expected = %{<input id="car" name="car" type="color" />}
assert_dom_equal(expected, color_field_tag("car"))
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 1eed8adb62..de6a6eaab3 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -42,48 +42,6 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given))
end
- def test_button_to_function
- assert_deprecated do
- assert_dom_equal %(<input type="button" onclick="alert(&#39;Hello world!&#39;);" value="Greeting" />),
- button_to_function("Greeting", "alert('Hello world!')")
- end
- end
-
- def test_button_to_function_with_onclick
- assert_deprecated do
- assert_dom_equal "<input onclick=\"alert(&#39;Goodbye World :(&#39;); alert(&#39;Hello world!&#39;);\" type=\"button\" value=\"Greeting\" />",
- button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')")
- end
- end
-
- def test_button_to_function_without_function
- assert_deprecated do
- assert_dom_equal "<input onclick=\";\" type=\"button\" value=\"Greeting\" />",
- button_to_function("Greeting")
- end
- end
-
- def test_link_to_function
- assert_deprecated do
- assert_dom_equal %(<a href="#" onclick="alert(&#39;Hello world!&#39;); return false;">Greeting</a>),
- link_to_function("Greeting", "alert('Hello world!')")
- end
- end
-
- def test_link_to_function_with_existing_onclick
- assert_deprecated do
- assert_dom_equal %(<a href="#" onclick="confirm(&#39;Sanity!&#39;); alert(&#39;Hello world!&#39;); return false;">Greeting</a>),
- link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
- end
- end
-
- def test_function_with_href
- assert_deprecated do
- assert_dom_equal %(<a href="http://example.com/" onclick="alert(&#39;Hello world!&#39;); return false;">Greeting</a>),
- link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
- end
- end
-
def test_javascript_tag
self.output_buffer = 'foo'
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 8111e58527..81f3391fcd 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -29,14 +29,6 @@ module RenderTestCases
assert_equal "Hello world!", @view.render(:file => "test/hello_world")
end
- def test_render_file_not_using_full_path
- assert_equal "Hello world!", @view.render(:file => "test/hello_world")
- end
-
- def test_render_file_without_specific_extension
- assert_equal "Hello world!", @view.render(:file => "test/hello_world")
- end
-
# Test if :formats, :locale etc. options are passed correctly to the resolvers.
def test_render_file_with_format
assert_match "<h1>No Comment</h1>", @view.render(:file => "comments/empty", :formats => [:html])
@@ -61,7 +53,7 @@ module RenderTestCases
def test_render_partial_use_last_prepended_format_for_partials_with_the_same_names
@view.lookup_context.formats = [:html]
- assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priorty")
+ assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priority")
end
def test_render_template_with_a_missing_partial_of_another_format
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 8d32205fb8..c94508d678 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -64,13 +64,6 @@ class TestERBTemplate < ActiveSupport::TestCase
@context = Context.new
end
- def test_mime_type_is_deprecated
- template = new_template
- assert_deprecated 'Template#mime_type is deprecated and will be removed' do
- template.mime_type
- end
- end
-
def test_basic_template
@template = new_template
assert_equal "Hello", render
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index e359f47975..f63f235a5c 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -93,15 +93,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_button_to_with_deprecated_confirm
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>},
- button_to("Hello", "http://www.example.com", confirm: "Are you sure?")
- )
- end
- end
-
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>},
@@ -109,15 +100,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_button_to_with_javascript_deprecated_disable_with
- assert_deprecated ":disable_with option is deprecated and will be removed from Rails 4.1. Use 'data: { disable_with: \'Text\' }' instead" do
- 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...")
- )
- end
- end
-
def test_button_to_with_remote_and_form_options
assert_dom_equal(
%{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><div><input type="submit" value="Hello" /></div></form>},
@@ -132,15 +114,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_button_to_with_remote_and_javascript_with_deprecated_confirm
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>},
- button_to("Hello", "http://www.example.com", remote: true, confirm: "Are you sure?")
- )
- end
- 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>},
@@ -148,15 +121,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_button_to_with_remote_and_javascript_deprecated_disable_with
- assert_deprecated ":disable_with option is deprecated and will be removed from Rails 4.1. Use 'data: { disable_with: \'Text\' }' instead" do
- 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
- 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>},
@@ -265,27 +229,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_link_tag_with_deprecated_confirm
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %{<a href="http://www.example.com" data-confirm="Are you sure?">Hello</a>},
- link_to("Hello", "http://www.example.com", confirm: "Are you sure?")
- )
- end
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %{<a href="http://www.example.com" data-confirm="You cant possibly be sure, can you?">Hello</a>},
- link_to("Hello", "http://www.example.com", confirm: "You cant possibly be sure, can you?")
- )
- end
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %{<a href="http://www.example.com" data-confirm="You cant possibly be sure,\n can you?">Hello</a>},
- link_to("Hello", "http://www.example.com", confirm: "You cant possibly be sure,\n can you?")
- )
- end
- end
-
def test_link_to_with_remote
assert_dom_equal(
%{<a href="http://www.example.com" data-remote="true">Hello</a>},
@@ -349,15 +292,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_link_tag_using_post_javascript_and_with_deprecated_confirm
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %{<a href="http://www.example.com" data-method="post" rel="nofollow" data-confirm="Are you serious?">Hello</a>},
- link_to("Hello", "http://www.example.com", method: :post, confirm: "Are you serious?")
- )
- end
- end
-
def test_link_tag_using_delete_javascript_and_href_and_confirm
assert_dom_equal(
%{<a href="\#" rel="nofollow" data-confirm="Are you serious?" data-method="delete">Destroy</a>},
@@ -365,15 +299,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_link_tag_using_delete_javascript_and_href_and_with_deprecated_confirm
- assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do
- assert_dom_equal(
- %{<a href="\#" rel="nofollow" data-confirm="Are you serious?" data-method="delete">Destroy</a>},
- link_to("Destroy", "http://www.example.com", method: :delete, href: '#', confirm: "Are you serious?")
- )
- end
- end
-
def test_link_tag_with_block
assert_dom_equal %{<a href="/"><span>Example site</span></a>},
link_to('/') { content_tag(:span, 'Example site') }
@@ -538,6 +463,22 @@ class UrlHelperTest < ActiveSupport::TestCase
assert mail_to("david@loudthinking.com").html_safe?
end
+ def test_mail_to_with_block
+ assert_dom_equal %{<a href="mailto:me@example.com"><span>Email me</span></a>},
+ mail_to('me@example.com') { content_tag(:span, 'Email me') }
+ end
+
+ def test_mail_to_with_block_and_options
+ assert_dom_equal %{<a class="special" href="mailto:me@example.com?cc=ccaddress%40example.com"><span>Email me</span></a>},
+ mail_to('me@example.com', cc: "ccaddress@example.com", class: "special") { content_tag(:span, 'Email me') }
+ end
+
+ def test_mail_to_does_not_modify_html_options_hash
+ options = { class: 'special' }
+ mail_to 'me@example.com', 'ME!', options
+ assert_equal({ class: 'special' }, options)
+ end
+
def protect_against_forgery?
self.request_forgery
end
@@ -596,7 +537,7 @@ class UrlHelperControllerTest < ActionController::TestCase
render inline: "<%= url_for controller: 'url_helper_controller_test/url_helper', action: 'show_url_for' %>"
end
- def show_overriden_url_for
+ def show_overridden_url_for
render inline: "<%= url_for params.merge(controller: 'url_helper_controller_test/url_helper', action: 'show_url_for') %>"
end
@@ -633,8 +574,8 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
- def test_overriden_url_for_shows_only_path
- get :show_overriden_url_for
+ def test_overridden_url_for_shows_only_path
+ get :show_overridden_url_for
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
@@ -684,7 +625,7 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal 'ok', @response.body
end
- def test_url_helper_can_be_overriden
+ def test_url_helper_can_be_overridden
get :override_url_helper
assert_equal '/url_helper_controller_test/url_helper/override_url_helper/override', @response.body
end
diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb
deleted file mode 100644
index 55620abe84..0000000000
--- a/actionpack/test/ts_isolated.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'active_support/testing/autorun'
-require 'rbconfig'
-require 'abstract_unit'
-
-class TestIsolated < ActiveSupport::TestCase
- ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
-
- Dir["#{File.dirname(__FILE__)}/{abstract,controller,dispatch,template}/**/*_test.rb"].each do |file|
- define_method("test #{file}") do
- command = "#{ruby} -Ilib:test #{file}"
- result = silence_stderr { `#{command}` }
- assert $?.to_i.zero?, "#{command}\n#{result}"
- end
- end
-end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index e4c80b1bf8..6fc34ecd60 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,199 +1,10 @@
-## Rails 4.0.0 (unreleased) ##
+* Fix regression in has_secure_password. When a password is set, but a
+ confirmation is an empty string, it would incorrectly save.
-* Add ActiveModel::Errors#full_messages_for, a method that returns all the error
- messages for a given attribute.
+ *Steve Klabnik* and *Phillip Calvin*
- *Volodymyr Shatsky*
+* Deprecate `Validator#setup`. This should be done manually now in the validator's constructor.
-* Added a method so that validations can be easily cleared on a model.
- For example:
+ *Nick Sutterer*
- class Person
- include ActiveModel::Validations
-
- validates_uniqueness_of :first_name
- validate :cannot_be_robot
-
- def cannot_be_robot
- errors.add(:base, 'A person cannot be a robot') if person_is_robot
- end
- end
-
- Now, if someone runs `Person.clear_validators!`, then the following occurs:
-
- Person.validators # => []
- Person._validate_callbacks.empty? # => true
-
- *John Wang*
-
-* `has_secure_password` does not fail the confirmation validation
- when assigning empty String to `password` and `password_confirmation`.
-
- Example:
-
- # Given User has_secure_password.
- @user.password = ""
- @user.password_confirmation = ""
- @user.valid?(:update) # used to be false
-
- *Yves Senn*
-
-* `validates_confirmation_of` does not override writer methods for
- the confirmation attribute if no reader is defined.
-
- Example:
-
- class Blog
- def title=(new_title)
- @title = new_title.downcase
- end
-
- # previously this would override the setter above.
- validates_confirmation_of :title
- end
-
- *Yves Senn*
-
-## Rails 4.0.0.beta1 (February 25, 2013) ##
-
-* Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the
- absence of attributes.
-
- class Person
- include ActiveModel::Validations
-
- attr_accessor :first_name
- validates_absence_of :first_name
- end
-
- person = Person.new
- person.first_name = "John"
- person.valid?
- # => false
- person.errors.messages
- # => {:first_name=>["must be blank"]}
-
- *Roberto Vasquez Angel*
-
-* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`.
-
- *Renato Mascarenhas*
-
-* Observers was extracted from Active Model as `rails-observers` gem.
-
- *Rafael Mendonça França*
-
-* Specify type of singular association during serialization.
-
- *Steve Klabnik*
-
-* Fixed length validator to correctly handle `nil`. Fixes #7180.
-
- *Michal Zima*
-
-* Removed dispensable `require` statements. Make sure to require `active_model` before requiring
- individual parts of the framework.
-
- *Yves Senn*
-
-* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_password`.
-
- *Brian Cardarella + Jeremy Kemper + Trevor Turk*
-
-* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to
- protect attributes from mass assignment when non-permitted attributes are passed.
-
- *DHH + Guillermo Iguaran*
-
-* `ActiveModel::MassAssignmentSecurity` has been extracted from Active Model and the
- `protected_attributes` gem should be added to Gemfile in order to use
- `attr_accessible` and `attr_protected` macros in your models.
-
- *Guillermo Iguaran*
-
-* Due to a change in builder, `nil` and empty strings now generate
- closed tags, so instead of this:
-
- <pseudonyms nil=\"true\"></pseudonyms>
-
- it generates this:
-
- <pseudonyms nil=\"true\"/>
-
- *Carlos Antonio da Silva*
-
-* Inclusion/exclusion validators accept a method name passed as a symbol to the
- `:in` option.
-
- This allows to use dynamic inclusion/exclusion values using methods, besides
- the current lambda/proc support.
-
- *Gabriel Sobrinho*
-
-* `ActiveModel::Validation#validates` ability to pass custom exception to the
- `:strict` option.
-
- *Bogdan Gusiev*
-
-* Changed `ActiveModel::Serializers::Xml::Serializer#add_associations` to by default
- propagate `:skip_types, :dasherize, :camelize` keys to included associations.
- It can be overridden on each association by explicitly specifying the option on one
- or more associations
-
- *Anthony Alberto*
-
-* Changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to false.
- Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578.
-
- class User < ActiveRecord::Base; end
-
- class Person
- include ActiveModel::Model
- include ActiveModel::AttributeMethods
- include ActiveModel::Serializers::JSON
-
- attr_accessor :name, :age
-
- def attributes
- instance_values
- end
- end
-
- user.as_json
- => {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true}
- # root is not included
-
- person.as_json
- => {"name"=>"Francesco", "age"=>22}
- # root is not included
-
- *Francesco Rodriguez*
-
-* Passing false hash values to `validates` will no longer enable the corresponding validators.
-
- *Steve Purcell*
-
-* `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*
-
-* Trim down Active Model API by removing `valid?` and `errors.full_messages`.
-
- *José Valim*
-
-* When `^` or `$` are used in the regular expression provided to `validates_format_of`
- and the `:multiline` option is not set to true, an exception will be raised. This is
- to prevent security vulnerabilities when using `validates_format_of`. The problem is
- described in detail in the Rails security guide.
-
- *Jan Berdajs + Egor Homakov*
-
-Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activemodel/CHANGELOG.md) for previous changes.
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 1b1fe2fa2b..a399fe9051 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -2,7 +2,7 @@
Active Model provides a known set of interfaces for usage in model classes.
They allow for Action Pack helpers to interact with non-Active Record models,
-for example. Active Model also helps building custom ORMs for use outside of
+for example. Active Model also helps with building custom ORMs for use outside of
the Rails framework.
Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
@@ -24,8 +24,8 @@ to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
end
person = Person.new(name: 'bob', age: '18')
- person.name # => 'bob'
- person.age # => '18'
+ person.name # => 'bob'
+ person.age # => '18'
person.valid? # => true
It includes model name introspections, conversions, translations and
@@ -82,12 +82,12 @@ behavior out of the box:
end
person = Person.new
- person.name # => nil
- person.changed? # => false
+ person.name # => nil
+ person.changed? # => false
person.name = 'bob'
- person.changed? # => true
- person.changed # => ['name']
- person.changes # => { 'name' => [nil, 'bob'] }
+ person.changed? # => true
+ person.changed # => ['name']
+ person.changes # => { 'name' => [nil, 'bob'] }
person.name = 'robert'
person.save
person.previous_changes # => {'name' => ['bob, 'robert']}
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index fc5aaf9f8f..45d1587ed6 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -20,7 +20,6 @@ namespace :test do
end
end
-require 'rake/packagetask'
require 'rubygems/package_task'
spec = eval(File.read("#{dir}/activemodel.gemspec"))
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 6d11c0fbdc..98cde8ba59 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -12,19 +12,21 @@ module ActiveModel
# # => ActiveModel::MissingAttributeError: missing attribute: user_id
class MissingAttributeError < NoMethodError
end
+
# == Active \Model Attribute Methods
#
# <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and
- # suffixes to your methods as well as handling the creation of Active Record
- # like class methods such as +table_name+.
+ # suffixes to your methods as well as handling the creation of
+ # <tt>ActiveRecord::Base</tt>-like class methods such as +table_name+.
#
- # The requirements to implement ActiveModel::AttributeMethods are to:
+ # The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to:
#
- # * <tt>include ActiveModel::AttributeMethods</tt> in your object.
- # * Call each Attribute Method module method you want to add, such as
- # +attribute_method_suffix+ or +attribute_method_prefix+.
+ # * <tt>include ActiveModel::AttributeMethods</tt> in your class.
+ # * Call each of its method you want to add, such as +attribute_method_suffix+
+ # or +attribute_method_prefix+.
# * Call +define_attribute_methods+ after the other methods are called.
# * Define the various generic +_attribute+ methods that you have declared.
+ # * Define an +attributes+ method, see below.
#
# A minimal implementation could be:
#
@@ -38,6 +40,10 @@ module ActiveModel
#
# attr_accessor :name
#
+ # def attributes
+ # {'name' => @name}
+ # end
+ #
# private
#
# def attribute_contrived?(attr)
@@ -53,10 +59,10 @@ module ActiveModel
# end
# end
#
- # Note that whenever you include ActiveModel::AttributeMethods in your class,
- # it requires you to implement an +attributes+ method which returns a hash
- # with each attribute name in your model as hash key and the attribute value as
- # hash value.
+ # Note that whenever you include <tt>ActiveModel::AttributeMethods</tt> in
+ # your class, it requires you to implement an +attributes+ method which
+ # returns a hash with each attribute name in your model as hash key and the
+ # attribute value as hash value.
#
# Hash keys must be strings.
module AttributeMethods
@@ -179,7 +185,6 @@ module ActiveModel
undefine_attribute_methods
end
-
# Allows you to make aliases for attributes.
#
# class Person
@@ -338,7 +343,7 @@ module ActiveModel
# significantly (in our case our test suite finishes 10% faster with
# this cache).
def attribute_method_matchers_cache #:nodoc:
- @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(:initial_capacity => 4)
+ @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4)
end
def attribute_method_matcher(method_name) #:nodoc:
@@ -413,17 +418,16 @@ module ActiveModel
end
end
- # Allows access to the object attributes, which are held in the
- # <tt>@attributes</tt> hash, as though they were first-class methods. So a
- # Person class with a name attribute can use Person#name and Person#name=
- # and never directly use the attributes hash -- except for multiple assigns
- # with ActiveRecord#attributes=. A Milestone class can also ask
- # Milestone#completed? to test that the completed attribute is not +nil+
- # or 0.
+ # Allows access to the object attributes, which are held in the hash
+ # returned by <tt>attributes</tt>, as though they were first-class
+ # methods. So a +Person+ class with a +name+ attribute can for example use
+ # <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
+ # the attributes hash -- except for multiple assigns with
+ # <tt>ActiveRecord::Base#attributes=</tt>.
#
- # It's also possible to instantiate related objects, so a Client class
- # belonging to the clients table with a +master_id+ foreign key can
- # instantiate master through Client#master.
+ # It's also possible to instantiate related objects, so a <tt>Client</tt>
+ # class belonging to the +clients+ table with a +master_id+ foreign key
+ # can instantiate master through <tt>Client#master</tt>.
def method_missing(method, *args, &block)
if respond_to_without_attributes?(method, true)
super
@@ -433,17 +437,17 @@ module ActiveModel
end
end
- # attribute_missing is like method_missing, but for attributes. When method_missing is
- # called we check to see if there is a matching attribute method. If so, we call
- # attribute_missing to dispatch the attribute. This method can be overloaded to
- # customize the behavior.
+ # +attribute_missing+ is like +method_missing+, but for attributes. When
+ # +method_missing+ is called we check to see if there is a matching
+ # attribute method. If so, we tell +attribute_missing+ to dispatch the
+ # attribute. This method can be overloaded to customize the behavior.
def attribute_missing(match, *args, &block)
__send__(match.target, match.attr_name, *args, &block)
end
- # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
- # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
- # which will all return +true+.
+ # A +Person+ instance with a +name+ attribute can ask
+ # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
+ # and <tt>person.respond_to?(:name?)</tt> which will all return +true+.
alias :respond_to_without_attributes? :respond_to?
def respond_to?(method, include_private_methods = false)
if super
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index b5562dda2e..377aa6ee27 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -100,10 +100,10 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
- :terminator => "result == false",
- :skip_after_callbacks_if_terminated => true,
- :scope => [:kind, :name],
- :only => [:before, :around, :after]
+ terminator: ->(_,result) { result == false },
+ skip_after_callbacks_if_terminated: true,
+ scope: [:kind, :name],
+ only: [:before, :around, :after]
}.merge!(options)
types = Array(options.delete(:only))
@@ -120,30 +120,27 @@ module ActiveModel
private
def _define_before_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
- def self.before_#{callback}(*args, &block)
- set_callback(:#{callback}, :before, *args, &block)
- end
- CALLBACK
+ klass.define_singleton_method("before_#{callback}") do |*args, &block|
+ set_callback(:"#{callback}", :before, *args, &block)
+ end
end
def _define_around_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
- def self.around_#{callback}(*args, &block)
- set_callback(:#{callback}, :around, *args, &block)
- end
- CALLBACK
+ klass.define_singleton_method("around_#{callback}") do |*args, &block|
+ set_callback(:"#{callback}", :around, *args, &block)
+ end
end
def _define_after_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
- def self.after_#{callback}(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- options[:if] = Array(options[:if]) << "value != false"
- set_callback(:#{callback}, :after, *(args << options), &block)
- end
- CALLBACK
+ klass.define_singleton_method("after_#{callback}") do |*args, &block|
+ options = args.extract_options!
+ options[:prepend] = true
+ conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
+ v != false
+ }
+ options[:if] = Array(options[:if]) << conditional
+ set_callback(:"#{callback}", :after, *(args << options), &block)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 6e67cd2285..ea5ddf71de 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -46,7 +46,7 @@ module ActiveModel
#
# A newly instantiated object is unchanged:
#
- # person = Person.find_by_name('Uncle Bob')
+ # person = Person.find_by(name: 'Uncle Bob')
# person.changed? # => false
#
# Change the name:
@@ -91,7 +91,7 @@ module ActiveModel
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
- attribute_method_affix :prefix => 'reset_', :suffix => '!'
+ attribute_method_affix prefix: 'reset_', suffix: '!'
end
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
@@ -142,23 +142,23 @@ module ActiveModel
@changed_attributes ||= {}
end
- private
+ # Handle <tt>*_changed?</tt> for +method_missing+.
+ def attribute_changed?(attr)
+ changed_attributes.include?(attr)
+ end
- # Handle <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr)
- changed_attributes.include?(attr)
- end
+ # Handle <tt>*_was</tt> for +method_missing+.
+ def attribute_was(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ end
+
+ private
# Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
- # Handle <tt>*_was</tt> for +method_missing+.
- def attribute_was(attr)
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
- end
-
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 485eb1a40a..0d7efab04b 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -12,7 +12,6 @@ module ActiveModel
# A minimal implementation could be:
#
# class Person
- #
# # Required dependency for ActiveModel::Errors
# extend ActiveModel::Naming
#
@@ -40,7 +39,6 @@ module ActiveModel
# def Person.lookup_ancestors
# [self]
# end
- #
# end
#
# The last three methods are required in your object for Errors to be
@@ -233,7 +231,7 @@ module ActiveModel
# # <error>name must be specified</error>
# # </errors>
def to_xml(options={})
- to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
+ to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
end
# Returns a Hash that can be used as the JSON representation for this
@@ -372,11 +370,11 @@ module ActiveModel
def full_message(attribute, message)
return message if attribute == :base
attr_name = attribute.to_s.tr('.', '_').humanize
- attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
+ attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
I18n.t(:"errors.format", {
- :default => "%{attribute} %{message}",
- :attribute => attr_name,
- :message => message
+ default: "%{attribute} %{message}",
+ attribute: attr_name,
+ message: message
})
end
@@ -428,10 +426,10 @@ module ActiveModel
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
options = {
- :default => defaults,
- :model => @base.class.model_name.human,
- :attribute => @base.class.human_attribute_name(attribute),
- :value => value
+ default: defaults,
+ model: @base.class.model_name.human,
+ attribute: @base.class.human_attribute_name(attribute),
+ value: value
}.merge!(options)
I18n.translate(key, options)
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 1be2913f0b..46b446dc08 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -98,7 +98,7 @@ module ActiveModel
private
def model
- assert @model.respond_to?(:to_model), "The object should respond_to to_model"
+ assert @model.respond_to?(:to_model), "The object should respond to to_model"
@model.to_model
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 6887f6d781..bc9edf4a56 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -129,7 +129,7 @@ module ActiveModel
#
# Equivalent to +to_s+.
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
- :to_str, :to => :name
+ :to_str, to: :name
# Returns a new ActiveModel::Name instance. By default, the +namespace+
# and +name+ option will take the namespace and name of the given class
@@ -183,7 +183,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/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 474cb0aea0..e553590671 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -30,15 +30,15 @@ module ActiveModel
# end
#
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
- # user.save # => false, password required
+ # user.save # => false, password required
# user.password = 'mUc3m00RsqyRe'
- # user.save # => false, confirmation doesn't match
+ # user.save # => false, confirmation doesn't match
# user.password_confirmation = 'mUc3m00RsqyRe'
- # user.save # => true
- # user.authenticate('notright') # => false
- # user.authenticate('mUc3m00RsqyRe') # => user
- # User.find_by_name('david').try(:authenticate, 'notright') # => false
- # User.find_by_name('david').try(:authenticate, 'mUc3m00RsqyRe') # => user
+ # user.save # => true
+ # user.authenticate('notright') # => false
+ # 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(options = {})
# Load bcrypt-ruby only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
@@ -56,8 +56,9 @@ module ActiveModel
include InstanceMethodsOnActivation
if options.fetch(:validations, true)
- validates_confirmation_of :password
- validates_presence_of :password, :on => :create
+ validates_confirmation_of :password, if: lambda { |m| m.password.present? }
+ validates_presence_of :password, on: :create
+ validates_presence_of :password_confirmation, if: lambda { |m| m.password.present? }
before_create { raise "Password digest missing on new record" if password_digest.blank? }
end
@@ -106,9 +107,7 @@ module ActiveModel
end
def password_confirmation=(unencrypted_password)
- unless unencrypted_password.blank?
- @password_confirmation = unencrypted_password
- end
+ @password_confirmation = unencrypted_password
end
end
end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 9d984b7a18..05e2e089e5 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -109,7 +109,7 @@ module ActiveModel
#
# def attributes=(hash)
# hash.each do |key, value|
- # instance_variable_set("@#{key}", value)
+ # send("#{key}=", value)
# end
# end
#
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 648ae7ce3d..2803f69b6f 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -79,7 +79,7 @@ module ActiveModel
require 'builder' unless defined? ::Builder
options[:indent] ||= 2
- options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
+ options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
@builder = options[:builder]
@builder.instruct! unless options[:skip_instruct]
@@ -88,8 +88,8 @@ module ActiveModel
root = ActiveSupport::XmlMini.rename_key(root, options)
args = [root]
- args << {:xmlns => options[:namespace]} if options[:namespace]
- args << {:type => options[:type]} if options[:type] && !options[:skip_types]
+ args << { xmlns: options[:namespace] } if options[:namespace]
+ args << { type: options[:type] } if options[:type] && !options[:skip_types]
@builder.tag!(*args) do
add_attributes_and_methods
@@ -132,7 +132,7 @@ module ActiveModel
records = records.to_ary
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
- type = options[:skip_types] ? { } : {:type => "array"}
+ type = options[:skip_types] ? { } : { type: "array" }
association_name = association.to_s.singularize
merged_options[:root] = association_name
@@ -145,7 +145,7 @@ module ActiveModel
record_type = {}
else
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
- record_type = {:type => record_class}
+ record_type = { type: record_class }
end
record.to_xml merged_options.merge(record_type)
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 0d098ba93d..8470915abb 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -41,7 +41,7 @@ module ActiveModel
#
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
- options = { :count => 1 }.merge!(options)
+ options = { count: 1 }.merge!(options)
parts = attribute.to_s.split(".")
attribute = parts.pop
namespace = parts.join("/") unless parts.empty?
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 714cc95b9a..31c2245265 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -46,7 +46,7 @@ module ActiveModel
include HelperMethods
attr_accessor :validation_context
- define_callbacks :validate, :scope => :name
+ define_callbacks :validate, scope: :name
class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
@@ -142,7 +142,9 @@ module ActiveModel
if options.key?(:on)
options = options.dup
options[:if] = Array(options[:if])
- options[:if].unshift("validation_context == :#{options[:on]}")
+ options[:if].unshift lambda { |o|
+ o.validation_context == options[:on]
+ }
end
args << options
set_callback(:validate, *args, &block)
@@ -226,7 +228,6 @@ module ActiveModel
# Person.validators_on(:name)
# # => [
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
- # # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}>
# # ]
def validators_on(*attributes)
attributes.flat_map do |attribute|
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 0935ad0d2a..139de16326 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -3,7 +3,8 @@ module ActiveModel
module Validations
class AcceptanceValidator < EachValidator # :nodoc:
def initialize(options)
- super({ :allow_nil => true, :accept => "1" }.merge!(options))
+ super({ allow_nil: true, accept: "1" }.merge!(options))
+ setup!(options[:class])
end
def validate_each(record, attribute, value)
@@ -12,7 +13,8 @@ module ActiveModel
end
end
- def setup(klass)
+ private
+ def setup!(klass)
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
klass.send(:attr_reader, *attr_readers)
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index e28ad2841b..cabb9482f2 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -22,7 +22,10 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
- define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name]
+ define_callbacks :validation,
+ terminator: ->(_,result) { result == false },
+ skip_after_callbacks_if_terminated: true,
+ scope: [:kind, :name]
end
module ClassMethods
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index d14fb4dc53..b0542661af 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -2,14 +2,20 @@ module ActiveModel
module Validations
class ConfirmationValidator < EachValidator # :nodoc:
+ def initialize(options)
+ super
+ setup!(options[:class])
+ end
+
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
human_attribute_name = record.class.human_attribute_name(attribute)
- record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(attribute: human_attribute_name))
end
end
- def setup(klass)
+ private
+ def setup!(klass)
klass.send(:attr_reader, *attributes.map do |attribute|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
end.compact)
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index b7f38e48f5..48bf5cd802 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -8,7 +8,7 @@ module ActiveModel
def validate_each(record, attribute, value)
if include?(record, value)
- record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(:value => value))
+ record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 9398b7e66e..be7cae588f 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -29,7 +29,7 @@ module ActiveModel
end
def record_error(record, attribute, name, value)
- record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
+ record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
end
def regexp_using_multiline_anchors?(regexp)
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 5e45a04c2c..1cfd86efee 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -8,7 +8,7 @@ module ActiveModel
def validate_each(record, attribute, value)
unless include?(record, value)
- record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(:value => value))
+ record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value))
end
end
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 675fb5f1e5..ddfd8a342e 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -3,8 +3,8 @@ module ActiveModel
# == Active \Model Length \Validator
module Validations
class LengthValidator < EachValidator # :nodoc:
- MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
- CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
+ MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
+ CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 085532c35b..c6abe45f4a 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -2,9 +2,9 @@ module ActiveModel
module Validations
class NumericalityValidator < EachValidator # :nodoc:
- CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
- :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
- :odd => :odd?, :even => :even?, :other_than => :!= }.freeze
+ CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
+ equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
+ odd: :odd?, even: :even?, other_than: :!= }.freeze
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
@@ -47,7 +47,7 @@ module ActiveModel
option_value = record.send(option_value) if option_value.is_a?(Symbol)
unless value.send(CHECKS[option], option_value)
- record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value))
+ record.errors.add(attr_name, option, filtered_options(value).merge(count: option_value))
end
end
end
@@ -73,7 +73,7 @@ module ActiveModel
end
def filtered_options(value)
- options.except(*RESERVED_OPTIONS).merge!(:value => value)
+ options.except(*RESERVED_OPTIONS).merge!(value: value)
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 1eb0716891..9a1ff2ad39 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -159,9 +159,9 @@ module ActiveModel
when Hash
options
when Range, Array
- { :in => options }
+ { in: options }
else
- { :with => options }
+ { with: options }
end
end
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 2ae335d0f4..16bd6670d1 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -83,9 +83,10 @@ module ActiveModel
# end
def validates_with(*args, &block)
options = args.extract_options!
+ options[:class] = self
+
args.each do |klass|
validator = klass.new(options, &block)
- validator.setup(self) if validator.respond_to?(:setup)
if validator.respond_to?(:attributes) && !validator.attributes.empty?
validator.attributes.each do |attribute|
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 037650e5ac..690856aee1 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -82,18 +82,16 @@ module ActiveModel
# validates :title, presence: true
# end
#
- # Validator may also define a +setup+ instance method which will get called
- # with the class that using that validator as its argument. This can be
- # useful when there are prerequisites such as an +attr_accessor+ being present.
+ # It can be useful to access the class that is using that validator when there are prerequisites such
+ # as an +attr_accessor+ being present. This class is accessable via +options[:class]+ in the constructor.
+ # To setup your validator override the constructor.
#
# class MyValidator < ActiveModel::Validator
- # def setup(klass)
- # klass.send :attr_accessor, :custom_attribute
+ # def initialize(options={})
+ # super
+ # options[:class].send :attr_accessor, :custom_attribute
# end
# end
- #
- # This setup method is only called when used with validation macros or the
- # class level <tt>validates_with</tt> method.
class Validator
attr_reader :options
@@ -107,7 +105,8 @@ module ActiveModel
# Accepts options that will be made available through the +options+ reader.
def initialize(options = {})
- @options = options.freeze
+ @options = options.except(:class).freeze
+ deprecated_setup(options)
end
# Return the kind for this validator.
@@ -123,6 +122,21 @@ module ActiveModel
def validate(record)
raise NotImplementedError, "Subclasses must implement a validate(record) method."
end
+
+ private
+ def deprecated_setup(options) # TODO: remove me in 4.2.
+ return unless respond_to?(:setup)
+ ActiveSupport::Deprecation.warn "The `Validator#setup` instance method is deprecated and will be removed on Rails 4.2. Do your setup in the constructor instead:
+
+class MyValidator < ActiveModel::Validator
+ def initialize(options={})
+ super
+ options[:class].send :attr_accessor, :custom_attribute
+ end
+end
+"
+ setup(options[:class])
+ end
end
# +EachValidator+ is a validator which iterates through the attributes given
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 35c4b30999..86340bba37 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,7 +1,7 @@
module ActiveModel
# Returns the version of the currently loaded ActiveModel as a Gem::Version
def self.version
- Gem::Version.new "4.0.0.beta1"
+ Gem::Version.new "4.1.0.beta"
end
module VERSION #:nodoc:
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index d3ec78157c..25eb4860e3 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', :baz => 'value of baz' }
+ { foo: 'value of foo', baz: 'value of baz' }
end
private
@@ -80,7 +80,7 @@ class ModelWithRubyKeywordNamedAttributes
include ActiveModel::AttributeMethods
def attributes
- { :begin => 'value of begin', :end => 'value of end' }
+ { begin: 'value of begin', end: 'value of end' }
end
private
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index c4c34b0be7..5fede098d1 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -15,9 +15,9 @@ class CallbacksTest < ActiveModel::TestCase
extend ActiveModel::Callbacks
define_model_callbacks :create
- define_model_callbacks :initialize, :only => :after
- define_model_callbacks :multiple, :only => [:before, :around]
- define_model_callbacks :empty, :only => []
+ define_model_callbacks :initialize, only: :after
+ define_model_callbacks :multiple, only: [:before, :around]
+ define_model_callbacks :empty, only: []
before_create :before_create
around_create CallbackValidator.new
diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb
index a037666cbc..3bb177591d 100644
--- a/activemodel/test/cases/conversion_test.rb
+++ b/activemodel/test/cases/conversion_test.rb
@@ -13,7 +13,7 @@ class ConversionTest < ActiveModel::TestCase
end
test "to_key default implementation returns the id in an array for persisted records" do
- assert_equal [1], Contact.new(:id => 1).to_key
+ assert_equal [1], Contact.new(id: 1).to_key
end
test "to_param default implementation returns nil for new records" do
@@ -21,7 +21,7 @@ class ConversionTest < ActiveModel::TestCase
end
test "to_param default implementation returns a string of ids for persisted records" do
- assert_equal "1", Contact.new(:id => 1).to_param
+ assert_equal "1", Contact.new(id: 1).to_param
end
test "to_partial_path default implementation returns a string giving a relative path" do
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 139fb6795a..4e07e0e00b 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -54,7 +54,7 @@ class ErrorsTest < ActiveModel::TestCase
assert errors.has_key?(:foo), 'errors should have key :foo'
end
- test "should be able to clear the errors" do
+ test "clear errors" do
person = Person.new
person.validate!
@@ -63,7 +63,7 @@ class ErrorsTest < ActiveModel::TestCase
assert person.errors.empty?
end
- test "get returns the error by the provided key" do
+ test "get returns the errors for the provided key" do
errors = ActiveModel::Errors.new(self)
errors[:foo] = "omg"
@@ -93,21 +93,7 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal [:foo, :baz], errors.keys
end
- test "as_json returns a json formatted representation of the errors hash" do
- person = Person.new
- person.validate!
-
- assert_equal({ name: ["can not be nil"] }, person.errors.as_json)
- end
-
- test "as_json with :full_messages option" do
- person = Person.new
- person.validate!
-
- assert_equal({ name: ["name can not be nil"] }, person.errors.as_json(full_messages: true))
- end
-
- test "should return true if no errors" do
+ test "detecting whether there are errors with empty?, blank?, include?" do
person = Person.new
person.errors[:foo]
assert person.errors.empty?
@@ -115,160 +101,154 @@ class ErrorsTest < ActiveModel::TestCase
assert !person.errors.include?(:foo)
end
- test "method validate! should work" do
+ test "adding errors using conditionals with Person#validate!" do
person = Person.new
person.validate!
assert_equal ["name can not be nil"], person.errors.full_messages
assert_equal ["can not be nil"], person.errors[:name]
end
- test 'should be able to assign error' do
+ test "assign error" do
person = Person.new
person.errors[:name] = 'should not be nil'
assert_equal ["should not be nil"], person.errors[:name]
end
- test 'should be able to add an error on an attribute' do
+ test "add an error message on a specific attribute" do
person = Person.new
person.errors.add(:name, "can not be blank")
assert_equal ["can not be blank"], person.errors[:name]
end
- test "should be able to add an error with a symbol" do
+ test "add an error with a symbol" do
person = Person.new
person.errors.add(:name, :blank)
message = person.errors.generate_message(:name, :blank)
assert_equal [message], person.errors[:name]
end
- test "should be able to add an error with a proc" do
+ test "add an error with a proc" do
person = Person.new
message = Proc.new { "can not be blank" }
person.errors.add(:name, message)
assert_equal ["can not be blank"], person.errors[:name]
end
- test "added? should be true if that error was added" do
+ test "added? detects if a specific error was added to the object" do
person = Person.new
person.errors.add(:name, "can not be blank")
assert person.errors.added?(:name, "can not be blank")
end
- test "added? should handle when message is a symbol" do
+ test "added? handles symbol message" do
person = Person.new
person.errors.add(:name, :blank)
assert person.errors.added?(:name, :blank)
end
- test "added? should handle when message is a proc" do
+ test "added? handles proc messages" do
person = Person.new
message = Proc.new { "can not be blank" }
person.errors.add(:name, message)
assert person.errors.added?(:name, message)
end
- test "added? should default message to :invalid" do
+ test "added? defaults message to :invalid" do
person = Person.new
person.errors.add(:name)
assert person.errors.added?(:name)
end
- test "added? should be true when several errors are present, and we ask for one of them" do
+ test "added? matches the given message when several errors are present for the same attribute" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "is invalid")
assert person.errors.added?(:name, "can not be blank")
end
- test "added? should be false if no errors are present" do
+ test "added? returns false when no errors are present" do
person = Person.new
assert !person.errors.added?(:name)
end
- test "added? should be false when an error is present, but we check for another error" do
+ test "added? returns false when checking a nonexisting error and other errors are present for the given attribute" do
person = Person.new
person.errors.add(:name, "is invalid")
assert !person.errors.added?(:name, "can not be blank")
end
- test 'should respond to size' do
+ test "size calculates the number of error messages" do
person = Person.new
person.errors.add(:name, "can not be blank")
assert_equal 1, person.errors.size
end
- test 'to_a should return an array' do
+ test "to_a returns the list of errors with complete messages containing the attribute names" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
end
- test 'to_hash should return a hash' do
+ test "to_hash returns the error messages hash" do
person = Person.new
person.errors.add(:name, "can not be blank")
- assert_instance_of ::Hash, person.errors.to_hash
+ assert_equal({ name: ["can not be blank"] }, person.errors.to_hash)
end
- test 'full_messages should return an array of error messages, with the attribute name included' do
+ test "full_messages creates a list of error messages with the attribute name included" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages
end
-
- test 'full_messages_for should contain all the messages for a given attribute' do
+
+ test "full_messages_for contains all the error messages for the given attribute" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages_for(:name)
end
- test 'full_messages_for should not contain messages for another attributes' do
+ test "full_messages_for does not contain error messages from other attributes" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:email, "can not be blank")
assert_equal ["name can not be blank"], person.errors.full_messages_for(:name)
end
- test "full_messages_for should return an empty array in case if errors hash doesn't contain a given attribute" do
+ test "full_messages_for returns an empty list in case there are no errors for the given attribute" do
person = Person.new
person.errors.add(:name, "can not be blank")
assert_equal [], person.errors.full_messages_for(:email)
end
- test 'full_message should return the given message if attribute equals :base' do
+ test "full_message returns the given message when attribute is :base" do
person = Person.new
assert_equal "press the button", person.errors.full_message(:base, "press the button")
end
- test 'full_message should return the given message with the attribute name included' do
+ test "full_message returns the given message with the attribute name included" do
person = Person.new
assert_equal "name can not be blank", person.errors.full_message(:name, "can not be blank")
assert_equal "name_test can not be blank", person.errors.full_message(:name_test, "can not be blank")
end
- test 'should return a JSON hash representation of the errors' do
+ test "as_json creates a json formatted representation of the errors hash" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:name, "can not be nil")
- person.errors.add(:email, "is invalid")
- hash = person.errors.as_json
- assert_equal ["can not be blank", "can not be nil"], hash[:name]
- assert_equal ["is invalid"], hash[:email]
+ person.validate!
+
+ assert_equal({ name: ["can not be nil"] }, person.errors.as_json)
end
- test 'should return a JSON hash representation of the errors with full messages' do
+ test "as_json with :full_messages option creates a json formatted representation of the errors containing complete messages" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:name, "can not be nil")
- person.errors.add(:email, "is invalid")
- hash = person.errors.as_json(:full_messages => true)
- assert_equal ["name can not be blank", "name can not be nil"], hash[:name]
- assert_equal ["email is invalid"], hash[:email]
+ person.validate!
+
+ assert_equal({ name: ["name can not be nil"] }, person.errors.as_json(full_messages: true))
end
- test "generate_message should work without i18n_scope" do
+ test "generate_message works without i18n_scope" do
person = Person.new
assert !Person.respond_to?(:i18n_scope)
assert_nothing_raised {
@@ -291,8 +271,8 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message with custom default message" do
person = Person.new
- person.errors.expects(:generate_message).with(:name, :empty, {:message => 'custom'})
- person.errors.add_on_empty :name, :message => 'custom'
+ person.errors.expects(:generate_message).with(:name, :empty, { message: 'custom' })
+ person.errors.add_on_empty :name, message: 'custom'
end
test "add_on_empty generates message with empty string value" do
@@ -317,7 +297,7 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_blank generates message with custom default message" do
person = Person.new
- person.errors.expects(:generate_message).with(:name, :blank, {:message => 'custom'})
- person.errors.add_on_blank :name, :message => 'custom'
+ person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' })
+ person.errors.add_on_blank :name, message: 'custom'
end
end
diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb
index 588d8e661e..24e4ca91c6 100644
--- a/activemodel/test/cases/model_test.rb
+++ b/activemodel/test/cases/model_test.rb
@@ -13,7 +13,7 @@ class ModelTest < ActiveModel::TestCase
end
def test_initialize_with_params
- object = BasicModel.new(:attr => "value")
+ object = BasicModel.new(attr: "value")
assert_equal object.attr, "value"
end
@@ -26,7 +26,7 @@ class ModelTest < ActiveModel::TestCase
end
def test_persisted_is_always_false
- object = BasicModel.new(:attr => "value")
+ object = BasicModel.new(attr: "value")
assert object.persisted? == false
end
end
diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb
index a0cd1402b1..0643fa775d 100644
--- a/activemodel/test/cases/railtie_test.rb
+++ b/activemodel/test/cases/railtie_test.rb
@@ -7,9 +7,12 @@ class RailtieTest < ActiveModel::TestCase
def setup
require 'active_model/railtie'
+ # Set a fake logger to avoid creating the log directory automatically
+ fake_logger = mock()
+
@app ||= Class.new(::Rails::Application) do
config.eager_load = false
- config.logger = Logger.new(STDOUT)
+ config.logger = fake_logger
end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 02cd3b8a93..0b900d934d 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -94,4 +94,13 @@ class SecurePasswordTest < ActiveModel::TestCase
@user.password_confirmation = ""
assert @user.valid?(:update), "user should be valid"
end
+
+ test "will not save if confirmation is blank but password is not" do
+ @user.password = "password"
+ @user.password_confirmation = ""
+ assert_not @user.valid?(:create)
+
+ @user.password_confirmation = "password"
+ assert @user.valid?(:create)
+ end
end
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index d2ba9fd95d..4ae41aa19c 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -49,32 +49,32 @@ class SerializationTest < ActiveModel::TestCase
def test_method_serializable_hash_should_work_with_only_option
expected = {"name"=>"David"}
- assert_equal expected, @user.serializable_hash(:only => [:name])
+ assert_equal expected, @user.serializable_hash(only: [:name])
end
def test_method_serializable_hash_should_work_with_except_option
expected = {"gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(:except => [:name])
+ assert_equal expected, @user.serializable_hash(except: [:name])
end
def test_method_serializable_hash_should_work_with_methods_option
expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(:methods => [:foo])
+ assert_equal expected, @user.serializable_hash(methods: [:foo])
end
def test_method_serializable_hash_should_work_with_only_and_methods
expected = {"foo"=>"i_am_foo"}
- assert_equal expected, @user.serializable_hash(:only => [], :methods => [:foo])
+ assert_equal expected, @user.serializable_hash(only: [], methods: [:foo])
end
def test_method_serializable_hash_should_work_with_except_and_methods
expected = {"gender"=>"male", "foo"=>"i_am_foo"}
- assert_equal expected, @user.serializable_hash(:except => [:name, :email], :methods => [:foo])
+ assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo])
end
def test_should_not_call_methods_that_dont_respond
expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(:methods => [:bar])
+ assert_equal expected, @user.serializable_hash(methods: [:bar])
end
def test_should_use_read_attribute_for_serialization
@@ -83,26 +83,26 @@ class SerializationTest < ActiveModel::TestCase
end
expected = { "name" => "Jon" }
- assert_equal expected, @user.serializable_hash(:only => :name)
+ assert_equal expected, @user.serializable_hash(only: :name)
end
def test_include_option_with_singular_association
expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
"address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
- assert_equal expected, @user.serializable_hash(:include => :address)
+ assert_equal expected, @user.serializable_hash(include: :address)
end
def test_include_option_with_plural_association
expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
"friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
{"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected, @user.serializable_hash(:include => :friends)
+ assert_equal expected, @user.serializable_hash(include: :friends)
end
def test_include_option_with_empty_association
@user.friends = []
expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[]}
- assert_equal expected, @user.serializable_hash(:include => :friends)
+ assert_equal expected, @user.serializable_hash(include: :friends)
end
class FriendList
@@ -120,7 +120,7 @@ class SerializationTest < ActiveModel::TestCase
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)
+ assert_equal expected, @user.serializable_hash(include: :friends)
end
def test_multiple_includes
@@ -128,13 +128,13 @@ class SerializationTest < ActiveModel::TestCase
"address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
"friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
{"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected, @user.serializable_hash(:include => [:address, :friends])
+ assert_equal expected, @user.serializable_hash(include: [:address, :friends])
end
def test_include_with_options
expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
"address"=>{"street"=>"123 Lane"}}
- assert_equal expected, @user.serializable_hash(:include => {:address => {:only => "street"}})
+ assert_equal expected, @user.serializable_hash(include: { address: { only: "street" } })
end
def test_nested_include
@@ -143,19 +143,19 @@ class SerializationTest < ActiveModel::TestCase
"friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male',
"friends"=> [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
{"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', "friends"=> []}]}
- assert_equal expected, @user.serializable_hash(:include => {:friends => {:include => :friends}})
+ assert_equal expected, @user.serializable_hash(include: { friends: { include: :friends } })
end
def test_only_include
expected = {"name"=>"David", "friends" => [{"name" => "Joe"}, {"name" => "Sue"}]}
- assert_equal expected, @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}})
+ assert_equal expected, @user.serializable_hash(only: :name, include: { friends: { only: :name } })
end
def test_except_include
expected = {"name"=>"David", "email"=>"david@example.com",
"friends"=> [{"name" => 'Joe', "email" => 'joe@example.com'},
{"name" => "Sue", "email" => 'sue@example.com'}]}
- assert_equal expected, @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}})
+ assert_equal expected, @user.serializable_hash(except: :gender, include: { friends: { except: :gender } })
end
def test_multiple_includes_with_options
@@ -163,6 +163,6 @@ class SerializationTest < ActiveModel::TestCase
"address"=>{"street"=>"123 Lane"},
"friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
{"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected, @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends])
+ assert_equal expected, @user.serializable_hash(include: [{ address: {only: "street" } }, :friends])
end
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 9134c4980c..f0347081ee 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -91,7 +91,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "should allow attribute filtering with only" do
- json = @contact.to_json(:only => [:name, :age])
+ json = @contact.to_json(only: [:name, :age])
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 99a9c1fe33..901f42f29b 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -35,7 +35,7 @@ end
class SerializableContact < Contact
def serializable_hash(options={})
- super(options.merge(:only => [:name, :age]))
+ super(options.merge(only: [:name, :age]))
end
end
@@ -50,14 +50,10 @@ class XmlSerializationTest < ActiveModel::TestCase
customer.name = "John"
@contact.preferences = customer
@contact.address = Address.new
- @contact.address.street = "123 Lane"
@contact.address.city = "Springfield"
- @contact.address.state = "CA"
- @contact.address.zip = 11111
@contact.address.apt_number = 35
@contact.friends = [Contact.new, Contact.new]
@related_contact = SerializableContact.new
- @related_contact.name = "related"
@contact.contact = @related_contact
end
@@ -74,33 +70,33 @@ class XmlSerializationTest < ActiveModel::TestCase
end
test "should serialize default root with namespace" do
- @xml = @contact.to_xml :namespace => "http://xml.rubyonrails.org/contact"
+ @xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact"
assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, @xml
assert_match %r{</contact>$}, @xml
end
test "should serialize custom root" do
- @xml = @contact.to_xml :root => 'xml_contact'
+ @xml = @contact.to_xml root: 'xml_contact'
assert_match %r{^<xml-contact>}, @xml
assert_match %r{</xml-contact>$}, @xml
end
test "should allow undasherized tags" do
- @xml = @contact.to_xml :root => 'xml_contact', :dasherize => false
+ @xml = @contact.to_xml root: 'xml_contact', dasherize: false
assert_match %r{^<xml_contact>}, @xml
assert_match %r{</xml_contact>$}, @xml
assert_match %r{<created_at}, @xml
end
test "should allow camelized tags" do
- @xml = @contact.to_xml :root => 'xml_contact', :camelize => true
+ @xml = @contact.to_xml root: 'xml_contact', camelize: true
assert_match %r{^<XmlContact>}, @xml
assert_match %r{</XmlContact>$}, @xml
assert_match %r{<CreatedAt}, @xml
end
test "should allow lower-camelized tags" do
- @xml = @contact.to_xml :root => 'xml_contact', :camelize => :lower
+ @xml = @contact.to_xml root: 'xml_contact', camelize: :lower
assert_match %r{^<xmlContact>}, @xml
assert_match %r{</xmlContact>$}, @xml
assert_match %r{<createdAt}, @xml
@@ -118,7 +114,7 @@ class XmlSerializationTest < ActiveModel::TestCase
end
test "should allow skipped types" do
- @xml = @contact.to_xml :skip_types => true
+ @xml = @contact.to_xml skip_types: true
assert_match %r{<age>25</age>}, @xml
end
@@ -134,7 +130,7 @@ class XmlSerializationTest < ActiveModel::TestCase
end
test "should serialize nil" do
- assert_match %r{<pseudonyms nil=\"true\"/>}, @contact.to_xml(:methods => :pseudonyms)
+ assert_match %r{<pseudonyms nil=\"true\"/>}, @contact.to_xml(methods: :pseudonyms)
end
test "should serialize integer" do
@@ -150,11 +146,11 @@ class XmlSerializationTest < ActiveModel::TestCase
end
test "should serialize array" do
- assert_match %r{<social type=\"array\">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(:methods => :social)
+ assert_match %r{<social type=\"array\">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(methods: :social)
end
test "should serialize hash" do
- assert_match %r{<network>\s*<git type=\"symbol\">github</git>\s*</network>}, @contact.to_xml(:methods => :network)
+ assert_match %r{<network>\s*<git type=\"symbol\">github</git>\s*</network>}, @contact.to_xml(methods: :network)
end
test "should serialize yaml" do
@@ -163,29 +159,29 @@ class XmlSerializationTest < ActiveModel::TestCase
test "should call proc on object" do
proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') }
- xml = @contact.to_xml(:procs => [ proc ])
+ xml = @contact.to_xml(procs: [ proc ])
assert_match %r{<nationality>unknown</nationality>}, xml
end
test 'should supply serializable to second proc argument' do
proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- xml = @contact.to_xml(:procs => [ proc ])
+ xml = @contact.to_xml(procs: [ proc ])
assert_match %r{<name-reverse>kcats noraa</name-reverse>}, xml
end
test "should serialize string correctly when type passed" do
- xml = @contact.to_xml :type => 'Contact'
+ xml = @contact.to_xml type: 'Contact'
assert_match %r{<contact type="Contact">}, xml
assert_match %r{<name>aaron stack</name>}, xml
end
test "include option with singular association" do
- xml = @contact.to_xml :include => :address, :indent => 0
- assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true))
+ xml = @contact.to_xml include: :address, indent: 0
+ assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true))
end
test "include option with plural association" do
- xml = @contact.to_xml :include => :friends, :indent => 0
+ xml = @contact.to_xml include: :friends, indent: 0
assert_match %r{<friends type="array">}, xml
assert_match %r{<friend type="Contact">}, xml
end
@@ -202,60 +198,60 @@ class XmlSerializationTest < ActiveModel::TestCase
test "include option with ary" do
@contact.friends = FriendList.new(@contact.friends)
- xml = @contact.to_xml :include => :friends, :indent => 0
+ 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))
+ xml = @contact.to_xml indent: 0, skip_instruct: true, include: [ :address, :friends ]
+ assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true))
assert_match %r{<friends type="array">}, xml
assert_match %r{<friend type="Contact">}, xml
end
test "include with options" do
- xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => { :address => { :only => :city } }
+ xml = @contact.to_xml indent: 0, skip_instruct: true, include: { address: { only: :city } }
assert xml.include?(%(><address><city>Springfield</city></address>))
end
test "propagates skip_types option to included associations" do
- xml = @contact.to_xml :include => :friends, :indent => 0, :skip_types => true
+ xml = @contact.to_xml include: :friends, indent: 0, skip_types: true
assert_match %r{<friends>}, xml
assert_match %r{<friend>}, xml
end
test "propagates skip-types option to included associations and attributes" do
- xml = @contact.to_xml :skip_types => true, :include => :address, :indent => 0
+ xml = @contact.to_xml skip_types: true, include: :address, indent: 0
assert_match %r{<address>}, xml
assert_match %r{<apt-number>}, xml
end
test "propagates camelize option to included associations and attributes" do
- xml = @contact.to_xml :camelize => true, :include => :address, :indent => 0
+ xml = @contact.to_xml camelize: true, include: :address, indent: 0
assert_match %r{<Address>}, xml
assert_match %r{<AptNumber type="integer">}, xml
end
test "propagates dasherize option to included associations and attributes" do
- xml = @contact.to_xml :dasherize => false, :include => :address, :indent => 0
+ xml = @contact.to_xml dasherize: false, include: :address, indent: 0
assert_match %r{<apt_number type="integer">}, xml
end
test "don't propagate skip_types if skip_types is defined at the included association level" do
- xml = @contact.to_xml :skip_types => true, :include => { :address => { :skip_types => false } }, :indent => 0
+ xml = @contact.to_xml skip_types: true, include: { address: { skip_types: false } }, indent: 0
assert_match %r{<address>}, xml
assert_match %r{<apt-number type="integer">}, xml
end
test "don't propagate camelize if camelize is defined at the included association level" do
- xml = @contact.to_xml :camelize => true, :include => { :address => { :camelize => false } }, :indent => 0
+ xml = @contact.to_xml camelize: true, include: { address: { camelize: false } }, indent: 0
assert_match %r{<address>}, xml
assert_match %r{<apt-number type="integer">}, xml
end
test "don't propagate dasherize if dasherize is defined at the included association level" do
- xml = @contact.to_xml :dasherize => false, :include => { :address => { :dasherize => true } }, :indent => 0
+ xml = @contact.to_xml dasherize: false, include: { address: { dasherize: true } }, indent: 0
assert_match %r{<address>}, xml
assert_match %r{<apt-number type="integer">}, xml
end
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index fd833cdd06..deb4e1ed0a 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -8,22 +8,22 @@ class ActiveModelI18nTests < ActiveModel::TestCase
end
def test_translated_model_attributes
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } }
+ I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute' } } }
assert_equal 'person name attribute', Person.human_attribute_name('name')
end
def test_translated_model_attributes_with_default
- I18n.backend.store_translations 'en', :attributes => { :name => 'name default attribute' }
+ I18n.backend.store_translations 'en', attributes: { name: 'name default attribute' }
assert_equal 'name default attribute', Person.human_attribute_name('name')
end
def test_translated_model_attributes_using_default_option
- assert_equal 'name default attribute', Person.human_attribute_name('name', :default => "name default attribute")
+ assert_equal 'name default attribute', Person.human_attribute_name('name', default: "name default attribute")
end
def test_translated_model_attributes_using_default_option_as_symbol
- I18n.backend.store_translations 'en', :default_name => 'name default attribute'
- assert_equal 'name default attribute', Person.human_attribute_name('name', :default => :default_name)
+ I18n.backend.store_translations 'en', default_name: 'name default attribute'
+ assert_equal 'name default attribute', Person.human_attribute_name('name', default: :default_name)
end
def test_translated_model_attributes_falling_back_to_default
@@ -31,71 +31,74 @@ class ActiveModelI18nTests < ActiveModel::TestCase
end
def test_translated_model_attributes_using_default_option_as_symbol_and_falling_back_to_default
- assert_equal 'Name', Person.human_attribute_name('name', :default => :default_name)
+ assert_equal 'Name', Person.human_attribute_name('name', default: :default_name)
end
def test_translated_model_attributes_with_symbols
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } }
+ I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } }
assert_equal 'person name attribute', Person.human_attribute_name(:name)
end
def test_translated_model_attributes_with_ancestor
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:child => {:name => 'child name attribute'} } }
+ I18n.backend.store_translations 'en', activemodel: { attributes: { child: { name: 'child name attribute'} } }
assert_equal 'child name attribute', Child.human_attribute_name('name')
end
def test_translated_model_attributes_with_ancestors_fallback
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } }
+ I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } }
assert_equal 'person name attribute', Child.human_attribute_name('name')
end
def test_translated_model_attributes_with_attribute_matching_namespaced_model_name
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:gender => 'person gender'}, :"person/gender" => {:attribute => 'person gender attribute'}}}
+ I18n.backend.store_translations 'en', activemodel: { attributes: {
+ person: { gender: 'person gender'},
+ :"person/gender" => { attribute: 'person gender attribute' }
+ } }
assert_equal 'person gender', Person.human_attribute_name('gender')
assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute')
end
def test_translated_deeply_nested_model_attributes
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/contacts/addresses" => {:street => 'Deeply Nested Address Street'}}}
+ I18n.backend.store_translations 'en', activemodel: { attributes: { :"person/contacts/addresses" => { street: 'Deeply Nested Address Street' } } }
assert_equal 'Deeply Nested Address Street', Person.human_attribute_name('contacts.addresses.street')
end
def test_translated_nested_model_attributes
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/addresses" => {:street => 'Person Address Street'}}}
+ I18n.backend.store_translations 'en', activemodel: { attributes: { :"person/addresses" => { street: 'Person Address Street' } } }
assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street')
end
def test_translated_nested_model_attributes_with_namespace_fallback
- I18n.backend.store_translations 'en', :activemodel => {:attributes => {:addresses => {:street => 'Cool Address Street'}}}
+ I18n.backend.store_translations 'en', activemodel: { attributes: { addresses: { street: 'Cool Address Street' } } }
assert_equal 'Cool Address Street', Person.human_attribute_name('addresses.street')
end
def test_translated_model_names
- I18n.backend.store_translations 'en', :activemodel => {:models => {:person => 'person model'} }
+ I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } }
assert_equal 'person model', Person.model_name.human
end
def test_translated_model_names_with_sti
- I18n.backend.store_translations 'en', :activemodel => {:models => {:child => 'child model'} }
+ I18n.backend.store_translations 'en', activemodel: { models: { child: 'child model' } }
assert_equal 'child model', Child.model_name.human
end
def test_translated_model_names_with_ancestors_fallback
- I18n.backend.store_translations 'en', :activemodel => {:models => {:person => 'person model'} }
+ I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } }
assert_equal 'person model', Child.model_name.human
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' }
+ options = { default: 'Cool gender' }
Person.human_attribute_name('gender', options)
- assert_equal({ :default => 'Cool gender' }, options)
+ assert_equal({ default: 'Cool gender' }, options)
end
end
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index de04e11258..dc413bef30 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -30,7 +30,7 @@ class AcceptanceValidationTest < ActiveModel::TestCase
end
def test_eula
- Topic.validates_acceptance_of(:eula, :message => "must be abided")
+ Topic.validates_acceptance_of(:eula, message: "must be abided")
t = Topic.new("title" => "We should be confirmed","eula" => "")
assert t.invalid?
@@ -41,7 +41,7 @@ class AcceptanceValidationTest < ActiveModel::TestCase
end
def test_terms_of_service_agreement_with_accept_value
- Topic.validates_acceptance_of(:terms_of_service, :accept => "I agree.")
+ Topic.validates_acceptance_of(:terms_of_service, accept: "I agree.")
t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "")
assert t.invalid?
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index 0015b3c196..6cd0f4ed4d 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -40,8 +40,29 @@ class DogWithMissingName < Dog
validates_presence_of :name
end
+class DogValidatorWithIfCondition < Dog
+ before_validation :set_before_validation_marker1, if: -> { true }
+ before_validation :set_before_validation_marker2, if: -> { false }
+
+ after_validation :set_after_validation_marker1, if: -> { true }
+ after_validation :set_after_validation_marker2, if: -> { false }
+
+ def set_before_validation_marker1; self.history << 'before_validation_marker1'; end
+ def set_before_validation_marker2; self.history << 'before_validation_marker2' ; end
+
+ def set_after_validation_marker1; self.history << 'after_validation_marker1'; end
+ def set_after_validation_marker2; self.history << 'after_validation_marker2' ; end
+end
+
+
class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
+ def test_if_condition_is_respected_for_before_validation
+ d = DogValidatorWithIfCondition.new
+ d.valid?
+ assert_equal ["before_validation_marker1", "after_validation_marker1"], d.history
+ end
+
def test_before_validation_and_after_validation_callbacks_should_be_called
d = DogWithMethodCallbacks.new
d.valid?
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index e06b04af19..41a4c33727 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -11,7 +11,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_method_true
# When the method returns true
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -20,7 +20,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_method_true
# When the method returns true
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -28,7 +28,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_method_false
# When the method returns false
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true_but_its_not )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true_but_its_not)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -36,7 +36,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_method_false
# When the method returns false
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true_but_its_not )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true_but_its_not)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -45,7 +45,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "a = 1; a == 1" )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -54,7 +54,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "a = 1; a == 1" )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -62,7 +62,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "false")
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -70,7 +70,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "false")
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -79,8 +79,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_block_true
# When the block returns true
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
- :if => Proc.new { |r| r.content.size > 4 } )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
+ if: Proc.new { |r| r.content.size > 4 })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -89,8 +89,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_block_true
# When the block returns true
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
- :unless => Proc.new { |r| r.content.size > 4 } )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
+ unless: Proc.new { |r| r.content.size > 4 })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -98,8 +98,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_block_false
# When the block returns false
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
- :if => Proc.new { |r| r.title != "uhohuhoh"} )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
+ if: Proc.new { |r| r.title != "uhohuhoh"})
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert t.errors[:title].empty?
@@ -107,8 +107,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_block_false
# When the block returns false
- Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}",
- :unless => Proc.new { |r| r.title != "uhohuhoh"} )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
+ unless: Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -120,7 +120,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
# ensure that it works correctly
def test_validation_with_if_as_string
Topic.validates_presence_of(:title)
- Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')")
+ Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')")
t = Topic.new
assert t.invalid?, "A topic without a title should not be valid"
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index 814eec3f59..f03de2c24a 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -13,7 +13,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase
def test_no_title_confirmation
Topic.validates_confirmation_of(:title)
- t = Topic.new(:author_name => "Plutarch")
+ t = Topic.new(author_name: "Plutarch")
assert t.valid?
t.title_confirmation = "Parallel Lives"
@@ -57,8 +57,8 @@ class ConfirmationValidationTest < ActiveModel::TestCase
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'}}}
+ errors: { messages: { confirmation: "doesn't match %{attribute}" } },
+ activemodel: { attributes: { topic: { title: 'Test Title'} } }
})
Topic.validates_confirmation_of(:title)
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index 7d5af27f3d..81455ba519 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -11,14 +11,14 @@ class ExclusionValidationTest < ActiveModel::TestCase
end
def test_validates_exclusion_of
- Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )
+ Topic.validates_exclusion_of(:title, in: %w( abe monkey ))
assert Topic.new("title" => "something", "content" => "abc").valid?
assert Topic.new("title" => "monkey", "content" => "abc").invalid?
end
def test_validates_exclusion_of_with_formatted_message
- Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option %{value} is restricted" )
+ Topic.validates_exclusion_of(:title, in: %w( abe monkey ), message: "option %{value} is restricted")
assert Topic.new("title" => "something", "content" => "abc")
@@ -29,7 +29,7 @@ class ExclusionValidationTest < ActiveModel::TestCase
end
def test_validates_exclusion_of_with_within_option
- Topic.validates_exclusion_of( :title, :within => %w( abe monkey ) )
+ Topic.validates_exclusion_of(:title, within: %w( abe monkey ))
assert Topic.new("title" => "something", "content" => "abc")
@@ -39,7 +39,7 @@ class ExclusionValidationTest < ActiveModel::TestCase
end
def test_validates_exclusion_of_for_ruby_class
- Person.validates_exclusion_of :karma, :in => %w( abe monkey )
+ Person.validates_exclusion_of :karma, in: %w( abe monkey )
p = Person.new
p.karma = "abe"
@@ -54,7 +54,7 @@ class ExclusionValidationTest < ActiveModel::TestCase
end
def test_validates_exclusion_of_with_lambda
- Topic.validates_exclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
+ Topic.validates_exclusion_of :title, in: lambda { |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
t = Topic.new
t.title = "elephant"
@@ -66,7 +66,7 @@ class ExclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_with_symbol
- Person.validates_exclusion_of :karma, :in => :reserved_karmas
+ Person.validates_exclusion_of :karma, in: :reserved_karmas
p = Person.new
p.karma = "abe"
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 308a3c6cef..26e8dbf19c 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -11,7 +11,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format
- Topic.validates_format_of(:title, :content, :with => /\AValidation\smacros \w+!\z/, :message => "is bad data")
+ Topic.validates_format_of(:title, :content, with: /\AValidation\smacros \w+!\z/, message: "is bad data")
t = Topic.new("title" => "i'm incorrect", "content" => "Validation macros rule!")
assert t.invalid?, "Shouldn't be valid"
@@ -27,7 +27,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_with_allow_blank
- Topic.validates_format_of(:title, :with => /\AValidation\smacros \w+!\z/, :allow_blank => true)
+ Topic.validates_format_of(:title, with: /\AValidation\smacros \w+!\z/, allow_blank: true)
assert Topic.new("title" => "Shouldn't be valid").invalid?
assert Topic.new("title" => "").valid?
assert Topic.new("title" => nil).valid?
@@ -36,7 +36,7 @@ class PresenceValidationTest < ActiveModel::TestCase
# testing ticket #3142
def test_validate_format_numeric
- Topic.validates_format_of(:title, :content, :with => /\A[1-9][0-9]*\z/, :message => "is bad data")
+ Topic.validates_format_of(:title, :content, with: /\A[1-9][0-9]*\z/, message: "is bad data")
t = Topic.new("title" => "72x", "content" => "6789")
assert t.invalid?, "Shouldn't be valid"
@@ -63,24 +63,24 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_with_formatted_message
- Topic.validates_format_of(:title, :with => /\AValid Title\z/, :message => "can't be %{value}")
- t = Topic.new(:title => 'Invalid title')
+ Topic.validates_format_of(:title, with: /\AValid Title\z/, message: "can't be %{value}")
+ t = Topic.new(title: 'Invalid title')
assert t.invalid?
assert_equal ["can't be Invalid title"], t.errors[:title]
end
def test_validate_format_of_with_multiline_regexp_should_raise_error
- assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => /^Valid Title$/) }
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /^Valid Title$/) }
end
def test_validate_format_of_with_multiline_regexp_and_option
assert_nothing_raised(ArgumentError) do
- Topic.validates_format_of(:title, :with => /^Valid Title$/, :multiline => true)
+ Topic.validates_format_of(:title, with: /^Valid Title$/, multiline: true)
end
end
def test_validate_format_with_not_option
- Topic.validates_format_of(:title, :without => /foo/, :message => "should not contain foo")
+ Topic.validates_format_of(:title, without: /foo/, message: "should not contain foo")
t = Topic.new
t.title = "foobar"
@@ -97,19 +97,19 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validates_format_of_with_both_regexps_should_raise_error
- assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => /this/, :without => /that/) }
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /this/, without: /that/) }
end
def test_validates_format_of_when_with_isnt_a_regexp_should_raise_error
- assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => "clearly not a regexp") }
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: "clearly not a regexp") }
end
def test_validates_format_of_when_not_isnt_a_regexp_should_raise_error
- assert_raise(ArgumentError) { Topic.validates_format_of(:title, :without => "clearly not a regexp") }
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, without: "clearly not a regexp") }
end
def test_validates_format_of_with_lambda
- Topic.validates_format_of :content, :with => lambda{ |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ }
+ Topic.validates_format_of :content, with: lambda { |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ }
t = Topic.new
t.title = "digit"
@@ -121,7 +121,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validates_format_of_without_lambda
- Topic.validates_format_of :content, :without => lambda{ |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ }
+ Topic.validates_format_of :content, without: lambda { |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ }
t = Topic.new
t.title = "characters"
@@ -133,7 +133,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validates_format_of_for_ruby_class
- Person.validates_format_of :karma, :with => /\A\d+\Z/
+ Person.validates_format_of :karma, with: /\A\d+\Z/
p = Person.new
p.karma = "Pixies"
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 302cbe9761..40a5aee997 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -10,29 +10,29 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
# validates_inclusion_of: generate_message(attr_name, :inclusion, message: custom_message, value: value)
def test_generate_message_inclusion_with_default_message
- assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :value => 'title')
+ assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, value: 'title')
end
def test_generate_message_inclusion_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :message => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, message: 'custom message %{value}', value: 'title')
end
# validates_exclusion_of: generate_message(attr_name, :exclusion, message: custom_message, value: value)
def test_generate_message_exclusion_with_default_message
- assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :value => 'title')
+ assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, value: 'title')
end
def test_generate_message_exclusion_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :message => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, message: 'custom message %{value}', value: 'title')
end
# validates_format_of: generate_message(attr_name, :invalid, message: custom_message, value: value)
def test_generate_message_invalid_with_default_message
- assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :value => 'title')
+ assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, value: 'title')
end
def test_generate_message_invalid_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :message => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, message: 'custom message %{value}', value: 'title')
end
# validates_confirmation_of: generate_message(attr_name, :confirmation, message: custom_message)
@@ -41,7 +41,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
end
def test_generate_message_confirmation_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :message => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, message: 'custom message')
end
# validates_acceptance_of: generate_message(attr_name, :accepted, message: custom_message)
@@ -50,7 +50,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
end
def test_generate_message_accepted_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :message => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, message: 'custom message')
end
# add_on_empty: generate_message(attr, :empty, message: custom_message)
@@ -59,7 +59,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
end
def test_generate_message_empty_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :message => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :empty, message: 'custom message')
end
# add_on_blank: generate_message(attr, :blank, message: custom_message)
@@ -68,71 +68,71 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
end
def test_generate_message_blank_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :message => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :blank, message: 'custom message')
end
# validates_length_of: generate_message(attr, :too_long, message: custom_message, count: option_value.end)
def test_generate_message_too_long_with_default_message
- assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :count => 10)
+ assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, count: 10)
end
def test_generate_message_too_long_with_custom_message
- assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :message => 'custom message %{count}', :count => 10)
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, message: 'custom message %{count}', count: 10)
end
# validates_length_of: generate_message(attr, :too_short, default: custom_message, count: option_value.begin)
def test_generate_message_too_short_with_default_message
- assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :count => 10)
+ assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, count: 10)
end
def test_generate_message_too_short_with_custom_message
- assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :message => 'custom message %{count}', :count => 10)
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, message: 'custom message %{count}', count: 10)
end
# validates_length_of: generate_message(attr, :wrong_length, message: custom_message, count: option_value)
def test_generate_message_wrong_length_with_default_message
- assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :count => 10)
+ assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, count: 10)
end
def test_generate_message_wrong_length_with_custom_message
- assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :message => 'custom message %{count}', :count => 10)
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, message: 'custom message %{count}', count: 10)
end
# validates_numericality_of: generate_message(attr_name, :not_a_number, value: raw_value, message: custom_message)
def test_generate_message_not_a_number_with_default_message
- assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :value => 'title')
+ assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, value: 'title')
end
def test_generate_message_not_a_number_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :message => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, message: 'custom message %{value}', value: 'title')
end
# validates_numericality_of: generate_message(attr_name, option, value: raw_value, default: custom_message)
def test_generate_message_greater_than_with_default_message
- assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :value => 'title', :count => 10)
+ assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, value: 'title', count: 10)
end
def test_generate_message_greater_than_or_equal_to_with_default_message
- assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :value => 'title', :count => 10)
+ assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, value: 'title', count: 10)
end
def test_generate_message_equal_to_with_default_message
- assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :value => 'title', :count => 10)
+ assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, value: 'title', count: 10)
end
def test_generate_message_less_than_with_default_message
- assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :value => 'title', :count => 10)
+ assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, value: 'title', count: 10)
end
def test_generate_message_less_than_or_equal_to_with_default_message
- assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :value => 'title', :count => 10)
+ assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, value: 'title', count: 10)
end
def test_generate_message_odd_with_default_message
- assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :value => 'title', :count => 10)
+ assert_equal "must be odd", @person.errors.generate_message(:title, :odd, value: 'title', count: 10)
end
def test_generate_message_even_with_default_message
- assert_equal "must be even", @person.errors.generate_message(:title, :even, :value => 'title', :count => 10)
+ assert_equal "must be even", @person.errors.generate_message(:title, :even, value: 'title', count: 10)
end
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 4c01b47608..e29771d6b7 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -12,7 +12,7 @@ class I18nValidationTest < ActiveModel::TestCase
@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 => {:custom => nil}})
+ I18n.backend.store_translations('en', errors: { messages: { custom: nil } })
end
def teardown
@@ -22,21 +22,21 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_full_message_encoding
- I18n.backend.store_translations('en', :errors => {
- :messages => { :too_short => '猫舌' }})
- Person.validates_length_of :title, :within => 3..5
+ I18n.backend.store_translations('en', errors: {
+ messages: { too_short: '猫舌' } })
+ Person.validates_length_of :title, within: 3..5
@person.valid?
assert_equal ['Title 猫舌'], @person.errors.full_messages
end
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@person.errors.add(:name, 'not found')
- Person.expects(:human_attribute_name).with(:name, :default => 'Name').returns("Person's name")
+ Person.expects(:human_attribute_name).with(:name, default: 'Name').returns("Person's name")
assert_equal ["Person's name not found"], @person.errors.full_messages
end
def test_errors_full_messages_uses_format
- I18n.backend.store_translations('en', :errors => {:format => "Field %{attribute} %{message}"})
+ I18n.backend.store_translations('en', errors: { format: "Field %{attribute} %{message}" })
@person.errors.add('name', 'empty')
assert_equal ["Field Name empty"], @person.errors.full_messages
end
@@ -49,10 +49,10 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES = [
# [ case, validation_options, generate_message_options]
[ "given no options", {}, {}],
- [ "given custom message", {:message => "custom"}, {:message => "custom"}],
- [ "given if condition", {:if => lambda { true }}, {}],
- [ "given unless condition", {:unless => lambda { false }}, {}],
- [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }]
+ [ "given custom message", { message: "custom" }, { message: "custom" }],
+ [ "given if condition", { if: lambda { true }}, {}],
+ [ "given unless condition", { unless: lambda { false }}, {}],
+ [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }]
]
# validates_confirmation_of w/ mocha
@@ -61,7 +61,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, :confirmation, generate_message_options.merge(:attribute => 'Title'))
+ @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title'))
@person.valid?
end
end
@@ -70,7 +70,7 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_acceptance_of on generated message #{name}" do
- Person.validates_acceptance_of :title, validation_options.merge(:allow_nil => false)
+ Person.validates_acceptance_of :title, validation_options.merge(allow_nil: false)
@person.errors.expects(:generate_message).with(:title, :accepted, generate_message_options)
@person.valid?
end
@@ -90,8 +90,8 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :withing on generated message when too short #{name}" do
- Person.validates_length_of :title, validation_options.merge(:within => 3..5)
- @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(:count => 3))
+ Person.validates_length_of :title, validation_options.merge(within: 3..5)
+ @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(count: 3))
@person.valid?
end
end
@@ -100,9 +100,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :too_long generated message #{name}" do
- Person.validates_length_of :title, validation_options.merge(:within => 3..5)
+ Person.validates_length_of :title, validation_options.merge(within: 3..5)
@person.title = 'this title is too long'
- @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(:count => 5))
+ @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(count: 5))
@person.valid?
end
end
@@ -111,8 +111,8 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :is on generated message #{name}" do
- Person.validates_length_of :title, validation_options.merge(:is => 5)
- @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(:count => 5))
+ Person.validates_length_of :title, validation_options.merge(is: 5)
+ @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(count: 5))
@person.valid?
end
end
@@ -121,9 +121,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_format_of on generated message #{name}" do
- Person.validates_format_of :title, validation_options.merge(:with => /\A[1-9][0-9]*\z/)
+ Person.validates_format_of :title, validation_options.merge(with: /\A[1-9][0-9]*\z/)
@person.title = '72x'
- @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(:value => '72x'))
+ @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(value: '72x'))
@person.valid?
end
end
@@ -132,9 +132,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_inclusion_of on generated message #{name}" do
- Person.validates_inclusion_of :title, validation_options.merge(:in => %w(a b c))
+ Person.validates_inclusion_of :title, validation_options.merge(in: %w(a b c))
@person.title = 'z'
- @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(:value => 'z'))
+ @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z'))
@person.valid?
end
end
@@ -143,9 +143,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_inclusion_of using :within on generated message #{name}" do
- Person.validates_inclusion_of :title, validation_options.merge(:within => %w(a b c))
+ Person.validates_inclusion_of :title, validation_options.merge(within: %w(a b c))
@person.title = 'z'
- @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(:value => 'z'))
+ @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z'))
@person.valid?
end
end
@@ -154,9 +154,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_exclusion_of generated message #{name}" do
- Person.validates_exclusion_of :title, validation_options.merge(:in => %w(a b c))
+ Person.validates_exclusion_of :title, validation_options.merge(in: %w(a b c))
@person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(:value => 'a'))
+ @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a'))
@person.valid?
end
end
@@ -165,9 +165,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_exclusion_of using :within generated message #{name}" do
- Person.validates_exclusion_of :title, validation_options.merge(:within => %w(a b c))
+ Person.validates_exclusion_of :title, validation_options.merge(within: %w(a b c))
@person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(:value => 'a'))
+ @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a'))
@person.valid?
end
end
@@ -178,7 +178,7 @@ class I18nValidationTest < ActiveModel::TestCase
test "validates_numericality_of generated message #{name}" do
Person.validates_numericality_of :title, validation_options
@person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(:value => 'a'))
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(value: 'a'))
@person.valid?
end
end
@@ -187,9 +187,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :only_integer on generated message #{name}" do
- Person.validates_numericality_of :title, validation_options.merge(:only_integer => true)
+ Person.validates_numericality_of :title, validation_options.merge(only_integer: true)
@person.title = '0.0'
- @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(:value => '0.0'))
+ @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(value: '0.0'))
@person.valid?
end
end
@@ -198,9 +198,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :odd on generated message #{name}" do
- Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :odd => true)
+ Person.validates_numericality_of :title, validation_options.merge(only_integer: true, odd: true)
@person.title = 0
- @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(:value => 0))
+ @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(value: 0))
@person.valid?
end
end
@@ -209,9 +209,9 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :less_than on generated message #{name}" do
- Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :less_than => 0)
+ Person.validates_numericality_of :title, validation_options.merge(only_integer: true, less_than: 0)
@person.title = 1
- @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(:value => 1, :count => 0))
+ @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(value: 1, count: 0))
@person.valid?
end
end
@@ -226,8 +226,8 @@ class I18nValidationTest < ActiveModel::TestCase
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 => {attribute => {error_type => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global 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?
@@ -236,17 +236,17 @@ class I18nValidationTest < ActiveModel::TestCase
# 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 => {attribute => {error_type => 'custom message with %{extra}'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
+ 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"})
+ yield(@person, { extra: "extra information" })
@person.valid?
assert_equal ['custom message with extra information'], @person.errors[attribute]
end
# test "validates_confirmation_of finds global default key translation when blank"
test "#{validation} finds global default key translation when #{error_type}" do
- I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
+ I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} }
yield(@person, {})
@person.valid?
@@ -264,7 +264,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_acceptance_of w/o mocha
set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge|
- Person.validates_acceptance_of :title, options_to_merge.merge(:allow_nil => false)
+ Person.validates_acceptance_of :title, options_to_merge.merge(allow_nil: false)
end
# validates_presence_of w/o mocha
@@ -276,36 +276,36 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_length_of :within w/o mocha
set_expectations_for_validation "validates_length_of", :too_short do |person, options_to_merge|
- Person.validates_length_of :title, options_to_merge.merge(:within => 3..5)
+ Person.validates_length_of :title, options_to_merge.merge(within: 3..5)
end
set_expectations_for_validation "validates_length_of", :too_long do |person, options_to_merge|
- Person.validates_length_of :title, options_to_merge.merge(:within => 3..5)
+ Person.validates_length_of :title, options_to_merge.merge(within: 3..5)
person.title = "too long"
end
# validates_length_of :is w/o mocha
set_expectations_for_validation "validates_length_of", :wrong_length do |person, options_to_merge|
- Person.validates_length_of :title, options_to_merge.merge(:is => 5)
+ Person.validates_length_of :title, options_to_merge.merge(is: 5)
end
# validates_format_of w/o mocha
set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge|
- Person.validates_format_of :title, options_to_merge.merge(:with => /\A[1-9][0-9]*\z/)
+ Person.validates_format_of :title, options_to_merge.merge(with: /\A[1-9][0-9]*\z/)
end
# validates_inclusion_of w/o mocha
set_expectations_for_validation "validates_inclusion_of", :inclusion do |person, options_to_merge|
- Person.validates_inclusion_of :title, options_to_merge.merge(:in => %w(a b c))
+ Person.validates_inclusion_of :title, options_to_merge.merge(in: %w(a b c))
end
# validates_exclusion_of w/o mocha
set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge|
- Person.validates_exclusion_of :title, options_to_merge.merge(:in => %w(a b c))
+ Person.validates_exclusion_of :title, options_to_merge.merge(in: %w(a b c))
person.title = 'a'
end
@@ -319,55 +319,54 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of with :only_integer w/o mocha
set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge|
- Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true)
+ Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true)
person.title = '1.0'
end
# validates_numericality_of :odd w/o mocha
set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge|
- Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :odd => true)
+ Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, odd: true)
person.title = 0
end
# validates_numericality_of :less_than w/o mocha
set_expectations_for_validation "validates_numericality_of", :less_than do |person, options_to_merge|
- Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :less_than => 0)
+ Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, less_than: 0)
person.title = 1
end
# test with validates_with
def test_validations_with_message_symbol_must_translate
- I18n.backend.store_translations 'en', :errors => {:messages => {:custom_error => "I am a custom error"}}
- Person.validates_presence_of :title, :message => :custom_error
+ I18n.backend.store_translations 'en', errors: { messages: { custom_error: "I am a custom error" } }
+ Person.validates_presence_of :title, message: :custom_error
@person.title = nil
@person.valid?
assert_equal ["I am a custom error"], @person.errors[:title]
end
def test_validates_with_message_symbol_must_translate_per_attribute
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
- Person.validates_presence_of :title, :message => :custom_error
+ I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { title: { custom_error: "I am a custom error" } } } } } }
+ Person.validates_presence_of :title, message: :custom_error
@person.title = nil
@person.valid?
assert_equal ["I am a custom error"], @person.errors[:title]
end
def test_validates_with_message_symbol_must_translate_per_model
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:custom_error => "I am a custom error"}}}}
- Person.validates_presence_of :title, :message => :custom_error
+ I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { custom_error: "I am a custom error" } } } }
+ Person.validates_presence_of :title, message: :custom_error
@person.title = nil
@person.valid?
assert_equal ["I am a custom error"], @person.errors[:title]
end
def test_validates_with_message_string
- Person.validates_presence_of :title, :message => "I am a custom error"
+ Person.validates_presence_of :title, message: "I am a custom error"
@person.title = nil
@person.valid?
assert_equal ["I am a custom error"], @person.errors[:title]
end
-
end
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 117e9109fc..ceec9dc256 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -11,7 +11,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_range
- Topic.validates_inclusion_of( :title, :in => 'aaa'..'bbb' )
+ Topic.validates_inclusion_of(:title, in: 'aaa'..'bbb')
assert Topic.new("title" => "bbc", "content" => "abc").invalid?
assert Topic.new("title" => "aa", "content" => "abc").invalid?
assert Topic.new("title" => "aaa", "content" => "abc").valid?
@@ -20,7 +20,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
+ Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ))
assert Topic.new("title" => "a!", "content" => "abc").invalid?
assert Topic.new("title" => "a b", "content" => "abc").invalid?
@@ -33,16 +33,16 @@ class InclusionValidationTest < ActiveModel::TestCase
assert t.errors[:title].any?
assert_equal ["is not included in the list"], t.errors[:title]
- assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
- assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: nil) }
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: 0) }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: "hi!") }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: {}) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: []) }
end
def test_validates_inclusion_of_with_allow_nil
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil => true )
+ Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), allow_nil: true)
assert Topic.new("title" => "a!", "content" => "abc").invalid?
assert Topic.new("title" => "", "content" => "abc").invalid?
@@ -50,7 +50,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_with_formatted_message
- Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option %{value} is not in the list" )
+ Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), message: "option %{value} is not in the list")
assert Topic.new("title" => "a", "content" => "abc").valid?
@@ -61,7 +61,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_with_within_option
- Topic.validates_inclusion_of( :title, :within => %w( a b c d e f g ) )
+ Topic.validates_inclusion_of(:title, within: %w( a b c d e f g ))
assert Topic.new("title" => "a", "content" => "abc").valid?
@@ -71,7 +71,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_for_ruby_class
- Person.validates_inclusion_of :karma, :in => %w( abe monkey )
+ Person.validates_inclusion_of :karma, in: %w( abe monkey )
p = Person.new
p.karma = "Lifo"
@@ -86,7 +86,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_with_lambda
- Topic.validates_inclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
+ Topic.validates_inclusion_of :title, in: lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
t = Topic.new
t.title = "wasabi"
@@ -98,7 +98,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_with_symbol
- Person.validates_inclusion_of :karma, :in => :available_karmas
+ Person.validates_inclusion_of :karma, in: :available_karmas
p = Person.new
p.karma = "Lifo"
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 6742a4bab0..84332ed014 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -30,84 +30,84 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_of_with_nil_allowed
- Topic.validates_numericality_of :approved, :allow_nil => true
+ Topic.validates_numericality_of :approved, allow_nil: true
invalid!(JUNK + BLANK)
valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
end
def test_validates_numericality_of_with_integer_only
- Topic.validates_numericality_of :approved, :only_integer => true
+ Topic.validates_numericality_of :approved, only_integer: true
invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
valid!(INTEGERS)
end
def test_validates_numericality_of_with_integer_only_and_nil_allowed
- Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
+ Topic.validates_numericality_of :approved, only_integer: true, allow_nil: true
invalid!(JUNK + BLANK + FLOATS + BIGDECIMAL + INFINITY)
valid!(NIL + INTEGERS)
end
def test_validates_numericality_with_greater_than
- Topic.validates_numericality_of :approved, :greater_than => 10
+ Topic.validates_numericality_of :approved, greater_than: 10
invalid!([-10, 10], 'must be greater than 10')
valid!([11])
end
def test_validates_numericality_with_greater_than_or_equal
- Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10
+ Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10
invalid!([-9, 9], 'must be greater than or equal to 10')
valid!([10])
end
def test_validates_numericality_with_equal_to
- Topic.validates_numericality_of :approved, :equal_to => 10
+ Topic.validates_numericality_of :approved, equal_to: 10
invalid!([-10, 11] + INFINITY, 'must be equal to 10')
valid!([10])
end
def test_validates_numericality_with_less_than
- Topic.validates_numericality_of :approved, :less_than => 10
+ Topic.validates_numericality_of :approved, less_than: 10
invalid!([10], 'must be less than 10')
valid!([-9, 9])
end
def test_validates_numericality_with_less_than_or_equal_to
- Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10
+ Topic.validates_numericality_of :approved, less_than_or_equal_to: 10
invalid!([11], 'must be less than or equal to 10')
valid!([-10, 10])
end
def test_validates_numericality_with_odd
- Topic.validates_numericality_of :approved, :odd => true
+ Topic.validates_numericality_of :approved, odd: true
invalid!([-2, 2], 'must be odd')
valid!([-1, 1])
end
def test_validates_numericality_with_even
- Topic.validates_numericality_of :approved, :even => true
+ Topic.validates_numericality_of :approved, even: true
invalid!([-1, 1], 'must be even')
valid!([-2, 2])
end
def test_validates_numericality_with_greater_than_less_than_and_even
- Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true
+ Topic.validates_numericality_of :approved, greater_than: 1, less_than: 4, even: true
invalid!([1, 3, 4])
valid!([2])
end
def test_validates_numericality_with_other_than
- Topic.validates_numericality_of :approved, :other_than => 0
+ Topic.validates_numericality_of :approved, other_than: 0
invalid!([0, 0.0])
valid!([-1, 42])
@@ -115,7 +115,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_with_proc
Topic.send(:define_method, :min_approved, lambda { 5 })
- Topic.validates_numericality_of :approved, :greater_than_or_equal_to => Proc.new {|topic| topic.min_approved }
+ Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new {|topic| topic.min_approved }
invalid!([3, 4])
valid!([5, 6])
@@ -124,7 +124,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_with_symbol
Topic.send(:define_method, :max_approved, lambda { 5 })
- Topic.validates_numericality_of :approved, :less_than_or_equal_to => :max_approved
+ Topic.validates_numericality_of :approved, less_than_or_equal_to: :max_approved
invalid!([6])
valid!([4, 5])
@@ -132,13 +132,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_numeric_message
- Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %{count}"
+ Topic.validates_numericality_of :approved, less_than: 4, message: "smaller than %{count}"
topic = Topic.new("title" => "numeric test", "approved" => 10)
assert !topic.valid?
assert_equal ["smaller than 4"], topic.errors[:approved]
- Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %{count}"
+ Topic.validates_numericality_of :approved, greater_than: 4, message: "greater than %{count}"
topic = Topic.new("title" => "numeric test", "approved" => 1)
assert !topic.valid?
@@ -146,7 +146,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_of_for_ruby_class
- Person.validates_numericality_of :karma, :allow_nil => false
+ Person.validates_numericality_of :karma, allow_nil: false
p = Person.new
p.karma = "Pix"
@@ -161,11 +161,11 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_invalid_args
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :less_than_or_equal_to => "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :greater_than => "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :less_than => "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :equal_to => "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than: "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than: "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, equal_to: "foo" }
end
private
@@ -185,7 +185,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def with_each_topic_approved_value(values)
- topic = Topic.new(:title => "numeric test", :content => "whatever")
+ topic = Topic.new(title: "numeric test", content: "whatever")
values.each do |value|
topic.approved = value
yield topic, value
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 144532d6f4..c1914b32bc 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -17,20 +17,20 @@ class ValidatesTest < ActiveModel::TestCase
end
def test_validates_with_messages_empty
- Person.validates :title, :presence => {:message => "" }
+ Person.validates :title, presence: { message: "" }
person = Person.new
assert !person.valid?, 'person should not be valid.'
end
def test_validates_with_built_in_validation
- Person.validates :title, :numericality => true
+ Person.validates :title, numericality: true
person = Person.new
person.valid?
assert_equal ['is not a number'], person.errors[:title]
end
def test_validates_with_attribute_specified_as_string
- Person.validates "title", :numericality => true
+ Person.validates "title", numericality: true
person = Person.new
person.valid?
assert_equal ['is not a number'], person.errors[:title]
@@ -41,14 +41,14 @@ class ValidatesTest < ActiveModel::TestCase
end
def test_validates_with_built_in_validation_and_options
- Person.validates :salary, :numericality => { :message => 'my custom message' }
+ Person.validates :salary, numericality: { message: 'my custom message' }
person = Person.new
person.valid?
assert_equal ['my custom message'], person.errors[:salary]
end
def test_validates_with_validator_class
- Person.validates :karma, :email => true
+ Person.validates :karma, email: true
person = Person.new
person.valid?
assert_equal ['is not an email'], person.errors[:karma]
@@ -62,33 +62,33 @@ class ValidatesTest < ActiveModel::TestCase
end
def test_validates_with_if_as_local_conditions
- Person.validates :karma, :presence => true, :email => { :unless => :condition_is_true }
+ Person.validates :karma, presence: true, email: { unless: :condition_is_true }
person = Person.new
person.valid?
assert_equal ["can't be blank"], person.errors[:karma]
end
def test_validates_with_if_as_shared_conditions
- Person.validates :karma, :presence => true, :email => true, :if => :condition_is_true
+ Person.validates :karma, presence: true, email: true, if: :condition_is_true
person = Person.new
person.valid?
assert_equal ["can't be blank", "is not an email"], person.errors[:karma].sort
end
def test_validates_with_unless_shared_conditions
- Person.validates :karma, :presence => true, :email => true, :unless => :condition_is_true
+ Person.validates :karma, presence: true, email: true, unless: :condition_is_true
person = Person.new
assert person.valid?
end
def test_validates_with_allow_nil_shared_conditions
- Person.validates :karma, :length => { :minimum => 20 }, :email => true, :allow_nil => true
+ Person.validates :karma, length: { minimum: 20 }, email: true, allow_nil: true
person = Person.new
assert person.valid?
end
def test_validates_with_regexp
- Person.validates :karma, :format => /positive|negative/
+ Person.validates :karma, format: /positive|negative/
person = Person.new
assert person.invalid?
assert_equal ['is invalid'], person.errors[:karma]
@@ -97,7 +97,7 @@ class ValidatesTest < ActiveModel::TestCase
end
def test_validates_with_array
- Person.validates :gender, :inclusion => %w(m f)
+ Person.validates :gender, inclusion: %w(m f)
person = Person.new
assert person.invalid?
assert_equal ['is not included in the list'], person.errors[:gender]
@@ -106,7 +106,7 @@ class ValidatesTest < ActiveModel::TestCase
end
def test_validates_with_range
- Person.validates :karma, :length => 6..20
+ Person.validates :karma, length: 6..20
person = Person.new
assert person.invalid?
assert_equal ['is too short (minimum is 6 characters)'], person.errors[:karma]
@@ -115,25 +115,25 @@ class ValidatesTest < ActiveModel::TestCase
end
def test_validates_with_validator_class_and_options
- Person.validates :karma, :email => { :message => 'my custom message' }
+ Person.validates :karma, email: { message: 'my custom message' }
person = Person.new
person.valid?
assert_equal ['my custom message'], person.errors[:karma]
end
def test_validates_with_unknown_validator
- assert_raise(ArgumentError) { Person.validates :karma, :unknown => true }
+ assert_raise(ArgumentError) { Person.validates :karma, unknown: true }
end
def test_validates_with_included_validator
- PersonWithValidator.validates :title, :presence => true
+ PersonWithValidator.validates :title, presence: true
person = PersonWithValidator.new
person.valid?
assert_equal ['Local validator'], person.errors[:title]
end
def test_validates_with_included_validator_and_options
- PersonWithValidator.validates :title, :presence => { :custom => ' please' }
+ PersonWithValidator.validates :title, presence: { custom: ' please' }
person = PersonWithValidator.new
person.valid?
assert_equal ['Local validator please'], person.errors[:title]
@@ -141,7 +141,7 @@ class ValidatesTest < ActiveModel::TestCase
def test_validates_with_included_validator_and_wildcard_shortcut
# Shortcut for PersonWithValidator.validates :title, like: { with: "Mr." }
- PersonWithValidator.validates :title, :like => "Mr."
+ PersonWithValidator.validates :title, like: "Mr."
person = PersonWithValidator.new
person.title = "Ms. Pacman"
person.valid?
@@ -149,7 +149,7 @@ class ValidatesTest < ActiveModel::TestCase
end
def test_defining_extra_default_keys_for_validates
- Topic.validates :title, :confirmation => true, :message => 'Y U NO CONFIRM'
+ Topic.validates :title, confirmation: true, message: 'Y U NO CONFIRM'
topic = Topic.new
topic.title = "What's happening"
topic.title_confirmation = "Not this"
diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb
index 15a49e38dd..5f99b320a6 100644
--- a/activemodel/test/cases/validations/validations_context_test.rb
+++ b/activemodel/test/cases/validations/validations_context_test.rb
@@ -18,22 +18,22 @@ class ValidationsContextTest < ActiveModel::TestCase
end
end
- test "with a class that adds errors on update and validating a new model with no arguments" do
- Topic.validates_with(ValidatorThatAddsErrors, :on => :create)
+ test "with a class that adds errors on create and validating a new model with no arguments" do
+ Topic.validates_with(ValidatorThatAddsErrors, on: :create)
topic = Topic.new
- assert topic.valid?, "Validation doesn't run on create if 'on' is set to update"
+ assert topic.valid?, "Validation doesn't run on valid? if 'on' is set to create"
end
test "with a class that adds errors on update and validating a new model" do
- Topic.validates_with(ValidatorThatAddsErrors, :on => :update)
+ Topic.validates_with(ValidatorThatAddsErrors, on: :update)
topic = Topic.new
assert topic.valid?(:create), "Validation doesn't run on create if 'on' is set to update"
end
test "with a class that adds errors on create and validating a new model" do
- Topic.validates_with(ValidatorThatAddsErrors, :on => :create)
+ Topic.validates_with(ValidatorThatAddsErrors, on: :create)
topic = Topic.new
assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create"
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
-end \ No newline at end of file
+end
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index daa7a8d5c4..93716f1433 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -72,26 +72,26 @@ class ValidatesWithTest < ActiveModel::TestCase
end
test "with if statements that return false" do
- Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 2")
+ Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2")
topic = Topic.new
assert topic.valid?
end
test "with if statements that return true" do
- Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 1")
+ Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1")
topic = Topic.new
assert topic.invalid?
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
test "with unless statements that return true" do
- Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 1")
+ Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1")
topic = Topic.new
assert topic.valid?
end
test "with unless statements that returns false" do
- Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 2")
+ Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2")
topic = Topic.new
assert topic.invalid?
assert topic.errors[:base].include?(ERROR_MESSAGE)
@@ -100,45 +100,23 @@ class ValidatesWithTest < ActiveModel::TestCase
test "passes all configuration options to the validator class" do
topic = Topic.new
validator = mock()
- validator.expects(:new).with(:foo => :bar, :if => "1 == 1").returns(validator)
+ validator.expects(:new).with(foo: :bar, if: "1 == 1", class: Topic).returns(validator)
validator.expects(:validate).with(topic)
- Topic.validates_with(validator, :if => "1 == 1", :foo => :bar)
- assert topic.valid?
- end
-
- test "calls setup method of validator passing in self when validator has setup method" do
- topic = Topic.new
- validator = stub_everything
- validator.stubs(:new).returns(validator)
- validator.stubs(:validate)
- validator.stubs(:respond_to?).with(:setup).returns(true)
- validator.expects(:setup).with(Topic).once
- Topic.validates_with(validator)
- assert topic.valid?
- end
-
- test "doesn't call setup method of validator when validator has no setup method" do
- topic = Topic.new
- validator = stub_everything
- validator.stubs(:new).returns(validator)
- validator.stubs(:validate)
- validator.stubs(:respond_to?).with(:setup).returns(false)
- validator.expects(:setup).with(Topic).never
- Topic.validates_with(validator)
+ Topic.validates_with(validator, if: "1 == 1", foo: :bar)
assert topic.valid?
end
test "validates_with with options" do
- Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name)
+ Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name)
topic = Topic.new
assert topic.invalid?
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
test "validates_with each validator" do
- Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content])
- topic = Topic.new :title => "Title", :content => "Content"
+ Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content])
+ topic = Topic.new title: "Title", content: "Content"
assert topic.invalid?
assert_equal ["Value is Title"], topic.errors[:title]
assert_equal ["Value is Content"], topic.errors[:content]
@@ -146,7 +124,7 @@ class ValidatesWithTest < ActiveModel::TestCase
test "each validator checks validity" do
assert_raise RuntimeError do
- Topic.validates_with(ValidatorCheckValidity, :attributes => [:title])
+ Topic.validates_with(ValidatorCheckValidity, attributes: [:title])
end
end
@@ -157,25 +135,25 @@ class ValidatesWithTest < ActiveModel::TestCase
end
test "each validator skip nil values if :allow_nil is set to true" do
- Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content], :allow_nil => true)
- topic = Topic.new :content => ""
+ Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_nil: true)
+ topic = Topic.new content: ""
assert topic.invalid?
assert topic.errors[:title].empty?
assert_equal ["Value is "], topic.errors[:content]
end
test "each validator skip blank values if :allow_blank is set to true" do
- Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content], :allow_blank => true)
- topic = Topic.new :content => ""
+ Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_blank: true)
+ topic = Topic.new content: ""
assert topic.valid?
assert topic.errors[:title].empty?
assert topic.errors[:content].empty?
end
test "validates_with can validate with an instance method" do
- Topic.validates :title, :with => :my_validation
+ Topic.validates :title, with: :my_validation
- topic = Topic.new :title => "foo"
+ topic = Topic.new title: "foo"
assert topic.valid?
assert topic.errors[:title].empty?
@@ -185,9 +163,9 @@ class ValidatesWithTest < ActiveModel::TestCase
end
test "optionally pass in the attribute being validated when validating with an instance method" do
- Topic.validates :title, :content, :with => :my_validation_with_arg
+ Topic.validates :title, :content, with: :my_validation_with_arg
- topic = Topic.new :title => "foo"
+ topic = Topic.new title: "foo"
assert !topic.valid?
assert topic.errors[:title].empty?
assert_equal ['is missing'], topic.errors[:content]
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 2934e70c2f..039b6b8872 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -166,7 +166,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_invalid_validator
Topic.validate :i_dont_exist
- assert_raise(NameError) do
+ assert_raises(NoMethodError) do
t = Topic.new
t.valid?
end
@@ -190,16 +190,16 @@ class ValidationsTest < ActiveModel::TestCase
def test_validation_order
Topic.validates_presence_of :title
- Topic.validates_length_of :title, :minimum => 2
+ Topic.validates_length_of :title, minimum: 2
t = Topic.new("title" => "")
assert t.invalid?
assert_equal "can't be blank", t.errors["title"].first
Topic.validates_presence_of :title, :author_name
Topic.validate {errors.add('author_email_address', 'will never be valid')}
- Topic.validates_length_of :title, :content, :minimum => 2
+ Topic.validates_length_of :title, :content, minimum: 2
- t = Topic.new :title => ''
+ t = Topic.new title: ''
assert t.invalid?
assert_equal :title, key = t.errors.keys[0]
@@ -214,9 +214,9 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_validation_with_if_and_on
- Topic.validates_presence_of :title, :if => Proc.new{|x| x.author_name = "bad"; true }, :on => :update
+ Topic.validates_presence_of :title, if: Proc.new{|x| x.author_name = "bad"; true }, on: :update
- t = Topic.new(:title => "")
+ t = Topic.new(title: "")
# If block should not fire
assert t.valid?
@@ -239,7 +239,7 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_validation_with_message_as_proc
- Topic.validates_presence_of(:title, :message => proc { "no blanks here".upcase })
+ Topic.validates_presence_of(:title, message: proc { "no blanks here".upcase })
t = Topic.new
assert t.invalid?
@@ -248,7 +248,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_list_of_validators_for_model
Topic.validates_presence_of :title
- Topic.validates_length_of :title, :minimum => 2
+ Topic.validates_length_of :title, minimum: 2
assert_equal 2, Topic.validators.count
assert_equal [:presence, :length], Topic.validators.map(&:kind)
@@ -256,7 +256,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_list_of_validators_on_an_attribute
Topic.validates_presence_of :title, :content
- Topic.validates_length_of :title, :minimum => 2
+ Topic.validates_length_of :title, minimum: 2
assert_equal 2, Topic.validators_on(:title).count
assert_equal [:presence, :length], Topic.validators_on(:title).map(&:kind)
@@ -265,13 +265,13 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_accessing_instance_of_validator_on_an_attribute
- Topic.validates_length_of :title, :minimum => 10
+ Topic.validates_length_of :title, minimum: 10
assert_equal 10, Topic.validators_on(:title).first.options[:minimum]
end
def test_list_of_validators_on_multiple_attributes
- Topic.validates :title, :length => { :minimum => 10 }
- Topic.validates :author_name, :presence => true, :format => /a/
+ Topic.validates :title, length: { minimum: 10 }
+ Topic.validates :author_name, presence: true, format: /a/
validators = Topic.validators_on(:title, :author_name)
@@ -283,7 +283,7 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_list_of_validators_will_be_empty_when_empty
- Topic.validates :title, :length => { :minimum => 10 }
+ Topic.validates :title, length: { minimum: 10 }
assert_equal [], Topic.validators_on(:author_name)
end
@@ -300,52 +300,52 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_strict_validation_in_validates
- Topic.validates :title, :strict => true, :presence => true
+ Topic.validates :title, strict: true, presence: true
assert_raises ActiveModel::StrictValidationFailed do
Topic.new.valid?
end
end
def test_strict_validation_not_fails
- Topic.validates :title, :strict => true, :presence => true
- assert Topic.new(:title => "hello").valid?
+ Topic.validates :title, strict: true, presence: true
+ assert Topic.new(title: "hello").valid?
end
def test_strict_validation_particular_validator
- Topic.validates :title, :presence => { :strict => true }
+ Topic.validates :title, presence: { strict: true }
assert_raises ActiveModel::StrictValidationFailed do
Topic.new.valid?
end
end
def test_strict_validation_in_custom_validator_helper
- Topic.validates_presence_of :title, :strict => true
+ Topic.validates_presence_of :title, strict: true
assert_raises ActiveModel::StrictValidationFailed do
Topic.new.valid?
end
end
def test_strict_validation_custom_exception
- Topic.validates_presence_of :title, :strict => CustomStrictValidationException
+ Topic.validates_presence_of :title, strict: CustomStrictValidationException
assert_raises CustomStrictValidationException do
Topic.new.valid?
end
end
def test_validates_with_bang
- Topic.validates! :title, :presence => true
+ Topic.validates! :title, presence: true
assert_raises ActiveModel::StrictValidationFailed do
Topic.new.valid?
end
end
def test_validates_with_false_hash_value
- Topic.validates :title, :presence => false
+ Topic.validates :title, presence: false
assert Topic.new.valid?
end
def test_strict_validation_error_message
- Topic.validates :title, :strict => true, :presence => true
+ Topic.validates :title, strict: true, presence: true
exception = assert_raises(ActiveModel::StrictValidationFailed) do
Topic.new.valid?
@@ -354,9 +354,9 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_does_not_modify_options_argument
- options = { :presence => true }
+ options = { presence: true }
Topic.validates :title, options
- assert_equal({ :presence => true }, options)
+ assert_equal({ presence: true }, options)
end
def test_dup_validity_is_independent
@@ -373,4 +373,25 @@ class ValidationsTest < ActiveModel::TestCase
assert topic.invalid?
assert duped.valid?
end
+
+ # validator test:
+ def test_setup_is_deprecated_but_still_receives_klass # TODO: remove me in 4.2.
+ validator_class = Class.new(ActiveModel::Validator) do
+ def setup(klass)
+ @old_klass = klass
+ end
+
+ def validate(*)
+ @old_klass == Topic or raise "#setup didn't work"
+ end
+ end
+
+ assert_deprecated do
+ Topic.validates_with validator_class
+ end
+
+ t = Topic.new
+ t.valid?
+ end
+
end
diff --git a/activemodel/test/models/automobile.rb b/activemodel/test/models/automobile.rb
index 021ea61c80..ece644c40c 100644
--- a/activemodel/test/models/automobile.rb
+++ b/activemodel/test/models/automobile.rb
@@ -7,6 +7,6 @@ class Automobile
def validations
validates_presence_of :make
- validates_length_of :model, :within => 2..10
+ validates_length_of :model, within: 2..10
end
-end \ No newline at end of file
+end
diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb
index 7bfc542afb..c25be28e1d 100644
--- a/activemodel/test/models/contact.rb
+++ b/activemodel/test/models/contact.rb
@@ -9,7 +9,7 @@ class Contact
end
def network
- {:git => :github}
+ { git: :github }
end
def initialize(options = {})
diff --git a/activemodel/test/models/reply.rb b/activemodel/test/models/reply.rb
index ec1efeac19..b77910e671 100644
--- a/activemodel/test/models/reply.rb
+++ b/activemodel/test/models/reply.rb
@@ -2,11 +2,11 @@ require 'models/topic'
class Reply < Topic
validate :errors_on_empty_content
- validate :title_is_wrong_create, :on => :create
+ validate :title_is_wrong_create, on: :create
validate :check_empty_title
- validate :check_content_mismatch, :on => :create
- validate :check_wrong_update, :on => :update
+ validate :check_content_mismatch, on: :create
+ validate :check_wrong_update, on: :update
def check_empty_title
errors[:title] << "is Empty" unless title && title.size > 0
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 0c8c8c006e..0955761b7f 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1898 +1,191 @@
-## Rails 4.0.0 (unreleased) ##
-
-* Allow ActiveRecord::Base.connection_handler to have thread affinity and be
- settable, this effectively allows ActiveRecord to be used in a multi threaded
- setup with multiple connections to multiple dbs.
-
- *Sam Saffron*
-
-* `rename_column` preserves auto_increment in mysql migrations.
- Fixes #3493.
-
- *Vipul A M*
-
-* PostgreSQL geometric type point is supported by ActiveRecord. Fixes #7324.
-
- *Martin Schuerrer*
-
-* Add support for concurrent indexing in PostgreSQL adapter via the
- `algorithm: :concurrently` option
-
- add_index(:people, :last_name, algorithm: :concurrently)
-
- Also adds support for MySQL index algorithms (`COPY`, `INPLACE`,
- `DEFAULT`) via the `algorithm: :copy` option
-
- add_index(:people, :last_name, algorithm: :copy) # or :inplace/:default
-
- *Dan McClain*
-
-* Add support for fulltext and spatial indexes on MySQL tables with MyISAM database
- engine via the `type: 'FULLTEXT'` / `type: 'SPATIAL'` option
-
- add_index(:people, :last_name, type: 'FULLTEXT')
- add_index(:people, :last_name, type: 'SPATIAL')
-
- *Ken Mazaika*
-
-* Add an `add_index` override in Postgresql adapter and MySQL adapter
- to allow custom index type support. Fixes #6101.
-
- add_index(:wikis, :body, :using => 'gin')
-
- *Stefan Huber* and *Doabit*
-
-* After extraction of mass-assignment attributes (which protects [id, type]
- by default) we can pass id to `update_attributes` and it will update
- another record because id will be used in where statement. We never have
- to change id in where statement because we try to set/replace fields for
- already loaded record but we have to try to set new id for that record.
-
- *Dmitry Vorotilin*
-
-* Models with multiple counter cache associations now update correctly on destroy.
- See #7706.
-
- *Ian Young*
-
-* If `:inverse_of` is true on an association, then when one calls `find()` on
- the association, Active Record will first look through the in-memory objects
- in the association for a particular id. Then, it will go to the DB if it
- is not found. This is accomplished by calling `find_by_scan` in
- collection associations whenever `options[:inverse_of]` is not nil.
-
- Fixes #9470.
-
- *John Wang*
-
-* `rake db:create` does not change permissions of the MySQL root user.
- Fixes #8079.
-
- *Yves Senn*
-
-* The length of the `version` column in the `schema_migrations` table
- created by the `mysql2` adapter is 191 if the encoding is "utf8mb4".
-
- The "utf8" encoding in MySQL has support for a maximum of 3 bytes per character,
- and only contains characters from the BMP. The recently added
- [utf8mb4](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)
- encoding extends the support to four bytes. As of this writing, said encoding
- is supported in the betas of the `mysql2` gem.
-
- Setting the encoding to "utf8mb4" has
- [a few implications](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-upgrading.html).
- This change addresses the max length for indexes, which is 191 instead of 255.
-
- *Xavier Noria*
-
-* Counter caches on associations will now stay valid when attributes are
- updated (not just when records are created or destroyed), for example,
- when calling `update_attributes`. The following code now works:
-
- class Comment < ActiveRecord::Base
- belongs_to :post, counter_cache: true
- end
-
- class Post < ActiveRecord::Base
- has_many :comments
- end
-
- post = Post.create
- comment = Comment.create
-
- post.comments << comment
- post.save.reload.comments_count # => 1
- comment.update_attributes(post_id: nil)
-
- post.save.reload.comments_count # => 0
-
- Updating the id of a `belongs_to` object with the id of a new object will
- also keep the count accurate.
-
- *John Wang*
-
-* Referencing join tables implicitly was deprecated. There is a
- possibility that these deprecation warnings are shown even if you
- don't make use of that feature. You can now disable the feature entirely.
- Fixes #9712.
-
- Example:
-
- # in your configuration
- config.active_record.disable_implicit_join_references = true
-
- # or directly
- ActiveRecord::Base.disable_implicit_join_references = true
-
- *Yves Senn*
-
-* The `:distinct` option for `Relation#count` is deprecated. You
- should use `Relation#distinct` instead.
-
- Example:
-
- # Before
- Post.select(:author_name).count(distinct: true)
-
- # After
- Post.select(:author_name).distinct.count
-
- *Yves Senn*
-
-* Rename `Relation#uniq` to `Relation#distinct`. `#uniq` is still
- available as an alias but we encourage to use `#distinct` instead.
- Also `Relation#uniq_value` is aliased to `Relation#distinct_value`,
- this is a temporary solution and you should migrate to `distinct_value`.
-
- *Yves Senn*
-
-* Fix quoting for sqlite migrations using `copy_table_contents` with binary
- columns.
-
- These would fail with "SQLite3::SQLException: unrecognized token" because
- the column was not being passed to `quote` so the data was not quoted
- correctly.
-
- *Matthew M. Boedicker*
-
-* Promotes `change_column_null` to the migrations API. This macro sets/removes
- `NOT NULL` constraints, and accepts an optional argument to replace existing
- `NULL`s if needed. The adapters for SQLite, MySQL, PostgreSQL, and (at least)
- Oracle, already implement this method.
-
- *Xavier Noria*
-
-* Uniqueness validation allows you to pass `:conditions` to limit
- the constraint lookup.
-
- Example:
-
- validates_uniqueness_of :title, conditions: -> { where('approved = ?', true) }
-
- *Mattias Pfeiffer + Yves Senn*
-
-* `connection` is deprecated as an instance method.
- This allows end-users to have a `connection` method on their models
- without clashing with Active Record internals.
-
- *Ben Moss*
-
-* When copying migrations, preserve their magic comments and content encoding.
-
- *OZAWA Sakuro*
-
-* Fix `subclass_from_attrs` when `eager_load` is false. It cannot find
- subclass because all classes are loaded automatically when it needs.
-
- *Dmitry Vorotilin*
-
-* When `:name` option is provided to `remove_index`, use it if there is no
- index by the conventional name.
-
- For example, previously if an index was removed like so
- `remove_index :values, column: :value, name: 'a_different_name'`
- the generated SQL would not contain the specified index name,
- and hence the migration would fail.
- Fixes #8858.
-
- *Ezekiel Smithburg*
-
-* Created block to by-pass the prepared statement bindings.
- This will allow to compose fragments of large SQL statements to
- avoid multiple round-trips between Ruby and the DB.
-
- Example:
-
- sql = Post.connection.unprepared_statement do
- Post.first.comments.to_sql
- end
-
- *Cédric Fabianski*
-
-* Change the semantics of combining scopes to be the same as combining
- class methods which return scopes. For example:
-
- class User < ActiveRecord::Base
- scope :active, -> { where state: 'active' }
- scope :inactive, -> { where state: 'inactive' }
- end
-
- class Post < ActiveRecord::Base
- def self.active
- where state: 'active'
- end
-
- def self.inactive
- where state: 'inactive'
- end
- end
-
- ### BEFORE ###
-
- User.where(state: 'active').where(state: 'inactive')
- # => SELECT * FROM users WHERE state = 'active' AND state = 'inactive'
-
- User.active.inactive
- # => SELECT * FROM users WHERE state = 'inactive'
-
- Post.active.inactive
- # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
-
- ### AFTER ###
-
- User.active.inactive
- # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
-
- Before this change, invoking a scope would merge it into the current
- scope and return the result. `Relation#merge` applies "last where
- wins" logic to de-duplicate the conditions, but this lead to
- confusing and inconsistent behaviour. This fixes that.
-
- If you really do want the "last where wins" logic, you can opt-in to
- it like so:
-
- User.active.merge(User.inactive)
-
- Fixes #7365.
-
- *Neeraj Singh* and *Jon Leighton*
-
-* Expand `#cache_key` to consult all relevant updated timestamps.
-
- Previously only `updated_at` column was checked, now it will
- consult other columns that received updated timestamps on save,
- such as `updated_on`. When multiple columns are present it will
- use the most recent timestamp.
- Fixes #9033.
-
- *Brendon Murphy*
-
-* Throw `NotImplementedError` when trying to instantiate `ActiveRecord::Base` or an abstract class.
-
- *Aaron Weiner*
-
-* Warn when `rake db:structure:dump` with a MySQL database and
- `mysqldump` is not in the PATH or fails.
- Fixes #9518.
-
- *Yves Senn*
-
-* Remove `connection#structure_dump`, which is no longer used. *Yves Senn*
-
-* Make it possible to execute migrations without a transaction even
- if the database adapter supports DDL transactions.
- Fixes #9483.
-
- Example:
-
- class ChangeEnum < ActiveRecord::Migration
- disable_ddl_transaction!
-
- def up
- execute "ALTER TYPE model_size ADD VALUE 'new_value'"
- end
- end
-
- *Yves Senn*
-
-* Assigning "0.0" to a nullable numeric column does not make it dirty.
- Fixes #9034.
-
- Example:
-
- product = Product.create price: 0.0
- product.price = '0.0'
- product.changed? # => false (this used to return true)
- product.changes # => {} (this used to return { price: [0.0, 0.0] })
-
- *Yves Senn*
-
-* Added functionality to unscope relations in a relations chain. For
- instance, if you are passed in a chain of relations as follows:
-
- User.where(name: "John").order('id DESC')
-
- but you want to get rid of order, then this feature allows you to do:
-
- User.where(name: "John").order('id DESC').unscope(:order)
- == User.where(name: "John")
-
- The .unscope() function is more general than the .except() method because
- .except() only works on the relation it is acting on. However, .unscope()
- works for any relation in the entire relation chain.
-
- *John Wang*
-
-* PostgreSQL timestamp with time zone (timestamptz) datatype now returns a
- ActiveSupport::TimeWithZone instance instead of a string
-
- *Troy Kruthoff*
-
-* The `#append` method for collection associations behaves like`<<`.
- `#prepend` is not defined and `<<` or `#append` should be used.
- Fixes #7364.
-
- *Yves Senn*
-
-* Added support for creating a table via Rails migration generator.
- For example,
-
- rails g migration create_books title:string content:text
-
- will generate a migration that creates a table called books with
- the listed attributes, without creating a model.
-
- *Sammy Larbi*
-
-* Fix bug that raises the wrong exception when the exception handled by PostgreSQL adapter
- doesn't respond to `#result`.
- Fixes #8617.
-
- *kennyj*
-
-* Support PostgreSQL specific column types when using `change_table`.
- Fixes #9480.
-
- Example:
-
- change_table :authors do |t|
- t.hstore :books
- t.json :metadata
- end
-
- *Yves Senn*
-
-* Revert 408227d9c5ed7d, 'quote numeric'. This introduced some regressions.
-
- *Steve Klabnik*
-
-* Fix calculation of `db_runtime` property in
- `ActiveRecord::Railties::ControllerRuntime#cleanup_view_runtime`.
- Previously, after raising `ActionView::MissingTemplate`, `db_runtime` was
- not populated.
- Fixes #9215.
-
- *Igor Fedoronchuk*
-
-* Do not try to touch invalid (and thus not persisted) parent record
- for a `belongs_to :parent, touch: true` association
-
- *Olek Janiszewski*
-
-* Fix when performing an ordered join query. The bug only
- affected queries where the order was given with a symbol.
- Fixes #9275.
-
- Example:
-
- # This will expand the order :name to "authors".name.
- Author.joins(:books).where('books.published = 1').order(:name)
-
-
-## Rails 4.0.0.beta1 (February 25, 2013) ##
-
-* Fix overriding of attributes by `default_scope` on `ActiveRecord::Base#dup`.
-
- *Hiroshige UMINO*
-
-* Update queries now use prepared statements.
-
- *Olli Rissanen*
-
-* Fixing issue #8345. Now throwing an error when one attempts to touch a
- new object that has not yet been persisted. For instance:
+* Remove column restrictions for `count`, let the database raise if the SQL is
+ invalid. The previous behavior was untested and surprising for the user.
+ Fixes #5554.
Example:
- ball = Ball.new
- ball.touch :updated_at # => raises error
+ User.select("name, username").count
+ # Before => SELECT count(*) FROM users
+ # After => ActiveRecord::StatementInvalid
- It is not until the ball object has been persisted that it can be touched.
- This follows the behavior of update_column.
-
- *John Wang*
-
-* Preloading ordered `has_many :through` associations no longer applies
- invalid ordering to the `:through` association.
- Fixes #8663.
-
- *Yves Senn*
-
-* The auto explain feature has been removed. This feature was
- activated by configuring `config.active_record.auto_explain_threshold_in_seconds`.
- The configuration option was deprecated and has no more effect.
-
- You can still use `ActiveRecord::Relation#explain` to see the EXPLAIN output for
- any given relation.
-
- *Yves Senn*
-
-* The `:on` option for `after_commit` and `after_rollback` now
- accepts an Array of actions.
- Fixes #988.
-
- Example:
-
- after_commit :update_cache on: [:create, :update]
-
- *Yves Senn*
-
-* Rename related indexes on `rename_table` and `rename_column`. This
- does not affect indexes with custom names.
+ # you can still use `count(:all)` to perform a query unrelated to the
+ # selected columns
+ User.select("name, username").count(:all) # => SELECT count(*) FROM users
*Yves Senn*
-* Prevent the creation of indices with too long names, which cause
- internal operations to fail (sqlite3 adapter only). The method
- `allowed_index_name_length` defines the length limit enforced by
- rails. It's value defaults to `index_name_length` but can vary per adapter.
- Fixes #8264.
+* Rails now automatically detects inverse associations. If you do not set the
+ `:inverse_of` option on the association, then Active Record will guess the
+ inverse association based on heuristics.
- *Yves Senn*
+ Note that automatic inverse detection only works on `has_many`, `has_one`,
+ and `belongs_to` associations. Extra options on the associations will
+ also prevent the association's inverse from being found automatically.
-* Fixing issue #776.
-
- Memory bloat in transactions is handled by having the transaction hold only
- the AR objects which it absolutely needs to know about. These are the AR
- objects with callbacks (they need to be updated as soon as something in the
- transaction occurs).
-
- All other AR objects can be updated lazily by keeping a reference to a
- TransactionState object. If an AR object gets inside a transaction, then
- the transaction will add its TransactionState to the AR object. When the
- user makes a call to some attribute on an AR object (which has no
- callbacks) associated with a transaction, the AR object will call the
- sync_with_transaction_state method and make sure it is up to date with the
- transaction. After it has synced with the transaction state, the AR object
- will return the attribute that was requested.
-
- Most of the logic in the changes are used to handle multiple transactions,
- in which case the AR object has to recursively follow parent pointers of
- TransactionState objects.
-
- *John Wang*
+ The automatic guessing of the inverse association uses a heuristic based
+ on the name of the class, so it may not work for all associations,
+ especially the ones with non-standard names.
-* Descriptive error message when the necessary AR adapter gem was not found.
- Fixes #7313.
+ You can turn off the automatic detection of inverse associations by setting
+ the `:inverse_of` option to `false` like so:
- *Yves Senn*
-
-* Active Record now raises an error when blank arguments are passed to query
- methods for which blank arguments do not make sense.
-
- Example:
-
- Post.includes() # => raises error
+ class Taggable < ActiveRecord::Base
+ belongs_to :tag, inverse_of: false
+ end
*John Wang*
-* Simplified type casting code for timezone aware attributes to use the
- `in_time_zone` method if it is available. This introduces a subtle change
- of behavior when using `Date` instances as they are directly converted to
- `ActiveSupport::TimeWithZone` instances without first being converted to
- `Time` instances. For example:
-
- # Rails 3.2 behavior
- >> Date.today.to_time.in_time_zone
- => Wed, 13 Feb 2013 07:00:00 UTC +00:00
-
- # Rails 4.0 behavior
- >> Date.today.in_time_zone
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- On the plus side it now behaves the same whether you pass a `String` date
- or an actual `Date` instance. For example:
-
- # Rails 3.2 behavior
- >> Date.civil(2013, 2, 13).to_time.in_time_zone
- => Wed, 13 Feb 2013 07:00:00 UTC +00:00
- >> Time.zone.parse("2013-02-13")
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- # Rails 4.0 behavior
- >> Date.civil(2013, 2, 13).in_time_zone
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
- >> "2013-02-13".in_time_zone
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- If you need the old behavior you can convert the dates to times manually.
- For example:
-
- >> Post.new(created_at: Date.today).created_at
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- >> Post.new(created_at: Date.today.to_time).created_at
- => Wed, 13 Feb 2013 07:00:00 UTC +00:00
-
- *Andrew White*
-
-* Preloading `has_many :through` associations with conditions won't
- cache the `:through` association. This will prevent invalid
- subsets to be cached.
- Fixes #8423.
-
- Example:
-
- class User
- has_many :posts
- has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts
- end
-
- a_user = User.includes(:recent_comments).first
-
- # This is preloaded.
- a_user.recent_comments
-
- # This is not preloaded, fetched now.
- a_user.posts
-
- *Yves Senn*
-
-* Don't run `after_commit` callbacks when creating through an association
- if saving the record fails.
-
- *James Miller*
-
-* Allow store accessors to be overridden like other attribute methods, e.g.:
-
- class User < ActiveRecord::Base
- store :settings, accessors: [ :color, :homepage ], coder: JSON
-
- def color
- super || 'red'
- end
- end
-
- *Sergey Nartimov*
-
-* Quote numeric values being compared to non-numeric columns. Otherwise,
- in some database, the string column values will be coerced to a numeric
- allowing 0, 0.0 or false to match any string starting with a non-digit.
-
- Example:
-
- App.where(apikey: 0) # => SELECT * FROM users WHERE apikey = '0'
-
- *Dylan Smith*
-
-* Schema dumper supports dumping the enabled database extensions to `schema.rb`
- (currently only supported by PostgreSQL).
-
- *Justin George*
-
-* The database adapters now converts the options passed thought `DATABASE_URL`
- environment variable to the proper Ruby types before using. For example, SQLite requires
- that the timeout value is an integer, and PostgreSQL requires that the
- prepared_statements option is a boolean. These now work as expected:
-
- Example:
-
- DATABASE_URL=sqlite3://localhost/test_db?timeout=500
- DATABASE_URL=postgresql://localhost/test_db?prepared_statements=false
-
- *Aaron Stone + Rafael Mendonça França*
-
-* `Relation#merge` now only overwrites where values on the LHS of the
- merge. Consider:
-
- left = Person.where(age: [13, 14, 15])
- right = Person.where(age: [13, 14]).where(age: [14, 15])
-
- `left` results in the following SQL:
-
- WHERE age IN (13, 14, 15)
-
- `right` results in the following SQL:
-
- WHERE age IN (13, 14) AND age IN (14, 15)
-
- Previously, `left.merge(right)` would result in all but the last
- condition being removed:
-
- WHERE age IN (14, 15)
-
- Now it results in the LHS condition(s) for `age` being removed, but
- the RHS remains as it is:
-
- WHERE age IN (13, 14) AND age IN (14, 15)
-
- *Jon Leighton*
-
-* Fix handling of dirty time zone aware attributes
-
- Previously, when `time_zone_aware_attributes` were enabled, after
- changing a datetime or timestamp attribute and then changing it back
- to the original value, `changed_attributes` still tracked the
- attribute as changed. This caused `[attribute]_changed?` and
- `changed?` methods to return true incorrectly.
-
- Example:
-
- in_time_zone 'Paris' do
- order = Order.new
- original_time = Time.local(2012, 10, 10)
- order.shipped_at = original_time
- order.save
- order.changed? # => false
-
- # changing value
- order.shipped_at = Time.local(2013, 1, 1)
- order.changed? # => true
-
- # reverting to original value
- order.shipped_at = original_time
- order.changed? # => false, used to return true
- end
-
- *Lilibeth De La Cruz*
-
-* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`.
- Fixes #6865.
-
- Example:
-
- relation.uniq.count # => SELECT COUNT(DISTINCT *)
-
- *Yves Senn + Kaspar Schiess*
-
-* PostgreSQL ranges type support. Includes: int4range, int8range,
- numrange, tsrange, tstzrange, daterange
-
- Ranges can be created with inclusive and exclusive bounds.
-
- Example:
-
- create_table :Room do |t|
- t.daterange :availability
- end
-
- Room.create(availability: (Date.today..Float::INFINITY))
- Room.first.availability # => Wed, 19 Sep 2012..Infinity
-
- One thing to note: Range class does not support exclusive lower
- bound.
+* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432
- *Alexander Grebennik*
-
-* Added a state instance variable to each transaction. Will allow other objects
- to know whether a transaction has been committed or rolled back.
-
- *John Wang*
+ *Adam Anderson*
-* Collection associations `#empty?` always respects built records.
- Fixes #8879.
+* Usage of `implicit_readonly` is being removed`. Please use `readonly` method
+ explicitly to mark records as `readonly.
+ Fixes #10615.
Example:
- widget = Widget.new
- widget.things.build
- widget.things.empty? # => false
+ user = User.joins(:todos).select("users.*, todos.title as todos_title").readonly(true).first
+ user.todos_title = 'clean pet'
+ user.save! # will raise error
*Yves Senn*
-* Support for PostgreSQL's `ltree` data type.
-
- *Rob Worley*
-
-* Fix undefined method `to_i` when calling `new` on a scope that uses an
- Array; Fix FloatDomainError when setting integer column to NaN.
- Fixes #8718, #8734, #8757.
-
- *Jason Stirk + Tristan Harward*
-
-* Rename `update_attributes` to `update`, keep `update_attributes` as an alias for `update` method.
- This is a soft-deprecation for `update_attributes`, although it will still work without any
- deprecation message in 4.0 is recommended to start using `update` since `update_attributes` will be
- deprecated and removed in future versions of Rails.
-
- *Amparo Luna + Guillermo Iguaran*
-
-* `after_commit` and `after_rollback` now validate the `:on` option and raise an `ArgumentError`
- if it is not one of `:create`, `:destroy` or `:update`
-
- *Pascal Friederich*
-
-* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary.
-
- * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given.
- The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not reversible).
- The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default`
-
- * New method `reversible` makes it possible to specify code to be run when migrating up or down.
- See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method)
-
- * New method `revert` will revert a whole migration or the given block.
- If migrating down, the given migration / block is run normally.
- See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations)
-
- Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now
- raise an `IrreversibleMigration` instead of actually executing them without any output.
-
- *Marc-André Lafortune*
-
-* Serialized attributes can be serialized in integer columns.
- Fixes #8575.
-
- *Rafael Mendonça França*
-
-* Keep index names when using `alter_table` with sqlite3.
- Fixes #3489.
-
- *Yves Senn*
-
-* Add ability for PostgreSQL adapter to disable user triggers in `disable_referential_integrity`.
- Fixes #5523.
-
- *Gary S. Weaver*
-
-* Added support for `validates_uniqueness_of` in PostgreSQL array columns.
- Fixes #8075.
-
- *Pedro Padron*
-
-* Allow int4range and int8range columns to be created in PostgreSQL and properly convert to/from database.
-
- *Alexey Vasiliev aka leopard*
-
-* Do not log the binding values for binary columns.
-
- *Matthew M. Boedicker*
-
-* Fix counter cache columns not updated when replacing `has_many :through`
- associations.
-
- *Matthew Robertson*
-
-* Recognize migrations placed in directories containing numbers and 'rb'.
- Fixes #8492.
+* Fix the `:primary_key` option for `has_many` associations.
+ Fixes #10693.
*Yves Senn*
-* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control
- the format of the timestamp value in the cache key. Defaults to `:nsec`.
- Fixes #8195.
-
- *Rafael Mendonça França*
-
-* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters
- in the `variables: <hash>` parameter in `config/database.yml`. The key-value pairs of this
- hash will be sent in a `SET key = value` query on new database connections. See also:
- http://dev.mysql.com/doc/refman/5.0/en/set-statement.html
- http://www.postgresql.org/docs/8.3/static/sql-set.html
-
- *Aaron Stone*
-
-* Allow `Relation#where` with no arguments to be chained with new `not` query method.
-
- Example:
-
- Developer.where.not(name: 'Aaron')
-
- *Akira Matsuda*
+* Fix bug where tiny types are incorrectly coerced as boolean when the length is more than 1.
-* Unscope `update_column(s)` query to ignore default scope.
+ Fixes #10620.
- When applying `default_scope` to a class with a where clause, using
- `update_column(s)` could generate a query that would not properly update
- the record due to the where clause from the `default_scope` being applied
- to the update query.
+ *Aaron Peterson*
- class User < ActiveRecord::Base
- default_scope -> { where(active: true) }
- end
-
- user = User.first
- user.active = false
- user.save!
-
- user.update_column(:active, true) # => false
-
- In this situation we want to skip the default_scope clause and just
- update the record based on the primary key. With this change:
-
- user.update_column(:active, true) # => true
-
- Fixes #8436.
-
- *Carlos Antonio da Silva*
-
-* SQLite adapter no longer corrupts binary data if the data contains `%00`.
-
- *Chris Feist*
-
-* Fix performance problem with `primary_key` method in PostgreSQL adapter when having many schemas.
- Uses `pg_constraint` table instead of `pg_depend` table which has many records in general.
- Fixes #8414.
+* Also support extensions in PostgreSQL 9.1. This feature has been supported since 9.1.
*kennyj*
-* Do not instantiate intermediate Active Record objects when eager loading.
- These records caused `after_find` to run more than expected.
- Fixes #3313.
+* Deprecate `ConnectionAdapters::SchemaStatements#distinct`,
+ as it is no longer used by internals.
- *Yves Senn*
+ *Ben Woosley#
-* Add STI support to init and building associations.
- Allows you to do `BaseClass.new(type: "SubClass")` as well as
- `parent.children.build(type: "SubClass")` or `parent.build_child`
- to initialize an STI subclass. Ensures that the class name is a
- valid class and that it is in the ancestors of the super class
- that the association is expecting.
+* Fix pending migrations error when loading schema and `ActiveRecord::Base.table_name_prefix`
+ is not blank.
- *Jason Rush*
+ Call `assume_migrated_upto_version` on connection to prevent it from first
+ being picked up in `method_missing`.
-* Observers was extracted from Active Record as `rails-observers` gem.
+ In the base class, `Migration`, `method_missing` expects the argument to be a
+ table name, and calls `proper_table_name` on the arguments before sending to
+ `connection`. If `table_name_prefix` or `table_name_suffix` is used, the schema
+ version changes to `prefix_version_suffix`, breaking `rake test:prepare`.
- *Rafael Mendonça França*
+ Fixes #10411.
-* Ensure that associations take a symbol argument. *Steve Klabnik*
+ *Kyle Stevens*
-* Fix dirty attribute checks for `TimeZoneConversion` with nil and blank
- datetime attributes. Setting a nil datetime to a blank string should not
- result in a change being flagged.
- Fixes #8310.
+* Method `read_attribute_before_type_cast` should accept input as symbol.
- *Alisdair McDiarmid*
+ *Neeraj Singh*
-* Prevent mass assignment to the type column of polymorphic associations when using `build`
- Fixes #8265.
+* Confirm a record has not already been destroyed before decrementing counter cache.
- *Yves Senn*
-
-* Deprecate calling `Relation#sum` with a block. To perform a calculation over
- the array result of the relation, use `to_a.sum(&block)`.
-
- *Carlos Antonio da Silva*
-
-* Fix PostgreSQL adapter to handle BC timestamps correctly
-
- HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years)
-
- *Bogdan Gusiev*
+ *Ben Tucker*
-* When running migrations on PostgreSQL, the `:limit` option for `binary` and `text` columns is silently dropped.
- Previously, these migrations caused sql exceptions, because PostgreSQL doesn't support limits on these types.
+* Fixed a bug in `ActiveRecord#sanitize_sql_hash_for_conditions` in which
+ `self.class` is an argument to `PredicateBuilder#build_from_hash`
+ causing `PredicateBuilder` to call non-existent method
+ `Class#reflect_on_association`.
- *Victor Costan*
+ *Zach Ohlgren*
-* Don't change STI type when calling `ActiveRecord::Base#becomes`.
- Add `ActiveRecord::Base#becomes!` with the previous behavior.
-
- See #3023 for more information.
-
- *Thomas Hollstegge*
-
-* `rename_index` can be used inside a `change_table` block.
-
- change_table :accounts do |t|
- t.rename_index :user_id, :account_id
- end
-
- *Jarek Radosz*
-
-* `#pluck` can be used on a relation with `select` clause. Fix #7551
-
- Example:
-
- Topic.select([:approved, :id]).order(:id).pluck(:id)
-
- *Yves Senn*
-
-* Do not create useless database transaction when building `has_one` association.
-
- Example:
+* While removing index if column option is missing then raise IrreversibleMigration exception.
- User.has_one :profile
- User.new.build_profile
+ Following code should raise `IrreversibleMigration`. But the code was
+ failing since options is an array and not a hash.
- *Bogdan Gusiev*
-
-* `:counter_cache` option for `has_many` associations to support custom named counter caches.
- Fixes #7993.
-
- *Yves Senn*
-
-* Deprecate the possibility to pass a string as third argument of `add_index`.
- Pass `unique: true` instead.
-
- add_index(:users, :organization_id, unique: true)
-
- *Rafael Mendonça França*
-
-* Raise an `ArgumentError` when passing an invalid option to `add_index`.
-
- *Rafael Mendonça França*
-
-* Fix `find_in_batches` crashing when IDs are strings and start option is not specified.
-
- *Alexis Bernard*
-
-* `AR::Base#attributes_before_type_cast` now returns unserialized values for serialized attributes.
-
- *Nikita Afanasenko*
-
-* Use query cache/uncache when using `DATABASE_URL`.
- Fixes #6951.
-
- *kennyj*
-
-* Fix bug where `update_columns` and `update_column` would not let you update the primary key column.
-
- *Henrik Nyh*
-
-* The `create_table` method raises an `ArgumentError` when the primary key column is redefined.
- Fixes #6378.
-
- *Yves Senn*
-
-* `ActiveRecord::AttributeMethods#[]` raises `ActiveModel::MissingAttributeError`
- error if the given attribute is missing. Fixes #5433.
-
- class Person < ActiveRecord::Base
- belongs_to :company
- end
-
- # Before:
- person = Person.select('id').first
- person[:name] # => nil
- person.name # => ActiveModel::MissingAttributeError: missing_attribute: name
- person[:company_id] # => nil
- person.company # => nil
-
- # After:
- person = Person.select('id').first
- person[:name] # => ActiveModel::MissingAttributeError: missing_attribute: name
- person.name # => ActiveModel::MissingAttributeError: missing_attribute: name
- person[:company_id] # => ActiveModel::MissingAttributeError: missing_attribute: company_id
- person.company # => ActiveModel::MissingAttributeError: missing_attribute: company_id
-
- *Francesco Rodriguez*
-
-* Small binary fields use the `VARBINARY` MySQL type, instead of `TINYBLOB`.
-
- *Victor Costan*
-
-* Decode URI encoded attributes on database connection URLs.
-
- *Shawn Veader*
-
-* Add `find_or_create_by`, `find_or_create_by!` and
- `find_or_initialize_by` methods to `Relation`.
-
- These are similar to the `first_or_create` family of methods, but
- the behaviour when a record is created is slightly different:
-
- User.where(first_name: 'Penélope').first_or_create
-
- will execute:
-
- User.where(first_name: 'Penélope').create
-
- Causing all the `create` callbacks to execute within the context of
- the scope. This could affect queries that occur within callbacks.
-
- User.find_or_create_by(first_name: 'Penélope')
-
- will execute:
-
- User.create(first_name: 'Penélope')
-
- Which obviously does not affect the scoping of queries within
- callbacks.
-
- The `find_or_create_by` version also reads better, frankly.
-
- If you need to add extra attributes during create, you can do one of:
-
- User.create_with(active: true).find_or_create_by(first_name: 'Jon')
- User.find_or_create_by(first_name: 'Jon') { |u| u.active = true }
-
- The `first_or_create` family of methods have been nodoc'ed in favour
- of this API. They may be deprecated in the future but their
- implementation is very small and it's probably not worth putting users
- through lots of annoying deprecation warnings.
-
- *Jon Leighton*
-
-* Fix bug with presence validation of associations. Would incorrectly add duplicated errors
- when the association was blank. Bug introduced in 1fab518c6a75dac5773654646eb724a59741bc13.
-
- *Scott Willson*
-
-* Fix bug where sum(expression) returns string '0' for no matching records.
- Fixes #7439
-
- *Tim Macfarlane*
-
-* PostgreSQL adapter correctly fetches default values when using multiple schemas and domains in a db. Fixes #7914
-
- *Arturo Pie*
-
-* Learn ActiveRecord::QueryMethods#order work with hash arguments
-
- When symbol or hash passed we convert it to Arel::Nodes::Ordering.
- If we pass invalid direction(like name: :DeSc) ActiveRecord::QueryMethods#order will raise an exception
-
- User.order(:name, email: :desc)
- # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
-
- *Tima Maslyuchenko*
-
-* Rename `ActiveRecord::Fixtures` class to `ActiveRecord::FixtureSet`.
- Instances of this class normally hold a collection of fixtures (records)
- loaded either from a single YAML file, or from a file and a folder
- with the same name. This change make the class name singular and makes
- the class easier to distinguish from the modules like
- `ActiveRecord::TestFixtures`, which operates on multiple fixture sets,
- or `DelegatingFixtures`, `::Fixtures`, etc.,
- and from the class `ActiveRecord::Fixture`, which corresponds to a single
- fixture.
-
- *Alexey Muranov*
-
-* The postgres adapter now supports tables with capital letters.
- Fixes #5920.
-
- *Yves Senn*
-
-* `CollectionAssociation#count` returns `0` without querying if the
- parent record is not persisted.
-
- Before:
-
- person.pets.count
- # SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" IS NULL
- # => 0
-
- After:
-
- person.pets.count
- # fires without sql query
- # => 0
-
- *Francesco Rodriguez*
-
-* Fix `reset_counters` crashing on `has_many :through` associations.
- Fixes #7822.
-
- *lulalala*
-
-* Support for partial inserts.
-
- When inserting new records, only the fields which have been changed
- from the defaults will actually be included in the INSERT statement.
- The other fields will be populated by the database.
-
- This is more efficient, and also means that it will be safe to
- remove database columns without getting subsequent errors in running
- app processes (so long as the code in those processes doesn't
- contain any references to the removed column).
-
- The `partial_updates` configuration option is now renamed to
- `partial_writes` to reflect the fact that it now impacts both inserts
- and updates.
-
- *Jon Leighton*
-
-* Allow before and after validations to take an array of lifecycle events
-
- *John Foley*
-
-* Support for specifying transaction isolation level
-
- If your database supports setting the isolation level for a transaction, you can set
- it like so:
-
- Post.transaction(isolation: :serializable) do
- # ...
- end
-
- Valid isolation levels are:
-
- * `:read_uncommitted`
- * `:read_committed`
- * `:repeatable_read`
- * `:serializable`
-
- You should consult the documentation for your database to understand the
- semantics of these different levels:
-
- * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
- * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
-
- An `ActiveRecord::TransactionIsolationError` will be raised if:
-
- * The adapter does not support setting the isolation level
- * You are joining an existing open transaction
- * You are creating a nested (savepoint) transaction
-
- The mysql, mysql2 and postgresql adapters support setting the transaction
- isolation level. However, support is disabled for mysql versions below 5,
- because they are affected by a bug (http://bugs.mysql.com/bug.php?id=39170)
- which means the isolation level gets persisted outside the transaction.
-
- *Jon Leighton*
-
-* `ActiveModel::ForbiddenAttributesProtection` is included by default
- in Active Record models. Check the docs of `ActiveModel::ForbiddenAttributesProtection`
- for more details.
-
- *Guillermo Iguaran*
-
-* Remove integration between Active Record and
- `ActiveModel::MassAssignmentSecurity`, `protected_attributes` gem
- should be added to use `attr_accessible`/`attr_protected`. Mass
- assignment options has been removed from all the AR methods that
- used it (ex. `AR::Base.new`, `AR::Base.create`, `AR::Base#update_attributes`, etc).
-
- *Guillermo Iguaran*
-
-* Fix the return of querying with an empty hash.
- Fixes #6971.
-
- User.where(token: {})
-
- Before:
-
- #=> SELECT * FROM users;
-
- After:
-
- #=> SELECT * FROM users WHERE 1=0;
-
- *Damien Mathieu*
-
-* Fix creation of through association models when using `collection=[]`
- on a `has_many :through` association from an unsaved model.
- Fixes #7661.
-
- *Ernie Miller*
-
-* Explain only normal CRUD sql (select / update / insert / delete).
- Fix problem that explains unexplainable sql.
- Closes #7544 #6458.
-
- *kennyj*
-
-* You can now override the generated accessor methods for stored attributes
- and reuse the original behavior with `read_store_attribute` and `write_store_attribute`,
- which are counterparts to `read_attribute` and `write_attribute`.
-
- *Matt Jones*
-
-* Accept `belongs_to` (including polymorphic) association keys in queries.
-
- The following queries are now equivalent:
-
- Post.where(author: author)
- Post.where(author_id: author)
-
- PriceEstimate.where(estimate_of: treasure)
- PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
-
- *Peter Brown*
-
-* Use native `mysqldump` command instead of `structure_dump` method
- when dumping the database structure to a sql file. Fixes #5547.
-
- *kennyj*
-
-* PostgreSQL inet and cidr types are converted to `IPAddr` objects.
-
- *Dan McClain*
-
-* PostgreSQL array type support. Any datatype can be used to create an
- array column, with full migration and schema dumper support.
-
- To declare an array column, use the following syntax:
-
- create_table :table_with_arrays do |t|
- t.integer :int_array, array: true
- # integer[]
- t.integer :int_array, array: true, length: 2
- # smallint[]
- t.string :string_array, array: true, length: 30
- # char varying(30)[]
- end
-
- This respects any other migration detail (limits, defaults, etc).
- Active Record will serialize and deserialize the array columns on
- their way to and from the database.
-
- One thing to note: PostgreSQL does not enforce any limits on the
- number of elements, and any array can be multi-dimensional. Any
- array that is multi-dimensional must be rectangular (each sub array
- must have the same number of elements as its siblings).
-
- If the `pg_array_parser` gem is available, it will be used when
- parsing PostgreSQL's array representation.
-
- *Dan McClain*
-
-* Attribute predicate methods, such as `article.title?`, will now raise
- `ActiveModel::MissingAttributeError` if the attribute being queried for
- truthiness was not read from the database, instead of just returning `false`.
-
- *Ernie Miller*
-
-* `ActiveRecord::SchemaDumper` uses Ruby 1.9 style hash, which means that the
- schema.rb file will be generated using this new syntax from now on.
-
- *Konstantin Shabanov*
-
-* Map interval with precision to string datatype in PostgreSQL. Fixes #7518.
-
- *Yves Senn*
-
-* Fix eagerly loading associations without primary keys. Fixes #4976.
-
- *Kelley Reynolds*
-
-* Rails now raise an exception when you're trying to run a migration that has an invalid
- file name. Only lower case letters, numbers, and '_' are allowed in migration's file name.
- Please see #7419 for more details.
-
- *Jan Bernacki*
-
-* Fix bug when calling `store_accessor` multiple times.
- Fixes #7532.
-
- *Matt Jones*
-
-* Fix store attributes that show the changes incorrectly.
- Fixes #7532.
-
- *Matt Jones*
-
-* Fix `ActiveRecord::Relation#pluck` when columns or tables are reserved words.
-
- *Ian Lesperance*
-
-* Allow JSON columns to be created in PostgreSQL and properly encoded/decoded.
- to/from database.
-
- *Dickson S. Guedes*
-
-* Fix time column type casting for invalid time string values to correctly return `nil`.
-
- *Adam Meehan*
-
-* Allow to pass Symbol or Proc into `:limit` option of #accepts_nested_attributes_for.
-
- *Mikhail Dieterle*
-
-* ActiveRecord::SessionStore has been extracted from Active Record as `activerecord-session_store`
- gem. Please read the `README.md` file on the gem for the usage.
-
- *Prem Sichanugrist*
-
-* Fix `reset_counters` when there are multiple `belongs_to` association with the
- same foreign key and one of them have a counter cache.
- Fixes #5200.
-
- *Dave Desrochers*
-
-* `serialized_attributes` and `_attr_readonly` become class method only. Instance reader methods are deprecated.
-
- *kennyj*
-
-* Round usec when comparing timestamp attributes in the dirty tracking.
- Fixes #6975.
-
- *kennyj*
-
-* Use inversed parent for first and last child of `has_many` association.
-
- *Ravil Bayramgalin*
-
-* Fix `Column.microseconds` and `Column.fast_string_to_time` to avoid converting
- timestamp seconds to a float, since it occasionally results in inaccuracies
- with microsecond-precision times. Fixes #7352.
-
- *Ari Pollak*
-
-* Fix AR#dup to nullify the validation errors in the dup'ed object. Previously the original
- and the dup'ed object shared the same errors.
-
- *Christian Seiler*
-
-* Raise `ArgumentError` if list of attributes to change is empty in `update_all`.
-
- *Roman Shatsov*
-
-* Fix AR#create to return an unsaved record when AR::RecordInvalid is
- raised. Fixes #3217.
-
- *Dave Yeu*
-
-* Fixed table name prefix that is generated in engines for namespaced models.
-
- *Wojciech Wnętrzak*
-
-* Make sure `:environment` task is executed before `db:schema:load` or `db:structure:load`.
- Fixes #4772.
-
- *Seamus Abshere*
-
-* Allow Relation#merge to take a proc.
-
- This was requested by DHH to allow creating of one's own custom
- association macros.
-
- For example:
-
- module Commentable
- def has_many_comments(extra)
- has_many :comments, -> { where(:foo).merge(extra) }
+ def change
+ change_table :users do |t|
+ t.remove_index [:name, :email]
end
end
- class Post < ActiveRecord::Base
- extend Commentable
- has_many_comments -> { where(:bar) }
- end
-
- *Jon Leighton*
-
-* Add CollectionProxy#scope.
-
- This can be used to get a Relation from an association.
-
- Previously we had a #scoped method, but we're deprecating that for
- AR::Base, so it doesn't make sense to have it here.
-
- This was requested by DHH, to facilitate code like this:
-
- Project.scope.order('created_at DESC').page(current_page).tagged_with(@tag).limit(5).scoping do
- @topics = @project.topics.scope
- @todolists = @project.todolists.scope
- @attachments = @project.attachments.scope
- @documents = @project.documents.scope
- end
-
- *Jon Leighton*
-
-* Add `Relation#load`.
-
- This method explicitly loads the records and then returns `self`.
-
- Rather than deciding between "do I want an array or a relation?",
- most people are actually asking themselves "do I want to eager load
- or lazy load?" Therefore, this method provides a way to explicitly
- eager-load without having to switch from a `Relation` to an array.
-
- Example:
-
- @posts = Post.where(published: true).load
-
- *Jon Leighton*
-
-* `Relation#order`: make new order prepend old one.
-
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY created_at desc, name asc
-
- This also affects order defined in `default_scope` or any kind of associations.
-
- *Bogdan Gusiev*
-
-* `Model.all` now returns an `ActiveRecord::Relation`, rather than an
- array of records. Use `Relation#to_a` if you really want an array.
-
- In some specific cases, this may cause breakage when upgrading.
- However in most cases the `ActiveRecord::Relation` will just act as a
- lazy-loaded array and there will be no problems.
-
- Note that calling `Model.all` with options (e.g.
- `Model.all(conditions: '...')` was already deprecated, but it will
- still return an array in order to make the transition easier.
-
- `Model.scoped` is deprecated in favour of `Model.all`.
-
- `Relation#all` still returns an array, but is deprecated (since it
- would serve no purpose if we made it return a `Relation`).
-
- *Jon Leighton*
-
-* `:finder_sql` and `:counter_sql` options on collection associations
- are deprecated. Please transition to using scopes.
-
- *Jon Leighton*
-
-* `:insert_sql` and `:delete_sql` options on `has_and_belongs_to_many`
- associations are deprecated. Please transition to using `has_many
- :through`.
-
- *Jon Leighton*
-
-* Added `#update_columns` method which updates the attributes from
- the passed-in hash without calling save, hence skipping validations and
- callbacks. `ActiveRecordError` will be raised when called on new objects
- or when at least one of the attributes is marked as read only.
-
- post.attributes # => {"id"=>2, "title"=>"My title", "body"=>"My content", "author"=>"Peter"}
- post.update_columns(title: 'New title', author: 'Sebastian') # => true
- post.attributes # => {"id"=>2, "title"=>"New title", "body"=>"My content", "author"=>"Sebastian"}
-
- *Sebastian Martinez + Rafael Mendonça França*
-
-* The migration generator now creates a join table with (commented) indexes every time
- the migration name contains the word `join_table`:
-
- rails g migration create_join_table_for_artists_and_musics artist_id:index music_id
-
- *Aleksey Magusev*
-
-* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to`
- and `remove_belongs_to` are acceptable. References are reversible.
-
- Examples:
-
- # Create a user_id column
- add_reference(:products, :user)
- # Create a supplier_id, supplier_type columns and appropriate index
- add_reference(:products, :supplier, polymorphic: true, index: true)
- # Remove polymorphic reference
- remove_reference(:products, :supplier, polymorphic: true)
-
- *Aleksey Magusev*
+ Fix was to check if the options is a Hash before operating on it.
-* Add `:default` and `:null` options to `column_exists?`.
+ Fixes #10419.
- column_exists?(:testings, :taggable_id, :integer, null: false)
- column_exists?(:testings, :taggable_type, :string, default: 'Photo')
+ *Neeraj Singh*
- *Aleksey Magusev*
+* Do not overwrite manually built records during one-to-one nested attribute assignment
-* `ActiveRecord::Relation#inspect` now makes it clear that you are
- dealing with a `Relation` object rather than an array:.
+ For one-to-one nested associations, if you build the new (in-memory)
+ child object yourself before assignment, then the NestedAttributes
+ module will not overwrite it, e.g.:
- User.where(age: 30).inspect
- # => <ActiveRecord::Relation [#<User ...>, #<User ...>, ...]>
+ class Member < ActiveRecord::Base
+ has_one :avatar
+ accepts_nested_attributes_for :avatar
- User.where(age: 30).to_a.inspect
- # => [#<User ...>, #<User ...>]
-
- The number of records displayed will be limited to 10.
-
- *Brian Cardarella, Jon Leighton & Damien Mathieu*
-
-* Add `collation` and `ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later.
- Example:
-
- development:
- adapter: postgresql
- host: localhost
- database: rails_development
- username: foo
- password: bar
- encoding: UTF8
- collation: ja_JP.UTF8
- ctype: ja_JP.UTF8
-
- *kennyj*
-
-* Changed `validates_presence_of` on an association so that children objects
- do not validate as being present if they are marked for destruction. This
- prevents you from saving the parent successfully and thus putting the parent
- in an invalid state.
-
- *Nick Monje & Brent Wheeldon*
-
-* `FinderMethods#exists?` now returns `false` with the `false` argument.
-
- *Egor Lynko*
-
-* Added support for specifying the precision of a timestamp in the PostgreSQL
- adapter. So, instead of having to incorrectly specify the precision using the
- `:limit` option, you may use `:precision`, as intended. For example, in a migration:
-
- def change
- create_table :foobars do |t|
- t.timestamps precision: 0
+ def avatar
+ super || build_avatar(width: 200)
end
end
- *Tony Schneider*
-
-* Allow `ActiveRecord::Relation#pluck` to accept multiple columns. Returns an
- array of arrays containing the typecasted values:
-
- Person.pluck(:id, :name)
- # SELECT people.id, people.name FROM people
- # [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
-
- *Jeroen van Ingen & Carlos Antonio da Silva*
-
-* Improve the derivation of HABTM join table name to take account of nesting.
- It now takes the table names of the two models, sorts them lexically and
- then joins them, stripping any common prefix from the second table name.
-
- Some examples:
-
- Top level models (Category <=> Product)
- Old: categories_products
- New: categories_products
-
- Top level models with a global table_name_prefix (Category <=> Product)
- Old: site_categories_products
- New: site_categories_products
-
- Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product)
- Old: categories_products
- New: categories_products
+ member = Member.new
+ member.avatar_attributes = {icon: 'sad'}
+ member.avatar.width # => 200
- Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product)
- Old: categories_products
- New: admin_categories_products
-
- Nested models in a parent model (Catalog::Category <=> Catalog::Product)
- Old: categories_products
- New: catalog_categories_products
-
- Nested models in different parent models (Catalog::Category <=> Content::Page)
- Old: categories_pages
- New: catalog_categories_content_pages
-
- *Andrew White*
-
-* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of
- this is to move when the exceptions are raised from the point of declaration
- to when the association is built. This is consistent with other association
- validity checks.
-
- *Andrew White*
-
-* Added `stored_attributes` hash which contains the attributes stored using
- `ActiveRecord::Store`. This allows you to retrieve the list of attributes
- you've defined.
-
- class User < ActiveRecord::Base
- store :settings, accessors: [:color, :homepage]
- end
-
- User.stored_attributes[:settings] # [:color, :homepage]
-
- *Joost Baaij & Carlos Antonio da Silva*
-
-* PostgreSQL default log level is now 'warning', to bypass the noisy notice
- messages. You can change the log level using the `min_messages` option
- available in your config/database.yml.
-
- *kennyj*
-
-* Add uuid datatype support to PostgreSQL adapter.
-
- *Konstantin Shabanov*
+ *Olek Janiszewski*
-* Added `ActiveRecord::Migration.check_pending!` that raises an error if
- migrations are pending.
+* fixes bug introduced by #3329. Now, when autosaving associations,
+ deletions happen before inserts and saves. This prevents a 'duplicate
+ unique value' database error that would occur if a record being created had
+ the same value on a unique indexed field as that of a record being destroyed.
- *Richard Schneeman*
+ *Johnny Holton*
-* Added `#destroy!` which acts like `#destroy` but will raise an
- `ActiveRecord::RecordNotDestroyed` exception instead of returning `false`.
+* Handle aliased attributes in ActiveRecord::Relation.
- *Marc-André Lafortune*
+ When using symbol keys, ActiveRecord will now translate aliased attribute names to the actual column name used in the database:
-* Added support to `CollectionAssociation#delete` for passing `fixnum`
- or `string` values as record ids. This finds the records responding
- to the `id` and executes delete on them.
+ With the model
- class Person < ActiveRecord::Base
- has_many :pets
+ class Topic
+ alias_attribute :heading, :title
end
- person.pets.delete("1") # => [#<Pet id: 1>]
- person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
-
- *Francesco Rodriguez*
-
-* 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
- `find_or_create_by(...)` or where(...).first_or_create`
- * `find_or_create_by_...!` can be rewritten using
- `find_or_create_by!(...) or `where(...).first_or_create!`
-
- The implementation of the deprecated dynamic finders has been moved
- to the `activerecord-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:
+ The call
- Post.find(:all, conditions: { comments_count: 10 }, limit: 5)
+ Topic.where(heading: 'The First Topic')
- Should be rewritten in the new style which has existed since Rails 3:
+ should yield the same result as
- Post.where(comments_count: 10).limit(5)
+ Topic.where(title: 'The First Topic')
- Note that as an interim step, it is possible to rewrite the above as:
+ This also applies to ActiveRecord::Relation::Calculations calls such as `Model.sum(:aliased)` and `Model.pluck(:aliased)`.
- Post.all.merge(where: { comments_count: 10 }, limit: 5)
+ This will not work with SQL fragment strings like `Model.sum('DISTINCT aliased')`.
- This could save you a lot of work if there is a lot of old-style
- finder usage in your application.
+ *Godfrey Chan*
- `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:
+* Mute `psql` output when running rake db:schema:load.
- * `:conditions` becomes `:where`.
- * `:include` becomes `:includes`.
+ *Godfrey Chan*
- The code to implement the deprecated features has been moved out to the
- `activerecord-deprecated_finders` gem. This gem is a dependency of Active
- Record in Rails 4.0, so the interface works out of the box. It will no
- longer be a dependency from Rails 4.1 (you'll need to add it to the
- `Gemfile` in 4.1), and will be maintained until Rails 5.0.
+* Trigger a save on `has_one association=(associate)` when the associate contents have changed.
- *Jon Leighton*
+ Fix #8856.
-* It's not possible anymore to destroy a model marked as read only.
+ *Chris Thompson*
- *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
- different 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 `#find_by` and `#find_by!` to mirror the functionality
- provided by dynamic finders in a way that allows dynamic input more
- easily:
-
- Post.find_by name: 'Spartacus', rating: 4
- Post.find_by "published_at < ?", 2.weeks.ago
- Post.find_by! name: 'Spartacus'
-
- *Jon Leighton*
-
-* Added ActiveRecord::Base#slice to return a hash of the given methods with
- their names as keys and returned values as values.
-
- *Guillermo Iguaran*
-
-* Deprecate eager-evaluated scopes.
-
- Don't use this:
-
- scope :red, where(color: 'red')
- default_scope where(color: 'red')
-
- Use this:
-
- scope :red, -> { where(color: 'red') }
- default_scope { where(color: 'red') }
-
- The former has numerous issues. It is a common newbie gotcha to do
- the following:
-
- scope :recent, where(published_at: Time.now - 2.weeks)
-
- Or a more subtle variant:
-
- scope :recent, -> { where(published_at: Time.now - 2.weeks) }
- scope :recent_red, recent.where(color: 'red')
-
- Eager scopes are also very complex to implement within Active
- Record, and there are still bugs. For example, the following does
- not do what you expect:
-
- scope :remove_conditions, except(:where)
- where(...).remove_conditions # => still has conditions
-
- *Jon Leighton*
-
-* Remove IdentityMap
-
- IdentityMap has never graduated to be an "enabled-by-default" feature, due
- to some inconsistencies with associations, as described in this commit:
-
- https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6
-
- Hence the removal from the codebase, until such issues are fixed.
-
- *Carlos Antonio da Silva*
-
-* Added the schema cache dump feature.
-
- `Schema cache dump` feature was implemented. This feature can dump/load internal state of `SchemaCache` instance
- because we want to boot rails more quickly when we have many models.
-
- Usage notes:
-
- 1) execute rake task.
- RAILS_ENV=production bundle exec rake db:schema:cache:dump
- => generate db/schema_cache.dump
-
- 2) add config.active_record.use_schema_cache_dump = true in config/production.rb. BTW, true is default.
-
- 3) boot rails.
- RAILS_ENV=production bundle exec rails server
- => use db/schema_cache.dump
-
- 4) If you remove clear dumped cache, execute rake task.
- RAILS_ENV=production bundle exec rake db:schema:cache:clear
- => remove db/schema_cache.dump
+* Abort a rake task when missing db/structure.sql like `db:schema:load` task.
*kennyj*
-* Added support for partial indices to PostgreSQL adapter.
-
- The `add_index` method now supports a `where` option that receives a
- string with the partial index criteria.
-
- add_index(:accounts, :code, where: 'active')
-
- Generates
-
- CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
-
- *Marcelo Silveira*
-
-* Implemented ActiveRecord::Relation#none method.
-
- The `none` method returns a chainable relation with zero records
- (an instance of the NullRelation class).
-
- Any subsequent condition chained to the returned relation will continue
- generating an empty relation and will not fire any query to the database.
-
- *Juanjo Bazán*
-
-* Added the `ActiveRecord::NullRelation` class implementing the null
- object pattern for the Relation class.
-
- *Juanjo Bazán*
-
-* Added new `dependent: :restrict_with_error` option. This will add
- an error to the model, rather than raising an exception.
-
- The `:restrict` option is renamed to `:restrict_with_exception` to
- make this distinction explicit.
-
- *Manoj Kumar & Jon Leighton*
-
-* Added `create_join_table` migration helper to create HABTM join tables.
-
- create_join_table :products, :categories
- # =>
- # create_table :categories_products, id: false do |td|
- # td.integer :product_id, null: false
- # td.integer :category_id, null: false
- # end
-
- *Rafael Mendonça França*
-
-* The primary key is always initialized in the @attributes hash to `nil` (unless
- another value has been specified).
-
- *Aaron Paterson*
-
-* In previous releases, the following would generate a single query with
- an `OUTER JOIN comments`, rather than two separate queries:
-
- Post.includes(:comments)
- .where("comments.name = 'foo'")
-
- This behaviour relies on matching SQL string, which is an inherently
- flawed idea unless we write an SQL parser, which we do not wish to
- do.
-
- Therefore, it is now deprecated.
-
- To avoid deprecation warnings and for future compatibility, you must
- explicitly state which tables you reference, when using SQL snippets:
-
- Post.includes(:comments)
- .where("comments.name = 'foo'")
- .references(:comments)
-
- Note that you do not need to explicitly specify references in the
- following cases, as they can be automatically inferred:
-
- Post.includes(:comments).where(comments: { name: 'foo' })
- Post.includes(:comments).where('comments.name' => 'foo')
- Post.includes(:comments).order('comments.name')
-
- You do not need to worry about this unless you are doing eager
- loading. Basically, don't worry unless you see a deprecation warning
- or (in future releases) an SQL error due to a missing JOIN.
-
- *Jon Leighton*
-
-* Support for the `schema_info` table has been dropped. Please
- switch to `schema_migrations`.
-
- *Aaron Patterson*
-
-* Connections *must* be closed at the end of a thread. If not, your
- connection pool can fill and an exception will be raised.
-
- *Aaron Patterson*
-
-* PostgreSQL hstore records can be created.
-
- *Aaron Patterson*
-
-* PostgreSQL hstore types are automatically deserialized from the database.
-
- *Aaron Patterson*
+* rake:db:test:prepare falls back to original environment after execution.
+ *Slava Markevich*
-Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index ed1e171d58..e04abe9b37 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -175,7 +175,7 @@ by relying on a number of conventions that make it easy for Active Record to inf
complex relations and structures from a minimal amount of explicit direction.
Convention over Configuration:
-* No XML-files!
+* No XML files!
* Lots of reflection and run-time extension
* Magic is not inherently a bad word
@@ -190,7 +190,7 @@ The latest version of Active Record can be installed with RubyGems:
% [sudo] gem install activerecord
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/activerecord
@@ -204,7 +204,7 @@ Active Record is released under the MIT license:
== Support
-API documentation is at
+API documentation is at:
* http://api.rubyonrails.org
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index 2f3d516c43..c3ee34da55 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -1,31 +1,43 @@
== Setup
-If you don't have the environment set make sure to read
-
- http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#testing-active-record
+If you don't have an environment for running tests, read
+http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#setting-up-a-development-environment
== Running the Tests
-You can run a particular test file from the command line, e.g.
+To run a specific test:
+
+ $ ruby -Itest test/cases/base_test.rb -n method_name
+
+To run a set of tests:
$ ruby -Itest test/cases/base_test.rb
-To run a specific test:
+You can also run tests that depend upon a specific database backend. For
+example:
- $ ruby -Itest test/cases/base_test.rb -n test_something_works
+ $ bundle exec rake test_sqlite3
-You can run with a database other than the default you set in test/config.yml, using the ARCONN
-environment variable:
+Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
- $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb
+ $ bundle exec rake test_mysql
+ $ bundle exec rake test_mysql2
+ $ bundle exec rake test_postgresql
+ $ bundle exec rake test_sqlite3
-You can run all the tests for a given database via rake:
+There should be tests available for each database backend listed in the {Config
+File}[rdoc-label:label-Config+File]. (the exact set of available tests is
+defined in +Rakefile+)
- $ rake test_mysql
+== Config File
-The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql.
+If +test/config.yml+ is present, it's parameters are obeyed. Otherwise, the
+parameters in +test/config.example.yml+ are obeyed.
-== Custom Config file
+You can override the +connections:+ parameter in either file using the +ARCONN+
+(Active Record CONNection) environment variable:
+
+ $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb
-By default, the config file is expected to be at the path test/config.yml. You can specify a
-custom location with the ARCONFIG environment variable.
+You can specify a custom location for the config file using the +ARCONFIG+
+environment variable.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 0523314128..136bb1dfbc 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rake/packagetask'
require 'rubygems/package_task'
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
@@ -125,8 +124,6 @@ namespace :postgresql do
%w(arunit arunit2).each do |db|
if version < "9.1.0"
puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
- else
- %x( psql #{config[db]['database']} -c "CREATE EXTENSION hstore;" )
end
end
end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 3e3475f709..337106cb92 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,6 +24,6 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '~> 4.0.0.beta2'
- s.add_dependency 'activerecord-deprecated_finders', '~> 0.0.3'
+ s.add_dependency 'arel', '~> 4.0.0'
+ s.add_dependency 'activerecord-deprecated_finders', '~> 1.0.2'
end
diff --git a/activerecord/examples/associations.png b/activerecord/examples/associations.png
deleted file mode 100644
index 661c7a8bbc..0000000000
--- a/activerecord/examples/associations.png
+++ /dev/null
Binary files differ
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index c33f03f13f..994cacb4f9 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -35,8 +35,8 @@ module ActiveRecord
autoload :Base
autoload :Callbacks
autoload :Core
- autoload :CounterCache
autoload :ConnectionHandling
+ autoload :CounterCache
autoload :DynamicMatchers
autoload :Explain
autoload :Inheritance
@@ -50,12 +50,14 @@ module ActiveRecord
autoload :Querying
autoload :ReadonlyAttributes
autoload :Reflection
+ autoload :RuntimeRegistry
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
+ autoload :StatementCache
autoload :Store
autoload :Timestamp
autoload :Transactions
@@ -69,11 +71,12 @@ module ActiveRecord
autoload :Aggregations
autoload :Associations
- autoload :AttributeMethods
autoload :AttributeAssignment
+ autoload :AttributeMethods
autoload :AutosaveAssociation
autoload :Relation
+ autoload :AssociationRelation
autoload :NullRelation
autoload_under 'relation' do
@@ -143,6 +146,10 @@ module ActiveRecord
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
autoload :PostgreSQLDatabaseTasks,
'active_record/tasks/postgresql_database_tasks'
+
+ autoload :FirebirdDatabaseTasks, 'active_record/tasks/firebird_database_tasks'
+ autoload :SqlserverDatabaseTasks, 'active_record/tasks/sqlserver_database_tasks'
+ autoload :OracleDatabaseTasks, 'active_record/tasks/oracle_database_tasks'
end
autoload :TestCase
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
new file mode 100644
index 0000000000..20516bba0c
--- /dev/null
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -0,0 +1,18 @@
+module ActiveRecord
+ class AssociationRelation < Relation
+ def initialize(klass, table, association)
+ super(klass, table)
+ @association = association
+ end
+
+ def proxy_association
+ @association
+ end
+
+ private
+
+ def exec_queries
+ super.each { |r| @association.set_inverse_instance r }
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index eb08f72286..6fd4f3042c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -172,7 +172,7 @@ module ActiveRecord
@association_cache[name] = association
end
- # Associations are a set of macro-like class methods for tying objects together through
+ # \Associations are a set of macro-like class methods for tying objects together through
# foreign keys. They express relationships like "Project has one Project Manager"
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
# class which are specialized according to the collection or association symbol and the
@@ -365,11 +365,11 @@ module ActiveRecord
# there is some special behavior you should be aware of, mostly involving the saving of
# associated objects.
#
- # You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
+ # You can set the <tt>:autosave</tt> option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
# to +true+ will _always_ save the members, whereas setting it to +false+ will
- # _never_ save the members. More details about :autosave option is available at
- # autosave_association.rb .
+ # _never_ save the members. More details about <tt>:autosave</tt> option is available at
+ # AutosaveAssociation.
#
# === One-to-one associations
#
@@ -402,7 +402,7 @@ module ActiveRecord
#
# == Customizing the query
#
- # Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
+ # \Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
# to customize them. For example, to add a condition:
#
# class Blog < ActiveRecord::Base
@@ -568,6 +568,8 @@ module ActiveRecord
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
# @group.avatars.delete(@group.avatars.last) # so would this
#
+ # == Setting Inverses
+ #
# If you are using a +belongs_to+ on the join model, it is a good idea to set the
# <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
# works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
@@ -584,7 +586,27 @@ module ActiveRecord
# belongs_to :tag, inverse_of: :taggings
# end
#
- # == Nested Associations
+ # If you do not set the <tt>:inverse_of</tt> record, the association will
+ # do its best to match itself up with the correct inverse. Automatic
+ # inverse detection only works on <tt>has_many</tt>, <tt>has_one</tt>, and
+ # <tt>belongs_to</tt> associations.
+ #
+ # Extra options on the associations, as defined in the
+ # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
+ # also prevent the association's inverse from being found automatically.
+ #
+ # The automatic guessing of the inverse association uses a heuristic based
+ # on the name of the class, so it may not work for all associations,
+ # especially the ones with non-standard names.
+ #
+ # You can turn off the automatic detection of inverse associations by setting
+ # the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
+ #
+ # class Taggable < ActiveRecord::Base
+ # belongs_to :tag, inverse_of: false
+ # end
+ #
+ # == Nested \Associations
#
# You can actually specify *any* association with the <tt>:through</tt> option, including an
# association which has a <tt>:through</tt> option itself. For example:
@@ -627,7 +649,7 @@ module ActiveRecord
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
#
- # == Polymorphic Associations
+ # == Polymorphic \Associations
#
# Polymorphic associations on models are not restricted on what types of models they
# can be associated with. Rather, they specify an interface that a +has_many+ association
@@ -789,7 +811,7 @@ module ActiveRecord
# For example if all the addressables are either of class Person or Company then a total
# of 3 queries will be executed. The list of addressable types to load is determined on
# the back of the addresses loaded. This is not supported if Active Record has to fallback
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
+ # to the previous implementation of eager loading and will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>.
# The reason is that the parent model's type is a column value so its corresponding table
# name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
@@ -988,7 +1010,7 @@ module ActiveRecord
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
#
- # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt>
+ # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
# to be removed from the database.
#
@@ -1024,7 +1046,7 @@ module ActiveRecord
# An empty array is returned if none are found.
# [collection<<(object, ...)]
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
- # Note that this operation instantly fires update sql without waiting for the save or update call on the
+ # Note that this operation instantly fires update SQL without waiting for the save or update call on the
# parent object, unless the parent object is a new record.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
@@ -1060,10 +1082,10 @@ module ActiveRecord
# [collection.size]
# Returns the number of associated objects.
# [collection.find(...)]
- # Finds an associated object according to the same rules as ActiveRecord::Base.find.
+ # Finds an associated object according to the same rules as <tt>ActiveRecord::Base.find</tt>.
# [collection.exists?(...)]
# Checks whether an associated object with the given conditions exists.
- # Uses the same rules as ActiveRecord::Base.exists?.
+ # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
# [collection.build(attributes = {}, ...)]
# Returns one or more new objects of the collection type that have been instantiated
# with +attributes+ and linked to this object through a foreign key, but have not yet
@@ -1073,13 +1095,16 @@ module ActiveRecord
# with +attributes+, linked to this object through a foreign key, and that has already
# been saved (if it passed the validation). *Note*: This only works if the base model
# already exists in the DB, not if it is a new (unsaved) record!
+ # [collection.create!(attributes = {})]
+ # Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
+ # if the record is invalid.
#
# (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
#
# === Example
#
- # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
+ # A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
# * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
# * <tt>Firm#clients<<</tt>
# * <tt>Firm#clients.delete</tt>
@@ -1094,6 +1119,7 @@ module ActiveRecord
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
+ # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
# The declaration can also include an options hash to specialize the behavior of the association.
#
# === Options
@@ -1112,14 +1138,14 @@ module ActiveRecord
# Controls what happens to the associated objects when
# their owner is destroyed. Note that these are implemented as
# callbacks, and Rails executes callbacks in order. Therefore, other
- # similar callbacks may affect the :dependent behavior, and the
- # :dependent behavior may affect other callbacks.
+ # similar callbacks may affect the <tt>:dependent</tt> behavior, and the
+ # <tt>:dependent</tt> behavior may affect other callbacks.
#
- # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
- # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
+ # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
- # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
@@ -1159,8 +1185,8 @@ module ActiveRecord
# If true, always save the associated objects or destroy them if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records. This option is implemented as a
- # before_save callback. Because callbacks are run in the order they are defined, associated objects
- # may need to be explicitly saved in any user-defined before_save callbacks.
+ # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects
+ # may need to be explicitly saved in any user-defined +before_save+ callbacks.
#
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
@@ -1185,7 +1211,7 @@ module ActiveRecord
# Specifies a one-to-one association with another class. This method should only be used
# if the other class contains the foreign key. If the current class contains the foreign key,
# then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
- # on when to use has_one and when to use belongs_to.
+ # on when to use +has_one+ and when to use +belongs_to+.
#
# The following methods for retrieval and query of a single associated object will be added:
#
@@ -1353,7 +1379,7 @@ module ActiveRecord
# 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
- # <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
+ # <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> 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
# option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
@@ -1435,7 +1461,7 @@ module ActiveRecord
# [collection<<(object, ...)]
# Adds one or more objects to the collection by creating associations in the join table
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
- # Note that this operation instantly fires update sql without waiting for the save or update call on the
+ # Note that this operation instantly fires update SQL without waiting for the save or update call on the
# parent object, unless the parent object is a new record.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by removing their associations from the join table.
@@ -1458,10 +1484,10 @@ module ActiveRecord
# [collection.find(id)]
# Finds an associated object responding to the +id+ and that
# meets the condition that it has to be associated with this object.
- # Uses the same rules as ActiveRecord::Base.find.
+ # Uses the same rules as <tt>ActiveRecord::Base.find</tt>.
# [collection.exists?(...)]
# Checks whether an associated object with the given conditions exists.
- # Uses the same rules as ActiveRecord::Base.exists?.
+ # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
# [collection.build(attributes = {})]
# Returns a new object of the collection type that has been instantiated
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 4c4b0f08e5..608a6af16c 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -30,7 +30,7 @@ module ActiveRecord
reset_scope
end
- # Returns the name of the table of the related class:
+ # Returns the name of the table of the associated class:
#
# post.comments.aliased_table_name # => "comments"
#
@@ -92,7 +92,7 @@ module ActiveRecord
# The scope for this association.
#
# Note that the association_scope is merged into the target_scope only when the
- # scoped method is called. This is because at that point the call may be surrounded
+ # scope method is called. This is because at that point the call may be surrounded
# by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
# actually gets built.
def association_scope
@@ -113,7 +113,7 @@ module ActiveRecord
end
end
- # This class of the target. belongs_to polymorphic overrides this to look at the
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
# polymorphic_type field on the owner.
def klass
reflection.klass
@@ -122,7 +122,11 @@ module ActiveRecord
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
# through association's scope)
def target_scope
- klass.all
+ all = klass.all
+ scope = AssociationRelation.new(klass, klass.arel_table, self)
+ scope.merge! all
+ scope.default_scoped = all.default_scoped?
+ scope
end
# Loads the \target if needed and returns it.
@@ -164,6 +168,13 @@ module ActiveRecord
@reflection = @owner.class.reflect_on_association(reflection_name)
end
+ def initialize_attributes(record) #:nodoc:
+ skip_assign = [reflection.foreign_key, reflection.type].compact
+ attributes = create_scope.except(*(record.changed - skip_assign))
+ record.assign_attributes(attributes)
+ set_inverse_instance(record)
+ end
+
private
def find_target?
@@ -217,7 +228,8 @@ module ActiveRecord
reflection.inverse_of
end
- # Is this association invertible? Can be redefined by subclasses.
+ # Returns true if inverse association on the given record needs to be set.
+ # This method is redefined by subclasses.
def invertible_for?(record)
inverse_reflection_for(record)
end
@@ -232,9 +244,7 @@ module ActiveRecord
def build_record(attributes)
reflection.build_association(attributes) do |record|
- skip_assign = [reflection.foreign_key, reflection.type].compact
- attributes = create_scope.except(*(record.changed - skip_assign))
- record.assign_attributes(attributes)
+ initialize_attributes(record)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index a9525436fb..f1bec5787a 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -96,11 +96,12 @@ module ActiveRecord
item = eval_scope(klass, scope_chain_item)
if scope_chain_item == self.reflection.scope
- scope.merge! item.except(:where, :includes)
+ scope.merge! item.except(:where, :includes, :bind)
end
scope.includes! item.includes_values
scope.where_values += item.where_values
+ scope.order_values |= item.order_values
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 5c37f42794..3254da4677 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,3 +1,15 @@
+# This is the parent Association class which defines the variables
+# used by all associations.
+#
+# The hierarchy is defined as follows:
+# Association
+# - SingularAssociation
+# - BelongsToAssociation
+# - HasOneAssociation
+# - CollectionAssociation
+# - HasManyAssociation
+# - HasAndBelongsToManyAssociation
+
module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
@@ -58,6 +70,13 @@ module ActiveRecord::Associations::Builder
def validate_options
options.assert_valid_keys(valid_options)
end
+
+ # Defines the setter and getter methods for the association
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # end
+ #
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
def define_accessors
define_readers
@@ -92,13 +111,8 @@ module ActiveRecord::Associations::Builder
)
end
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{macro}_dependent_for_#{name}
- association(:#{name}).handle_dependency
- end
- CODE
-
- model.before_destroy "#{macro}_dependent_for_#{name}"
+ n = name
+ model.before_destroy lambda { |o| o.association(n).handle_dependency }
end
def valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 9ac561b997..d4e1a0dda1 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -19,68 +19,116 @@ module ActiveRecord::Associations::Builder
reflection
end
- def add_counter_cache_callbacks(reflection)
- cache_column = reflection.counter_cache_column
- foreign_key = reflection.foreign_key
+ def valid_dependent_options
+ [:destroy, :delete]
+ end
+
+ private
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def belongs_to_counter_cache_after_create_for_#{name}
- record = #{name}
- record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
- @_after_create_counter_called = true
+ def add_counter_cache_methods(mixin)
+ return if mixin.method_defined? :belongs_to_counter_cache_after_create
+
+ mixin.class_eval do
+ def belongs_to_counter_cache_after_create(association, reflection)
+ if record = send(association.name)
+ cache_column = reflection.counter_cache_column
+ record.class.increment_counter(cache_column, record.id)
+ @_after_create_counter_called = true
+ end
end
- def belongs_to_counter_cache_before_destroy_for_#{name}
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
- record = #{name}
- record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
+ def belongs_to_counter_cache_before_destroy(association, reflection)
+ foreign_key = reflection.foreign_key.to_sym
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
+ record = send association.name
+ if record && !self.destroyed?
+ cache_column = reflection.counter_cache_column
+ record.class.decrement_counter(cache_column, record.id)
+ end
end
end
- def belongs_to_counter_cache_after_update_for_#{name}
+ def belongs_to_counter_cache_after_update(association, reflection)
+ foreign_key = reflection.foreign_key
+ cache_column = reflection.counter_cache_column
+
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
- elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize})
- model = #{name.to_s.camelize}
- foreign_key_was = self.#{foreign_key}_was
- foreign_key = self.#{foreign_key}
+ elsif attribute_changed?(foreign_key) && !new_record? && association.constructable?
+ model = reflection.klass
+ foreign_key_was = attribute_was foreign_key
+ foreign_key = attribute foreign_key
if foreign_key && model.respond_to?(:increment_counter)
- model.increment_counter(:#{cache_column}, foreign_key)
+ model.increment_counter(cache_column, foreign_key)
end
if foreign_key_was && model.respond_to?(:decrement_counter)
- model.decrement_counter(:#{cache_column}, foreign_key_was)
+ model.decrement_counter(cache_column, foreign_key_was)
end
end
end
- CODE
+ end
+ end
+
+ def add_counter_cache_callbacks(reflection)
+ cache_column = reflection.counter_cache_column
+ add_counter_cache_methods mixin
+ association = self
+
+ model.after_create lambda { |record|
+ record.belongs_to_counter_cache_after_create(association, reflection)
+ }
+
+ model.before_destroy lambda { |record|
+ record.belongs_to_counter_cache_before_destroy(association, reflection)
+ }
- model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
- model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
- model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
+ model.after_update lambda { |record|
+ record.belongs_to_counter_cache_after_update(association, reflection)
+ }
klass = reflection.class_name.safe_constantize
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
end
- def add_touch_callbacks(reflection)
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def belongs_to_touch_after_save_or_destroy_for_#{name}
- record = #{name}
+ def self.touch_record(o, foreign_key, name, touch) # :nodoc:
+ old_foreign_id = o.attribute_was(foreign_key)
+
+ if old_foreign_id
+ klass = o.association(name).klass
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
- unless record.nil? || record.new_record?
- record.touch #{options[:touch].inspect if options[:touch] != true}
+ if old_record
+ if touch != true
+ old_record.touch touch
+ else
+ old_record.touch
end
end
- CODE
+ end
- model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
- model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
- model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ record = o.send name
+ unless record.nil? || record.new_record?
+ if touch != true
+ record.touch touch
+ else
+ record.touch
+ end
+ end
end
- def valid_dependent_options
- [:destroy, :delete]
+ def add_touch_callbacks(reflection)
+ foreign_key = reflection.foreign_key
+ n = name
+ touch = options[:touch]
+
+ callback = lambda { |record|
+ BelongsTo.touch_record(record, foreign_key, n, touch)
+ }
+
+ model.after_save callback
+ model.after_touch callback
+ model.after_destroy callback
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index fdead16761..9c6690b721 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,3 +1,5 @@
+# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
+
require 'active_record/associations'
module ActiveRecord::Associations::Builder
@@ -66,6 +68,8 @@ module ActiveRecord::Associations::Builder
model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
end
+ # Defines the setter and getter methods for the collection_singular_ids.
+
def define_readers
super
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 6a5830e57f..76e48e66e5 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -1,3 +1,5 @@
+# This class is inherited by the has_one and belongs_to association classes
+
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
def valid_options
@@ -13,6 +15,8 @@ module ActiveRecord::Associations::Builder
define_constructors if constructable?
end
+ # Defines the (build|create)_association methods for belongs_to or has_one association
+
def define_constructors
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def build_#{name}(*args, &block)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2385c90c1a..efd7ecb97c 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -79,8 +79,20 @@ module ActiveRecord
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
else
- if options[:finder_sql] || options[:inverse_of]
+ if options[:finder_sql]
find_by_scan(*args)
+ elsif options[:inverse_of]
+ args = args.flatten
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank?
+
+ result = find_by_scan(*args)
+
+ result_size = Array(result).size
+ if !result || result_size != args.size
+ scope.raise_record_not_found_exception!(args, result_size, args.size)
+ else
+ result
+ end
else
scope.find(*args)
end
@@ -225,11 +237,11 @@ module ActiveRecord
end
end
- # Destroy +records+ and remove them from this association calling
- # +before_remove+ and +after_remove+ callbacks.
+ # Deletes the +records+ and removes them from this association calling
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
#
- # Note that this method will _always_ remove records from the database
- # ignoring the +:dependent+ option.
+ # Note that this method removes records from the database ignoring the
+ # +:dependent+ option.
def destroy(*records)
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index c2add32aa6..e82c195335 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -92,7 +92,7 @@ module ActiveRecord
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
#
# *Second:* You can pass a block so it can be used just like Array#select.
- # This build an array of objects from the database for the scope,
+ # This builds an array of objects from the database for the scope,
# converting them into an array and iterating through them using
# Array#select.
#
@@ -228,6 +228,7 @@ module ActiveRecord
def build(attributes = {}, &block)
@association.build(attributes, &block)
end
+ alias_method :new, :build
# Returns a new object of the collection type that has been instantiated with
# attributes, linked to this object and that has already been saved (if it
@@ -303,7 +304,7 @@ module ActiveRecord
@association.concat(*records)
end
- # Replace this collection with +other_array+. This will perform a diff
+ # Replaces this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
#
# class Person < ActiveRecord::Base
@@ -421,9 +422,9 @@ module ActiveRecord
@association.delete_all
end
- # Deletes the records of the collection directly from the database.
- # This will _always_ remove the records ignoring the +:dependent+
- # option.
+ # Deletes the records of the collection directly from the database
+ # ignoring the +:dependent+ option. It invokes +before_remove+,
+ # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -832,8 +833,6 @@ module ActiveRecord
@association.include?(record)
end
- alias_method :new, :build
-
def proxy_association
@association
end
@@ -848,11 +847,7 @@ module ActiveRecord
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- association = @association
-
- @association.scope.extending! do
- define_method(:proxy_association) { association }
- end
+ @association.scope
end
# :nodoc:
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 29fae809da..cf8a589496 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -115,8 +115,7 @@ module ActiveRecord
if records == :all
scope = self.scope
else
- keys = records.map { |r| r[reflection.association_primary_key] }
- scope = self.scope.where(reflection.association_primary_key => keys)
+ scope = self.scope.where(reflection.klass.primary_key => records)
end
if method == :delete_all
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 98bd010f70..920038a543 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -25,9 +25,8 @@ module ActiveRecord
raise_on_type_mismatch!(record) if record
load_target
- # If target and record are nil, or target is equal to record,
- # we don't need to have transaction.
- if (target || record) && target != record
+ return self.target if !(target || record)
+ if (target != record) || record.changed?
transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index f40368cfeb..5aa17e5fbb 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -5,10 +5,31 @@ module ActiveRecord
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
- attr_reader :join_parts, :reflections, :alias_tracker, :active_record
-
+ attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
+
+ # base is the base class on which operation is taking place.
+ # associations is the list of associations which are joined using hash, symbol or array.
+ # joins is the list of all string join commnads and arel nodes.
+ #
+ # Example :
+ #
+ # class Physician < ActiveRecord::Base
+ # has_many :appointments
+ # has_many :patients, through: :appointments
+ # end
+ #
+ # If I execute `@physician.patients.to_a` then
+ # base #=> Physician
+ # associations #=> []
+ # joins #=> [#<Arel::Nodes::InnerJoin: ...]
+ #
+ # However if I execute `Physician.joins(:appointments).to_a` then
+ # base #=> Physician
+ # associations #=> [:appointments]
+ # joins #=> []
+ #
def initialize(base, associations, joins)
- @active_record = base
+ @base_klass = base
@table_joins = joins
@join_parts = [JoinBase.new(base)]
@associations = {}
@@ -27,13 +48,19 @@ module ActiveRecord
end
def join_associations
- join_parts.last(join_parts.length - 1)
+ join_parts.drop 1
end
def join_base
join_parts.first
end
+ def join_relation(relation)
+ join_associations.inject(relation) do |rel,association|
+ association.join_relation(rel)
+ end
+ end
+
def columns
join_parts.collect { |join_part|
table = join_part.aliased_table
@@ -54,10 +81,12 @@ module ActiveRecord
parent
}.uniq
- remove_duplicate_results!(active_record, records, @associations)
+ remove_duplicate_results!(base_klass, records, @associations)
records
end
+ protected
+
def remove_duplicate_results!(base, records, associations)
case associations
when Symbol, String
@@ -88,8 +117,6 @@ module ActiveRecord
end
end
- protected
-
def cache_joined_association(association)
associations = []
parent = association.parent
@@ -104,12 +131,11 @@ module ActiveRecord
ref[association.reflection.name] ||= {}
end
- def build(associations, parent = nil, join_type = Arel::InnerJoin)
- parent ||= join_parts.last
+ def build(associations, parent = join_parts.last, join_type = Arel::InnerJoin)
case associations
when Symbol, String
- reflection = parent.reflections[associations.to_s.intern] or
- raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
+ reflection = parent.reflections[associations.intern] or
+ raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
unless join_association = find_join_association(reflection, parent)
@reflections << reflection
join_association = build_join_association(reflection, parent)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0d3b4dbab1..b81aecb4e5 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -55,14 +55,19 @@ module ActiveRecord
def find_parent_in(other_join_dependency)
other_join_dependency.join_parts.detect do |join_part|
- parent == join_part
+ case parent
+ when JoinBase
+ parent.base_klass == join_part.base_klass
+ else
+ parent == join_part
+ end
end
end
- def join_to(relation)
+ def join_to(manager)
tables = @tables.dup
foreign_table = parent_table
- foreign_klass = parent.active_record
+ foreign_klass = parent.base_klass
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@@ -75,7 +80,7 @@ module ActiveRecord
foreign_key = reflection.foreign_key
when :has_and_belongs_to_many
# Join the join table first...
- relation.from(join(
+ manager.from(join(
table,
table[reflection.foreign_key].
eq(foreign_table[reflection.active_record_primary_key])
@@ -101,23 +106,42 @@ module ActiveRecord
]
end
- scope_chain_items.each do |item|
+ constraint = scope_chain_items.inject(constraint) do |chain, item|
unless item.is_a?(Relation)
item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
end
- constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
+ if item.arel.constraints.empty?
+ chain
+ else
+ chain.and(item.arel.constraints)
+ end
end
- relation.from(join(table, constraint))
+ manager.from(join(table, constraint))
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, reflection.klass
end
- relation
+ manager
end
+ # Builds equality condition.
+ #
+ # Example:
+ #
+ # class Physician < ActiveRecord::Base
+ # has_many :appointments
+ # end
+ #
+ # If I execute `Physician.joins(:appointments).to_a` then
+ # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
+ # table #=> #<Arel::Table @name="appointments" ...>
+ # key #=> physician_id
+ # foreign_table #=> #<Arel::Table @name="physicians" ...>
+ # foreign_key #=> id
+ #
def build_constraint(reflection, table, key, foreign_table, foreign_key)
constraint = table[key].eq(foreign_table[foreign_key])
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index 3920e84976..a7dacdbfd6 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -4,7 +4,7 @@ module ActiveRecord
class JoinBase < JoinPart # :nodoc:
def ==(other)
other.class == self.class &&
- other.active_record == active_record
+ other.base_klass == base_klass
end
def aliased_prefix
@@ -16,7 +16,7 @@ module ActiveRecord
end
def aliased_table_name
- active_record.table_name
+ base_klass.table_name
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 5604687b57..b534569063 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Associations
class JoinDependency # :nodoc:
- # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
+ # A JoinPart represents a part of a JoinDependency. It is inherited
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
# everything else is being joined onto. A JoinAssociation represents an association which
# is joining to the base. A JoinAssociation may result in more than one actual join
@@ -11,12 +11,12 @@ module ActiveRecord
# The Active Record class which this join part is associated 'about'; for a JoinBase
# this is the actual base model, for a JoinAssociation this is the target model of the
# association.
- attr_reader :active_record
+ attr_reader :base_klass
- delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
- def initialize(active_record)
- @active_record = active_record
+ def initialize(base_klass)
+ @base_klass = base_klass
@cached_record = {}
@column_names_with_alias = nil
end
@@ -70,7 +70,7 @@ module ActiveRecord
end
def instantiate(row)
- @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
+ @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index 38bc7ce7da..157b627ad5 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -5,9 +5,13 @@ module ActiveRecord
include ThroughAssociation
def associated_records_by_owner
- super.each do |owner, records|
- records.uniq! if reflection_scope.distinct_value
+ records_by_owner = super
+
+ if reflection_scope.distinct_value
+ records_by_owner.each_value { |records| records.uniq! }
end
+
+ records_by_owner
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 43520142bf..35f29b37a2 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,7 +14,7 @@ module ActiveRecord
def target_scope
scope = super
chain[1..-1].each do |reflection|
- scope = scope.merge(
+ scope.merge!(
reflection.klass.all.with_default_scope.
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index e536f5ebcc..75377bba57 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -1,3 +1,4 @@
+require 'active_model/forbidden_attributes_protection'
module ActiveRecord
module AttributeAssignment
@@ -44,7 +45,7 @@ module ActiveRecord
if respond_to?("#{k}=")
raise
else
- raise UnknownAttributeError, "unknown attribute: #{k}"
+ raise UnknownAttributeError.new(self, k)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 22405c5d74..609c6e8cab 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -56,7 +56,7 @@ module ActiveRecord
# # => false
def instance_method_already_implemented?(method_name)
if dangerous_attribute_method?(method_name)
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record"
end
if superclass == Base
@@ -163,8 +163,22 @@ module ActiveRecord
# person.respond_to('age?') # => true
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
+ name = name.to_s
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
- super
+ result = super
+
+ # If the result is false the answer is false.
+ return false unless result
+
+ # If the result is true then check for the select case.
+ # For queries selecting a subset of columns, return false for unselected columns.
+ # We check defined?(@attributes) not to issue warnings if called on objects that
+ # have been allocated but not yet initialized.
+ if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
+ return has_attribute?(name)
+ end
+
+ return true
end
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
@@ -328,6 +342,7 @@ module ActiveRecord
end
def attribute_method?(attr_name) # :nodoc:
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
defined?(@attributes) && @attributes.include?(attr_name)
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index a23baeaced..f596a8b02e 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -41,8 +41,9 @@ module ActiveRecord
# task.read_attribute_before_type_cast('id') # => '1'
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
+ # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- @attributes[attr_name]
+ @attributes[attr_name.to_s]
end
# Returns a hash of attributes before typecasting and deserialization.
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 25d62fdb85..9caf73e627 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -11,6 +11,12 @@ module ActiveRecord
end
module ClassMethods
+ ##
+ # :method: serialized_attributes
+ #
+ # Returns a hash of all the attributes that have been specified for
+ # serialization as keys and their class restriction as values.
+
# If you have an attribute that needs to be saved to the database as an
# object, and retrieved as the same object, then specify the name of that
# attribute using this method and it will be handled automatically. The
@@ -44,19 +50,13 @@ module ActiveRecord
end
end
- def serialized_attributes
- message = "Instance level serialized_attributes method is deprecated, please use class level method."
- ActiveSupport::Deprecation.warn message
- defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
- end
-
class Type # :nodoc:
def initialize(column)
@column = column
end
def type_cast(value)
- value.unserialized_value
+ value.unserialized_value @column.type_cast value.value
end
def type
@@ -65,17 +65,17 @@ module ActiveRecord
end
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
- def unserialized_value
- state == :serialized ? unserialize : value
+ def unserialized_value(v = value)
+ state == :serialized ? unserialize(v) : value
end
def serialized_value
state == :unserialized ? serialize : value
end
- def unserialize
+ def unserialize(v)
self.state = :unserialized
- self.value = coder.load(value)
+ self.value = coder.load(v)
end
def serialize
@@ -86,10 +86,10 @@ module ActiveRecord
# This is only added to the model when serialize is called, which
# ensures we do not make things slower when serialization is not used.
- module Behavior #:nodoc:
+ module Behavior # :nodoc:
extend ActiveSupport::Concern
- module ClassMethods
+ module ClassMethods # :nodoc:
def initialize_attributes(attributes, options = {})
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
super(attributes, options)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0df3e57947..a8a1847554 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -17,7 +17,8 @@ module ActiveRecord
# be destroyed directly. They will however still be marked for destruction.
#
# Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
- # When the <tt>:autosave</tt> option is not present new associations are saved.
+ # When the <tt>:autosave</tt> option is not present then new association records are
+ # saved but the updated association records are not saved.
#
# == Validation
#
@@ -62,14 +63,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.author.id
- # Author.find_by_id(id).nil? # => false
+ # Author.find_by(id: id).nil? # => false
#
# post.save
# post.reload.author # => nil
#
# Now it _is_ removed from the database:
#
- # Author.find_by_id(id).nil? # => true
+ # Author.find_by(id: id).nil? # => true
#
# === One-to-many Example
#
@@ -113,14 +114,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.comments.last.id
- # Comment.find_by_id(id).nil? # => false
+ # Comment.find_by(id: id).nil? # => false
#
# post.save
# post.reload.comments.length # => 1
#
# Now it _is_ removed from the database:
#
- # Comment.find_by_id(id).nil? # => true
+ # Comment.find_by(id: id).nil? # => true
module AutosaveAssociation
extend ActiveSupport::Concern
@@ -300,7 +301,7 @@ module ActiveRecord
def association_valid?(reflection, record)
return true if record.destroyed? || record.marked_for_destruction?
- unless valid = record.valid?(validation_context)
+ unless valid = record.valid?
if reflection.options[:autosave]
record.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
@@ -334,15 +335,18 @@ module ActiveRecord
autosave = reflection.options[:autosave]
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
- records_to_destroy = []
+
+ if autosave
+ records_to_destroy = records.select(&:marked_for_destruction?)
+ records_to_destroy.each { |record| association.destroy(record) }
+ records -= records_to_destroy
+ end
+
records.each do |record|
- next if record.destroyed?
saved = true
- if autosave && record.marked_for_destruction?
- records_to_destroy << record
- elsif autosave != false && (@new_record_before_save || record.new_record?)
+ if autosave != false && (@new_record_before_save || record.new_record?)
if autosave
saved = association.insert_record(record, false)
else
@@ -354,10 +358,6 @@ module ActiveRecord
raise ActiveRecord::Rollback unless saved
end
-
- records_to_destroy.each do |record|
- association.destroy(record)
- end
end
# reconstruct the scope now that we know the owner's id
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index e262401da6..b06add096f 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -160,10 +160,10 @@ module ActiveRecord #:nodoc:
#
# == Dynamic attribute-based finders
#
- # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
+ # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
# by simple queries without turning to SQL. They work by appending the name of an attribute
# to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
- # Instead of writing <tt>Person.where(user_name: user_name).first</tt>, you just do
+ # Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
# <tt>Person.find_by_user_name(user_name)</tt>.
#
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
@@ -172,7 +172,7 @@ module ActiveRecord #:nodoc:
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
#
- # Person.where(user_name: user_name, password: password).first
+ # Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
#
# It's even possible to call these dynamic finder methods on relations and named scopes.
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 22226b2f4f..e4c484d64b 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -83,7 +83,7 @@ module ActiveRecord
#
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
- # hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant
+ # hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
# to decide whether they want to call +super+ and trigger the inherited callbacks.
#
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 8d22942a06..d3d7396c91 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -23,19 +23,15 @@ module ActiveRecord
def load(yaml)
return object_class.new if object_class != Object && yaml.nil?
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
- begin
- obj = YAML.load(yaml)
-
- unless obj.is_a?(object_class) || obj.nil?
- raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
- end
- obj ||= object_class.new if object_class != Object
-
- obj
- rescue ArgumentError, Psych::SyntaxError
- yaml
+ obj = YAML.load(yaml)
+
+ unless obj.is_a?(object_class) || obj.nil?
+ raise SerializationTypeMismatch,
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
end
+ obj ||= object_class.new if object_class != Object
+
+ obj
end
end
end
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 bf2f945448..816b397fcf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -253,14 +253,6 @@ module ActiveRecord
@available = Queue.new self
end
- # Hack for tests to be able to add connections. Do not call outside of tests
- def insert_connection_for_test!(c) #:nodoc:
- synchronize do
- @connections << c
- @available.add c
- end
- end
-
# Retrieve the connection associated with the current thread, or call
# #checkout to obtain one if necessary.
#
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 553bce983a..0be4b5cb19 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -68,8 +68,7 @@ module ActiveRecord
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name, type = :primary_key, options = {})
- options[:primary_key] = true
- column(name, type, options)
+ column(name, type, options.merge(:primary_key => true))
end
# Returns a ColumnDefinition for the column with name +name+.
@@ -261,7 +260,7 @@ module ActiveRecord
args.each do |col|
column("#{col}_id", :integer, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
end
alias :belongs_to :references
@@ -273,6 +272,7 @@ module ActiveRecord
end
column.limit = limit
+ column.array = options[:array] if column.respond_to?(:array)
column.precision = options[:precision]
column.scale = options[:scale]
column.default = options[:default]
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 742d198096..3ac55a0f11 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -443,7 +443,7 @@ module ActiveRecord
#
# add_index(:suppliers, :name)
#
- # generates
+ # generates:
#
# CREATE INDEX suppliers_name_index ON suppliers(name)
#
@@ -451,7 +451,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true)
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
#
@@ -459,7 +459,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
#
@@ -467,13 +467,13 @@ module ActiveRecord
#
# add_index(:accounts, :name, name: 'by_name', length: 10)
#
- # generates
+ # generates:
#
# CREATE INDEX by_name ON accounts(name(10))
#
# add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
#
- # generates
+ # generates:
#
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
@@ -483,7 +483,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
#
- # generates
+ # generates:
#
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
@@ -493,26 +493,26 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
# ====== Creating an index with a specific method
#
- # add_index(:developers, :name, :using => 'btree')
+ # add_index(:developers, :name, using: 'btree')
#
- # generates
+ # generates:
#
- # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
- # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
#
# Note: only supported by PostgreSQL and MySQL
#
# ====== Creating an index with a specific type
#
- # add_index(:developers, :name, :type => :fulltext)
+ # add_index(:developers, :name, type: :fulltext)
#
- # generates
+ # generates:
#
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
#
@@ -606,7 +606,7 @@ module ActiveRecord
index_options = options.delete(:index)
add_column(table_name, "#{ref_name}_id", :integer, options)
add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
alias :add_belongs_to :add_reference
@@ -695,12 +695,21 @@ module ActiveRecord
end
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
#
- # distinct("posts.id", "posts.created_at desc")
+ # distinct("posts.id", ["posts.created_at desc"])
#
def distinct(columns, order_by)
- "DISTINCT #{columns}"
+ ActiveSupport::Deprecation.warn("#distinct is deprecated and shall be removed from future releases.")
+ "DISTINCT #{columns_for_distinct(columns, order_by)}"
+ end
+
+ # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
+ # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
+ # require the order columns appear in the SELECT.
+ #
+ # columns_for_distinct("posts.id", ["posts.created_at desc"])
+ def columns_for_distinct(columns, orders) # :nodoc:
+ columns
end
# Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
@@ -755,37 +764,23 @@ module ActiveRecord
column_names = Array(column_name)
index_name = index_name(table_name, column: column_names)
- if Hash === options # legacy support, since this param was a string
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
- index_type = options[:unique] ? "UNIQUE" : ""
- index_type = options[:type].to_s if options.key?(:type)
- index_name = options[:name].to_s if options.key?(:name)
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+ index_type = options[:unique] ? "UNIQUE" : ""
+ index_type = options[:type].to_s if options.key?(:type)
+ index_name = options[:name].to_s if options.key?(:name)
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
- if index_algorithms.key?(options[:algorithm])
- algorithm = index_algorithms[options[:algorithm]]
- elsif options[:algorithm].present?
+ if options.key?(:algorithm)
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
- end
-
- using = "USING #{options[:using]}" if options[:using].present?
-
- if supports_partial_index?
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
- end
- else
- if options
- message = "Passing a string as third argument of `add_index` is deprecated and will" +
- " be removed in Rails 4.1." +
- " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead"
+ }
+ end
- ActiveSupport::Deprecation.warn message
- end
+ using = "USING #{options[:using]}" if options[:using].present?
- index_type = options
- max_index_length = allowed_index_name_length
- algorithm = using = nil
+ if supports_partial_index?
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
if index_name.length > max_index_length
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 0b24c042ee..e232cad982 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -101,6 +101,10 @@ module ActiveRecord
@visitor = nil
end
+ def valid_type?(type)
+ true
+ end
+
class SchemaCreation
def initialize(conn)
@conn = conn
@@ -295,9 +299,8 @@ module ActiveRecord
[]
end
- # A list of index algorithms, to be filled by adapters that
- # support them. MySQL and PostgreSQL has support for them right
- # now.
+ # A list of index algorithms, to be filled by adapters that support them.
+ # MySQL and PostgreSQL have support for them right now.
def index_algorithms
{}
end
@@ -443,7 +446,7 @@ module ActiveRecord
def translate_exception(exception, message)
# override in derived class
- ActiveRecord::StatementInvalid.new(message)
+ ActiveRecord::StatementInvalid.new(message, exception)
end
end
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 84bf3acf0e..5b25b26164 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -91,6 +91,8 @@ module ActiveRecord
def extract_limit(sql_type)
case sql_type
+ when /^enum\((.+)\)/i
+ $1.split(',').map{|enum| enum.strip.length - 2}.max
when /blob|text/i
case sql_type
when /tiny/i
@@ -107,8 +109,6 @@ module ActiveRecord
when /^mediumint/i; 3
when /^smallint/i; 2
when /^tinyint/i; 1
- when /^enum\((.+)\)/i
- $1.split(',').map{|enum| enum.strip.length - 2}.max
else
super
end
@@ -296,7 +296,7 @@ module ActiveRecord
end
rescue ActiveRecord::StatementInvalid => exception
if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ raise ActiveRecord::StatementInvalid.new("'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings.", exception.original_exception)
else
raise
end
@@ -373,7 +373,9 @@ module ActiveRecord
# and creates it again using the provided +options+.
def recreate_database(name, options = {})
drop_database(name)
- create_database(name, options)
+ sql = create_database(name, options)
+ reconnect!
+ sql
end
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a4b3a0c584..609ccc2ed2 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -161,7 +161,7 @@ module ActiveRecord
def value_to_date(value)
if value.is_a?(String)
- return nil if value.blank?
+ return nil if value.empty?
fast_string_to_date(value) || fallback_string_to_date(value)
elsif value.respond_to?(:to_date)
value.to_date
@@ -172,14 +172,14 @@ module ActiveRecord
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.blank?
+ return nil if string.empty?
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.blank?
+ return nil if string.empty?
dummy_time_string = "2000-01-01 #{string}"
@@ -192,7 +192,7 @@ module ActiveRecord
# convert something to a boolean
def value_to_boolean(value)
- if value.is_a?(String) && value.blank?
+ if value.is_a?(String) && value.empty?
nil
else
TRUE_VALUES.include?(value)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 6b319fed88..530a27d099 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -4,7 +4,7 @@ gem 'mysql2', '~> 0.3.10'
require 'mysql2'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 0e01fe0fdb..1826d88500 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -16,9 +16,9 @@ class Mysql
end
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
- def mysql_connection(config) # :nodoc:
+ def mysql_connection(config)
config = config.symbolize_keys
host = config[:host]
port = config[:port]
@@ -383,7 +383,7 @@ module ActiveRecord
TYPES = {}
- # Register an MySQL +type_id+ with a typcasting object in
+ # Register an MySQL +type_id+ with a typecasting object in
# +type+.
def self.register_type(type_id, type)
TYPES[type_id] = type
@@ -393,6 +393,14 @@ module ActiveRecord
TYPES[new] = TYPES[old]
end
+ def self.find_type(field)
+ if field.type == Mysql::Field::TYPE_TINY && field.length > 1
+ TYPES[Mysql::Field::TYPE_LONG]
+ else
+ TYPES.fetch(field.type) { Fields::Identity.new }
+ end
+ end
+
register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
@@ -425,9 +433,7 @@ module ActiveRecord
if field.decimals > 0
types[field.name] = Fields::Decimal.new
else
- types[field.name] = Fields::TYPES.fetch(field.type) {
- Fields::Identity.new
- }
+ types[field.name] = Fields.find_type field
end
}
result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 14ef07a75e..a73f0ac57f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -26,6 +26,15 @@ module ActiveRecord
end
end
+ def string_to_bit(value)
+ case value
+ when /^0x/i
+ value[2..-1].hex.to_s(2) # Hexadecimal notation
+ else
+ value # Bit-string notation
+ end
+ end
+
def hstore_to_string(object)
if Hash === object
object.map { |k,v|
@@ -51,7 +60,7 @@ module ActiveRecord
end
def json_to_string(object)
- if Hash === object
+ if Hash === object || Array === object
ActiveSupport::JSON.encode(object)
else
object
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 51f377dfd7..1be116ce10 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -18,8 +18,19 @@ module ActiveRecord
end
end
+ class Bit < Type
+ def type_cast(value)
+ if String === value
+ ConnectionAdapters::PostgreSQLColumn.string_to_bit value
+ else
+ value
+ end
+ end
+ end
+
class Bytea < Type
def type_cast(value)
+ return if value.nil?
PGconn.unescape_bytea value
end
end
@@ -322,14 +333,14 @@ module ActiveRecord
# FIXME: why are we keeping these types as strings?
alias_type 'tsvector', 'text'
alias_type 'interval', 'text'
- alias_type 'bit', 'text'
- alias_type 'varbit', 'text'
alias_type 'macaddr', 'text'
alias_type 'uuid', 'text'
register_type 'money', OID::Money.new
register_type 'bytea', OID::Bytea.new
register_type 'bool', OID::Boolean.new
+ register_type 'bit', OID::Bit.new
+ register_type 'varbit', OID::Bit.new
register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 6329733abc..e9daa5d7ff 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -30,9 +30,10 @@ module ActiveRecord
when Array
case sql_type
when 'point' then super(PostgreSQLColumn.point_to_string(value))
+ when 'json' then super(PostgreSQLColumn.json_to_string(value))
else
if column.array
- "'#{PostgreSQLColumn.array_to_string(value, column, self)}'"
+ "'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'"
else
super
end
@@ -98,6 +99,7 @@ module ActiveRecord
when Array
case column.sql_type
when 'point' then PostgreSQLColumn.point_to_string(value)
+ when 'json' then PostgreSQLColumn.json_to_string(value)
else
return super(value, column) unless column.array
PostgreSQLColumn.array_to_string(value, column, self)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 0168c36abc..a651b6c32e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -156,13 +156,15 @@ module ActiveRecord
column_names = columns.values_at(*indkey).compact
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
-
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ unless column_names.empty?
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
+
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ end
end.compact
end
@@ -319,6 +321,7 @@ module ActiveRecord
result = query(<<-end_sql, 'SCHEMA')[0]
SELECT attr.attname,
CASE
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
@@ -330,7 +333,7 @@ module ActiveRecord
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
WHERE t.oid = '#{quote_table_name(table)}'::regclass
AND cons.contype = 'p'
- AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
end_sql
end
@@ -464,22 +467,17 @@ module ActiveRecord
end
end
- # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
- #
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
- #
- # distinct("posts.id", ["posts.created_at desc"])
- # # => "DISTINCT posts.id, posts.created_at AS alias_0"
- def distinct(columns, orders) #:nodoc:
- order_columns = orders.map{ |s|
+ def columns_for_distinct(columns, orders) #:nodoc:
+ order_columns = orders.reject(&:blank?).map{ |s|
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
- [super].concat(order_columns).join(', ')
+ [super, *order_columns].join(', ')
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 46e41e6b48..d5a603cadc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -16,15 +16,15 @@ require 'pg'
require 'ipaddr'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
:client_encoding, :options, :application_name, :fallback_application_name,
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
- :tty, :sslmode, :requiressl, :sslcert, :sslkey, :sslrootcert, :sslcrl,
- :requirepeer, :krbsrvname, :gsslib, :service]
+ :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
+ :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
# Establishes a connection to the database that's used by all Active Record objects
- def postgresql_connection(config) # :nodoc:
+ def postgresql_connection(config)
conn_params = config.symbolize_keys
conn_params.delete_if { |_, v| v.nil? }
@@ -80,7 +80,7 @@ module ActiveRecord
when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
$1
# Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
$1
# Character types
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
@@ -330,9 +330,37 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+ # Defines the primary key field.
+ # Use of the native PostgreSQL UUID type is supported, and can be used
+ # by defining your tables as such:
+ #
+ # create_table :stuffs, id: :uuid do |t|
+ # t.string :content
+ # t.timestamps
+ # end
+ #
+ # By default, this will use the +uuid_generate_v4()+ function from the
+ # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
+ # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
+ # set the +:default+ option to +nil+:
+ #
+ # create_table :stuffs, id: false do |t|
+ # t.primary_key :id, :uuid, default: nil
+ # t.uuid :foo_id
+ # t.timestamps
+ # end
+ #
+ # You may also pass a different UUID generation function from +uuid-ossp+
+ # or another library.
+ #
+ # Note that setting the UUID primary key default value to +nil+ will
+ # require you to assure that you always provide a UUID value before saving
+ # a record (as primary keys cannot be +nil+). This might be done via the
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, options = {})
return super unless type == :uuid
- options[:default] ||= 'uuid_generate_v4()'
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()')
options[:primary_key] = true
column name, type, options
end
@@ -594,9 +622,9 @@ module ActiveRecord
true
end
- # Returns true if pg > 9.2
+ # Returns true if pg > 9.1
def supports_extensions?
- postgresql_version >= 90200
+ postgresql_version >= 90100
end
# Range datatypes weren't introduced until PostgreSQL 9.2
@@ -618,9 +646,9 @@ module ActiveRecord
def extension_enabled?(name)
if supports_extensions?
- res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)",
+ res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
'SCHEMA'
- res.column_types['exists'].type_cast res.rows.first.first
+ res.column_types['enabled'].type_cast res.rows.first.first
end
end
@@ -740,7 +768,7 @@ module ActiveRecord
end
def exec_cache(sql, binds)
- stmt_key = prepare_statement sql
+ stmt_key = prepare_statement(sql)
# Clear the queue
@connection.get_last_result
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c3bebf7a5e..7d940fe1c9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -6,9 +6,9 @@ gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# sqlite3 adapter reuses sqlite_connection.
- def sqlite3_connection(config) # :nodoc:
+ def sqlite3_connection(config)
# Require database.
unless config[:database]
raise ArgumentError, "No database file specified. Missing argument: database"
@@ -499,10 +499,6 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
- def valid_type?(type)
- true
- end
-
protected
def select(sql, name = nil, binds = []) #:nodoc:
exec_query(sql, name, binds)
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index d6d998c7be..a1943dfcb0 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -15,15 +15,15 @@ module ActiveRecord
# Example for SQLite database:
#
# ActiveRecord::Base.establish_connection(
- # adapter: "sqlite",
- # database: "path/to/dbfile"
+ # adapter: "sqlite",
+ # database: "path/to/dbfile"
# )
#
# Also accepts keys as strings (for parsing from YAML for example):
#
# ActiveRecord::Base.establish_connection(
- # "adapter" => "sqlite",
- # "database" => "path/to/dbfile"
+ # "adapter" => "sqlite",
+ # "database" => "path/to/dbfile"
# )
#
# Or a URL:
@@ -54,11 +54,11 @@ module ActiveRecord
end
def connection_id
- Thread.current['ActiveRecord::Base.connection_id']
+ ActiveRecord::RuntimeRegistry.connection_id
end
def connection_id=(connection_id)
- Thread.current['ActiveRecord::Base.connection_id'] = connection_id
+ ActiveRecord::RuntimeRegistry.connection_id = connection_id
end
# Returns the configuration of the associated connection as a hash:
@@ -79,7 +79,7 @@ module ActiveRecord
connection_handler.retrieve_connection(self)
end
- # Returns true if Active Record is connected.
+ # Returns +true+ if Active Record is connected.
def connected?
connection_handler.connected?(self)
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index db5e7b82ca..ba053700f2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -80,11 +80,11 @@ module ActiveRecord
class_attribute :default_connection_handler, instance_writer: false
def self.connection_handler
- Thread.current[:active_record_connection_handler] || self.default_connection_handler
+ ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
end
def self.connection_handler=(handler)
- Thread.current[:active_record_connection_handler] = handler
+ ActiveRecord::RuntimeRegistry.connection_handler = handler
end
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
@@ -307,9 +307,11 @@ module ActiveRecord
id.hash
end
- # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
+ # Clone and freeze the attributes hash such that associations are still
+ # accessible, even on destroyed records, but cloned models will not be
+ # frozen.
def freeze
- @attributes.freeze
+ @attributes = @attributes.clone.freeze
self
end
@@ -344,9 +346,15 @@ module ActiveRecord
self.class.connection
end
+ def connection_handler
+ self.class.connection_handler
+ end
+
# Returns the contents of the record as a nicely formatted string.
def inspect
- inspection = if @attributes
+ # We check defined?(@attributes) not to issue warnings if the object is
+ # allocated but not initialized.
+ inspection = if defined?(@attributes) && @attributes
self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index c615d59725..7e38719811 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -57,26 +57,23 @@ module ActiveRecord
class RecordNotDestroyed < ActiveRecordError
end
- # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
- # MySQL when Ruby driver used is too old).
+ # Superclass for all database execution errors.
+ #
+ # Wraps the underlying database error as +original_exception+.
class StatementInvalid < ActiveRecordError
- end
-
- # Raised when SQL statement is invalid and the application gets a blank result.
- class ThrowResult < ActiveRecordError
- end
-
- # Parent class for all specific exceptions which wrap database driver exceptions
- # provides access to the original exception also.
- class WrappedDatabaseException < StatementInvalid
attr_reader :original_exception
- def initialize(message, original_exception)
+ def initialize(message, original_exception = nil)
super(message)
@original_exception = original_exception
end
end
+ # Defunct wrapper class kept for compatibility.
+ # +StatementInvalid+ wraps the original exception now.
+ class WrappedDatabaseException < StatementInvalid
+ end
+
# Raised when a record cannot be inserted because it would violate a uniqueness constraint.
class RecordNotUnique < WrappedDatabaseException
end
@@ -158,6 +155,15 @@ module ActiveRecord
# Raised when unknown attributes are supplied via mass assignment.
class UnknownAttributeError < NoMethodError
+
+ attr_reader :record, :attribute
+
+ def initialize(record, attribute)
+ @record = record
+ @attribute = attribute.to_s
+ super("unknown attribute: #{attribute}")
+ end
+
end
# Raised when an error occurred while doing a mass assignment to an attribute through the
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 15736575a2..e65dab07ba 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,22 +1,22 @@
require 'active_support/lazy_load_hooks'
+require 'active_record/explain_registry'
module ActiveRecord
module Explain
- # Relation#explain needs to be able to collect the queries.
+ # Executes the block with the collect flag enabled. Queries are collected
+ # asynchronously by the subscriber and returned.
def collecting_queries_for_explain # :nodoc:
- current = Thread.current
- original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
+ ExplainRegistry.collect = true
yield
- return current[:available_queries_for_explain]
+ ExplainRegistry.queries
ensure
- # Note that the return value above does not depend on this assignment.
- current[:available_queries_for_explain] = original
+ ExplainRegistry.reset
end
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
- str = queries && queries.map do |sql, bind|
+ str = queries.map do |sql, bind|
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
unless bind.empty?
@@ -31,6 +31,7 @@ module ActiveRecord
def str.inspect
self
end
+
str
end
end
diff --git a/activerecord/lib/active_record/explain_registry.rb b/activerecord/lib/active_record/explain_registry.rb
new file mode 100644
index 0000000000..f5cd57e075
--- /dev/null
+++ b/activerecord/lib/active_record/explain_registry.rb
@@ -0,0 +1,30 @@
+require 'active_support/per_thread_registry'
+
+module ActiveRecord
+ # This is a thread locals registry for EXPLAIN. For example
+ #
+ # ActiveRecord::ExplainRegistry.queries
+ #
+ # returns the collected queries local to the current thread.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class ExplainRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_accessor :queries, :collect
+
+ def initialize
+ reset
+ end
+
+ def collect?
+ @collect
+ end
+
+ def reset
+ @collect = false
+ @queries = []
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 0f927496fb..6a49936644 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -1,4 +1,5 @@
require 'active_support/notifications'
+require 'active_record/explain_registry'
module ActiveRecord
class ExplainSubscriber # :nodoc:
@@ -7,8 +8,8 @@ module ActiveRecord
end
def finish(name, id, payload)
- if queries = Thread.current[:available_queries_for_explain]
- queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
end
end
@@ -18,7 +19,7 @@ module ActiveRecord
# On the other hand, we want to monitor the performance of our real database
# queries, not the performance of the access to the query cache.
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
- EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)/i
+ EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i
def ignore_payload?(payload)
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index c26fc76515..33e19313a0 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -567,7 +567,7 @@ module ActiveRecord
# interpolate the fixture label
row.each do |key, value|
- row[key] = label if value == "$LABEL"
+ row[key] = label if "$LABEL" == value
end
# generate a primary key if necessary
@@ -708,11 +708,18 @@ module ActiveRecord
module TestFixtures
extend ActiveSupport::Concern
- included do
- setup :setup_fixtures
- teardown :teardown_fixtures
+ def before_setup
+ setup_fixtures
+ super
+ end
+
+ def after_teardown
+ super
+ teardown_fixtures
+ end
- class_attribute :fixture_path
+ included do
+ class_attribute :fixture_path, :instance_writer => false
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_fixtures
@@ -765,8 +772,7 @@ module ActiveRecord
def try_to_load_dependency(file_name)
require_dependency file_name
rescue LoadError => e
- # Let's hope the developer has included it himself
-
+ # Let's hope the developer has included it
# Let's warn in case this is a subdependency, otherwise
# subdependency error messages are totally cryptic
if ActiveRecord::Base.logger
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index f54865c86e..40976bc29e 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -116,9 +116,10 @@ module ActiveRecord
begin
constant = ActiveSupport::Dependencies.constantize(candidate)
return constant if candidate == constant.to_s
- rescue NameError => e
- # We don't want to swallow NoMethodError < NameError errors
- raise e unless e.instance_of?(NameError)
+ # We don't want to swallow NoMethodError < NameError errors
+ rescue NoMethodError
+ raise
+ rescue NameError
end
end
@@ -174,7 +175,7 @@ module ActiveRecord
if subclass_name.present? && subclass_name != self.name
subclass = subclass_name.safe_constantize
- unless subclasses.include?(subclass)
+ unless descendants.include?(subclass)
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 48c73d7781..2589b2f3da 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# <tt>resources :users</tt> route. Normally, +user_path+ will
# construct a path with the user object's 'id' in it:
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/1"
#
# You can override +to_param+ in your model to make +user_path+ construct
@@ -33,7 +33,7 @@ module ActiveRecord
# end
# end
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/Phusion"
def to_param
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 8e4ddcac82..ddf2afca0c 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -64,7 +64,7 @@ module ActiveRecord
end
# Wraps the passed block in a transaction, locking the object
- # before yielding. You pass can the SQL locking clause
+ # before yielding. You can pass the SQL locking clause
# as argument (see <tt>lock!</tt>).
def with_lock(lock = true)
transaction do
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index c1ba524c84..61e5c120df 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -3,11 +3,11 @@ module ActiveRecord
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
def self.runtime=(value)
- Thread.current[:active_record_sql_runtime] = value
+ ActiveRecord::RuntimeRegistry.sql_runtime = value
end
def self.runtime
- Thread.current[:active_record_sql_runtime] ||= 0
+ ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
end
def self.reset_runtime
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index d3edcf3cdb..e96c347f6f 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -357,11 +357,14 @@ module ActiveRecord
class CheckPending
def initialize(app)
@app = app
+ @last_check = 0
end
def call(env)
- ActiveRecord::Base.logger.silence do
+ mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
+ if @last_check < mtime
ActiveRecord::Migration.check_pending!
+ @last_check = mtime
end
@app.call(env)
end
@@ -466,7 +469,7 @@ module ActiveRecord
@connection.respond_to?(:reverting) && @connection.reverting
end
- class ReversibleBlockHelper < Struct.new(:reverting)
+ class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc:
def up
yield unless reverting
end
@@ -668,6 +671,7 @@ module ActiveRecord
copied
end
+ # Determines the version number of the next migration.
def next_migration_number(number)
if ActiveRecord::Base.timestamped_migrations
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
@@ -699,6 +703,10 @@ module ActiveRecord
File.basename(filename)
end
+ def mtime
+ File.mtime filename
+ end
+
delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
private
@@ -714,6 +722,16 @@ module ActiveRecord
end
+ class NullMigration < MigrationProxy #:nodoc:
+ def initialize
+ super(nil, 0, nil, nil)
+ end
+
+ def mtime
+ 0
+ end
+ end
+
class Migrator#:nodoc:
class << self
attr_writer :migrations_paths
@@ -784,7 +802,11 @@ module ActiveRecord
end
def last_version
- migrations(migrations_paths).last.try(:version)||0
+ last_migration.version
+ end
+
+ def last_migration #:nodoc:
+ migrations(migrations_paths).last || NullMigration.new
end
def proper_table_name(name)
@@ -867,11 +889,15 @@ module ActiveRecord
alias :current :current_migration
def run
- target = migrations.detect { |m| m.version == @target_version }
- raise UnknownMigrationVersionError.new(@target_version) if target.nil?
- unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
- target.migrate(@direction)
- record_version_state_after_migrating(target.version)
+ migration = migrations.detect { |m| m.version == @target_version }
+ raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
+ unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
+ begin
+ execute_migration_in_transaction(migration, @direction)
+ rescue => e
+ canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
+ raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
+ end
end
end
@@ -892,10 +918,7 @@ module ActiveRecord
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
begin
- ddl_transaction(migration) do
- migration.migrate(@direction)
- record_version_state_after_migrating(migration.version)
- end
+ execute_migration_in_transaction(migration, @direction)
rescue => e
canceled_msg = use_transaction?(migration) ? "this and " : ""
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
@@ -932,6 +955,13 @@ module ActiveRecord
migrated.include?(migration.version.to_i)
end
+ def execute_migration_in_transaction(migration, direction)
+ ddl_transaction(migration) do
+ migration.migrate(direction)
+ record_version_state_after_migrating(migration.version)
+ end
+ end
+
def target
migrations.detect { |m| m.version == @target_version }
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 79c55045ba..9782a48055 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -144,7 +144,10 @@ module ActiveRecord
def invert_remove_index(args)
table, options = *args
- raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." unless options && options[:column]
+
+ unless options && options.is_a?(Hash) && options[:column]
+ raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
+ end
options = options.dup
[:add_index, [table, options.delete(:column), options]]
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 602ab9e2f4..0e8822d63f 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -90,8 +90,9 @@ module ActiveRecord
# accepts_nested_attributes_for :posts
# end
#
- # You can now set or update attributes on an associated post model through
- # the attribute hash.
+ # You can now set or update attributes on the associated posts through
+ # an attribute hash for a member: include the key +:posts_attributes+
+ # with an array of hashes of post attributes as a value.
#
# For each hash that does _not_ have an <tt>id</tt> key a new record will
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
@@ -114,10 +115,10 @@ module ActiveRecord
# hashes if they fail to pass your criteria. For example, the previous
# example could be rewritten as:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
+ # end
#
# params = { member: {
# name: 'joe', posts_attributes: [
@@ -134,19 +135,19 @@ module ActiveRecord
#
# Alternatively, :reject_if also accepts a symbol for using methods:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :new_record?
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :new_record?
+ # end
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :reject_posts
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :reject_posts
#
- # def reject_posts(attributed)
- # attributed['title'].blank?
- # end
- # end
+ # def reject_posts(attributed)
+ # attributed['title'].blank?
+ # end
+ # end
#
# If the hash contains an <tt>id</tt> key that matches an already
# associated record, the matching record will be modified:
@@ -183,6 +184,29 @@ module ActiveRecord
# member.save
# member.reload.posts.length # => 1
#
+ # Nested attributes for an associated collection can also be passed in
+ # the form of a hash of hashes instead of an array of hashes:
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: { first: { title: 'Foo' },
+ # second: { title: 'Bar' } })
+ #
+ # has the same effect as
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: [ { title: 'Foo' },
+ # { title: 'Bar' } ])
+ #
+ # The keys of the hash which is the value for +:posts_attributes+ are
+ # ignored in this case.
+ # However, it is not allowed to use +'id'+ or +:id+ for one of
+ # such keys, otherwise the hash will be wrapped in an array and
+ # interpreted as an attribute hash for a single post.
+ #
+ # Passing attributes for an associated collection in the form of a hash
+ # of hashes can be used with hashes generated from HTTP/HTML parameters,
+ # where there maybe no natural way to submit an array of hashes.
+ #
# === Saving
#
# All changes to models, including the destruction of those marked for
@@ -205,6 +229,27 @@ module ActiveRecord
# belongs_to :member, inverse_of: :posts
# validates_presence_of :member
# end
+ #
+ # Note that if you do not specify the <tt>inverse_of</tt> option, then
+ # Active Record will try to automatically guess the inverse association
+ # based on heuristics.
+ #
+ # For one-to-one nested associations, if you build the new (in-memory)
+ # child object yourself before assignment, then this module will not
+ # overwrite it, e.g.:
+ #
+ # class Member < ActiveRecord::Base
+ # has_one :avatar
+ # accepts_nested_attributes_for :avatar
+ #
+ # def avatar
+ # super || build_avatar(width: 200)
+ # end
+ # end
+ #
+ # member = Member.new
+ # member.avatar_attributes = {icon: 'sad'}
+ # member.avatar.width # => 200
module ClassMethods
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
@@ -264,6 +309,11 @@ module ActiveRecord
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
+ # Clear cached values of any inverse associations found in the
+ # reflection and prevent the reflection from finding inverses
+ # automatically in the future.
+ reflection.remove_automatic_inverse_of!
+
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
self.nested_attributes_options = nested_attributes_options
@@ -332,20 +382,28 @@ module ActiveRecord
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
+ existing_record = send(association_name)
- if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
- (options[:update_only] || record.id.to_s == attributes['id'].to_s)
- assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
+ if (options[:update_only] || !attributes['id'].blank?) && existing_record &&
+ (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s)
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
elsif attributes['id'].present?
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
- method = "build_#{association_name}"
- if respond_to?(method)
- send(method, attributes.except(*UNASSIGNABLE_KEYS))
+ assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
+
+ if existing_record && existing_record.new_record?
+ existing_record.assign_attributes(assignable_attributes)
+ association(association_name).initialize_attributes(existing_record)
else
- raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
+ method = "build_#{association_name}"
+ if respond_to?(method)
+ send(method, assignable_attributes)
+ else
+ raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
+ end
end
end
end
@@ -428,7 +486,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
else
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
end
end
end
@@ -490,7 +548,7 @@ module ActiveRecord
end
end
- def raise_nested_attributes_record_not_found(association_name, record_id)
+ def raise_nested_attributes_record_not_found!(association_name, record_id)
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 711fc8b883..d166f0dd66 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= ""
+ ""
end
def where_values_hash
@@ -55,7 +55,11 @@ module ActiveRecord
end
def calculate(_operation, _column_name, _options = {})
- nil
+ if _operation == :count
+ 0
+ else
+ nil
+ end
end
def exists?(_id = false)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 42cece3ad0..582006ea7d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -51,7 +51,7 @@ module ActiveRecord
# how this "single-table" inheritance mapping is implemented.
def instantiate(record, column_types = {})
klass = discriminate_class_for_record(record)
- column_types = klass.decorate_columns(column_types)
+ column_types = klass.decorate_columns(column_types.dup)
klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
end
@@ -99,6 +99,9 @@ module ActiveRecord
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
# +save+ returns +false+. See ActiveRecord::Callbacks for further
# details.
+ #
+ # Attributes marked as readonly are silently ignored if the record is
+ # being updated.
def save(*)
create_or_update
rescue ActiveRecord::RecordInvalid
@@ -118,6 +121,9 @@ module ActiveRecord
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
# ActiveRecord::Callbacks for further details.
+ #
+ # Attributes marked as readonly are silently ignored if the record is
+ # being updated.
def save!(*)
create_or_update || raise(RecordNotSaved)
end
@@ -204,6 +210,8 @@ module ActiveRecord
# * updated_at/updated_on column is updated if that column is available.
# * Updates all the attributes that are dirty in this object.
#
+ # This method raises an +ActiveRecord::ActiveRecordError+ if the
+ # attribute is marked as readonly.
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
@@ -325,10 +333,44 @@ module ActiveRecord
toggle(attribute).update_attribute(attribute, self[attribute])
end
- # Reloads the attributes of this object from the database.
- # The optional options argument is passed to find when reloading so you
- # may do e.g. record.reload(lock: true) to reload the same record with
- # an exclusive row lock.
+ # Reloads the record from the database.
+ #
+ # This method modifies the receiver in-place. Attributes are updated, and
+ # caches busted, in particular the associations cache.
+ #
+ # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
+ # is raised. Otherwise, in addition to the in-place modification the method
+ # returns +self+ for convenience.
+ #
+ # The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
+ #
+ # reload(lock: true) # reload with pessimistic locking
+ #
+ # Reloading is commonly used in test suites to test something is actually
+ # written to the database, or when some action modifies the corresponding
+ # row in the database but not the object in memory:
+ #
+ # assert account.deposit!(25)
+ # assert_equal 25, account.credit # check it is updated in memory
+ # assert_equal 25, account.reload.credit # check it is also persisted
+ #
+ # Another commom use case is optimistic locking handling:
+ #
+ # def with_optimistic_retry
+ # begin
+ # yield
+ # rescue ActiveRecord::StaleObjectError
+ # begin
+ # # Reload lock_version in particular.
+ # reload
+ # rescue ActiveRecord::RecordNotFound
+ # # If the record is gone there is nothing to do.
+ # else
+ # retry
+ # end
+ # end
+ # end
+ #
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
@@ -428,23 +470,11 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update_record(attribute_names = @attributes.keys)
- attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
- if attributes_with_values.empty?
+ attributes_values = arel_attributes_with_values_for_update(attribute_names)
+ if attributes_values.empty?
0
else
- klass = self.class
- column_hash = klass.connection.schema_cache.columns_hash klass.table_name
- db_columns_with_values = attributes_with_values.map { |attr,value|
- real_column = column_hash[attr.name]
- [real_column, value]
- }
- bind_attrs = attributes_with_values.dup
- bind_attrs.keys.each_with_index do |column, i|
- real_column = db_columns_with_values[i].first
- bind_attrs[column] = klass.connection.substitute_at(real_column, i)
- end
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was || id)).arel.compile_update(bind_attrs)
- klass.connection.update stmt, 'SQL', db_columns_with_values
+ self.class.unscoped.update_record attributes_values, id, id_was
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 902fd90c54..3d85898c41 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -8,13 +8,13 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :to => :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :distinct, :references, :none, :to => :all
+ :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :to => :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
# 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
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
- # a Product object with the attributes you specified in the SQL query.
+ # a +Product+ object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
@@ -29,9 +29,10 @@ module ActiveRecord
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
# # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
#
- # # You can use the same string replacement techniques as you can with ActiveRecord#find
+ # You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>:
+ #
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
- # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
+ # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
def find_by_sql(sql, binds = [])
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
column_types = {}
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 99117b74c5..31a0ace864 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -36,6 +36,20 @@ module ActiveRecord
rake_tasks do
require "active_record/base"
+
+ ActiveRecord::Tasks::DatabaseTasks.env = Rails.env
+ ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first
+ ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application
+ ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
+
+ if defined?(APP_RAKEFILE) && engine = Rails::Engine.find(find_engine_path(APP_RAKEFILE))
+ if engine.paths['db/migrate'].existent
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a
+ end
+ end
+
load "active_record/railties/databases.rake"
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d92e268109..f69e9b2217 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,14 +2,8 @@ require 'active_record'
db_namespace = namespace :db do
task :load_config do
- ActiveRecord::Base.configurations = Rails.application.config.database_configuration || {}
- ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
-
- if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
- if engine.paths['db/migrate'].existent
- ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a
- end
- end
+ ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
+ ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
namespace :create do
@@ -156,7 +150,7 @@ db_namespace = namespace :db do
begin
puts ActiveRecord::Tasks::DatabaseTasks.collation_current
rescue NoMethodError
- $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch'
+ $stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.'
end
end
@@ -166,11 +160,11 @@ db_namespace = namespace :db do
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => [:environment, :load_config] do
+ task :abort_if_pending_migrations => :environment do
pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
- puts "You have #{pending_migrations.size} pending migrations:"
+ puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
pending_migrations.each do |pending_migration|
puts ' %4d %s' % [pending_migration.version, pending_migration.name]
end
@@ -184,7 +178,7 @@ db_namespace = namespace :db do
desc 'Load the seed data from db/seeds.rb'
task :seed do
db_namespace['abort_if_pending_migrations'].invoke
- Rails.application.load_seed
+ ActiveRecord::Tasks::DatabaseTasks.load_seed
end
namespace :fixtures do
@@ -192,7 +186,15 @@ db_namespace = namespace :db do
task :load => [:environment, :load_config] do
require 'active_record/fixtures'
- base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ base_dir = if ENV['FIXTURES_PATH']
+ STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
+ "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
+ "instead."
+ File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ else
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path
+ end
+
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
@@ -209,7 +211,16 @@ db_namespace = namespace :db do
puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label
- base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
+ base_dir = if ENV['FIXTURES_PATH']
+ STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
+ "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
+ "instead."
+ File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ else
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path
+ end
+
+
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
@@ -228,7 +239,7 @@ db_namespace = namespace :db do
desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
task :dump => [:environment, :load_config] do
require 'active_record/schema_dumper'
- filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
+ filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
File.open(filename, "w:utf-8") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
@@ -237,12 +248,9 @@ db_namespace = namespace :db do
desc 'Load a schema.rb file into the database'
task :load => [:environment, :load_config] do
- file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
- if File.exists?(file)
- load(file)
- else
- abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded}
- end
+ file = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file(file)
+ load(file)
end
task :load_if_ruby => ['db:create', :environment] do
@@ -253,7 +261,7 @@ db_namespace = namespace :db do
desc 'Create a db/schema_cache.dump file.'
task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
- filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
con.schema_cache.clear!
con.tables.each { |table| con.schema_cache.add(table) }
@@ -262,7 +270,7 @@ db_namespace = namespace :db do
desc 'Clear a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
- filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
FileUtils.rm(filename) if File.exists?(filename)
end
end
@@ -270,32 +278,11 @@ db_namespace = namespace :db do
end
namespace :structure do
- def set_firebird_env(config)
- ENV['ISC_USER'] = config['username'].to_s if config['username']
- ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
- end
-
- def firebird_db_string(config)
- FireRuby::Database.db_string_for(config.symbolize_keys)
- end
-
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
task :dump => [:environment, :load_config] do
- filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
+ filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- case current_config['adapter']
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when 'sqlserver'
- `smoscript -s #{current_config['host']} -d #{current_config['database']} -u #{current_config['username']} -p #{current_config['password']} -f #{filename} -A -U`
- when "firebird"
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -a #{db_string} > #{filename}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
- end
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
if ActiveRecord::Base.connection.supports_migrations?
File.open(filename, "a") do |f|
@@ -307,23 +294,10 @@ db_namespace = namespace :db do
# desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
+ filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file(filename)
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
- case current_config['adapter']
- when 'sqlserver'
- `sqlcmd -S #{current_config['host']} -d #{current_config['database']} -U #{current_config['username']} -P #{current_config['password']} -i #{filename}`
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- IO.read(filename).split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -i #{filename} #{db_string}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
- end
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
end
task :load_if_sql => ['db:create', :environment] do
@@ -345,9 +319,13 @@ db_namespace = namespace :db do
# desc "Recreate the test database from an existent schema.rb file"
task :load_schema => 'db:test:purge' do
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
- ActiveRecord::Schema.verbose = false
- db_namespace["schema:load"].invoke
+ begin
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
+ ActiveRecord::Schema.verbose = false
+ db_namespace["schema:load"].invoke
+ ensure
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env])
+ end
end
# desc "Recreate the test database from an existent structure.sql file"
@@ -378,29 +356,11 @@ db_namespace = namespace :db do
# desc "Empty the test database"
task :purge => [:environment, :load_config] do
- abcs = ActiveRecord::Base.configurations
- case abcs['test']['adapter']
- when 'sqlserver'
- test = abcs.deep_dup['test']
- test_database = test['database']
- test['database'] = 'master'
- ActiveRecord::Base.establish_connection(test)
- ActiveRecord::Base.connection.recreate_database!(test_database)
- when "oci", "oracle"
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database!
- else
- ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
- end
+ ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare => 'db:abort_if_pending_migrations' do
+ task :prepare => :load_config do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
@@ -426,7 +386,7 @@ namespace :railties do
puts "NOTE: Migration #{migration.basename} from #{name} has been skipped. Migration with the same name already exists."
end
- on_copy = Proc.new do |name, migration, old_path|
+ on_copy = Proc.new do |name, migration|
puts "Copied migration #{migration.basename} from #{name}"
end
@@ -436,5 +396,5 @@ namespace :railties do
end
end
-task 'test:prepare' => 'db:test:prepare'
+task 'test:prepare' => ['db:test:prepare', 'db:test:load', 'db:abort_if_pending_migrations']
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 8499bb16e7..b3ddfd63d4 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -20,11 +20,5 @@ module ActiveRecord
self._attr_readonly
end
end
-
- def _attr_readonly
- message = "Instance level _attr_readonly method is deprecated, please use class level method."
- ActiveSupport::Deprecation.warn message
- defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
- end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0995750ecd..2aec73bd5b 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -5,10 +5,12 @@ module ActiveRecord
included do
class_attribute :reflections
+ class_attribute :aggregate_reflections
self.reflections = {}
+ self.aggregate_reflections = {}
end
- # Reflection enables to interrogate Active Record classes and objects
+ # \Reflection enables to interrogate Active Record classes and objects
# about their associations and aggregations. This information can,
# for example, be used in a form builder that takes an Active Record object
# and creates input fields for all of the attributes depending on their type
@@ -21,18 +23,24 @@ module ActiveRecord
case macro
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
klass = options[:through] ? ThroughReflection : AssociationReflection
- reflection = klass.new(macro, name, scope, options, active_record)
when :composed_of
- reflection = AggregateReflection.new(macro, name, scope, options, active_record)
+ klass = AggregateReflection
+ end
+
+ reflection = klass.new(macro, name, scope, options, active_record)
+
+ if klass == AggregateReflection
+ self.aggregate_reflections = self.aggregate_reflections.merge(name => reflection)
+ else
+ self.reflections = self.reflections.merge(name => reflection)
end
- self.reflections = self.reflections.merge(name => reflection)
reflection
end
# Returns an array of AggregateReflection objects for all the aggregations in the class.
def reflect_on_all_aggregations
- reflections.values.grep(AggregateReflection)
+ aggregate_reflections.values
end
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
@@ -40,8 +48,7 @@ module ActiveRecord
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
- reflection = reflections[aggregation]
- reflection if reflection.is_a?(AggregateReflection)
+ aggregate_reflections[aggregation]
end
# Returns an array of AssociationReflection objects for all the
@@ -55,7 +62,7 @@ module ActiveRecord
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
#
def reflect_on_all_associations(macro = nil)
- association_reflections = reflections.values.grep(AssociationReflection)
+ association_reflections = reflections.values
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
end
@@ -65,8 +72,7 @@ module ActiveRecord
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
- reflection = reflections[association]
- reflection if reflection.is_a?(AssociationReflection)
+ reflections[association]
end
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -75,8 +81,13 @@ module ActiveRecord
end
end
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
+ # Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ #
+ # MacroReflection
+ # AggregateReflection
+ # AssociationReflection
+ # ThroughReflection
class MacroReflection
# Returns the name of the macro.
#
@@ -95,7 +106,7 @@ module ActiveRecord
# Returns the hash of options used for the macro.
#
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
- # <tt>has_many :clients</tt> returns +{}+
+ # <tt>has_many :clients</tt> returns <tt>{}</tt>
attr_reader :options
attr_reader :active_record
@@ -176,6 +187,7 @@ module ActiveRecord
def initialize(*args)
super
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
+ @automatic_inverse_of = nil
end
# Returns a new, unsaved instance of the associated class. +attributes+ will
@@ -284,15 +296,31 @@ module ActiveRecord
alias :source_macro :macro
def has_inverse?
- @options[:inverse_of]
+ @options[:inverse_of] || find_inverse_of_automatically
end
def inverse_of
- if has_inverse?
- @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
+ @inverse_of ||= if options[:inverse_of]
+ klass.reflect_on_association(options[:inverse_of])
+ else
+ find_inverse_of_automatically
end
end
+ # Clears the cached value of +@inverse_of+ on this object. This will
+ # not remove the :inverse_of option however, so future calls on the
+ # +inverse_of+ will have to recompute the inverse.
+ def clear_inverse_of_cache!
+ @inverse_of = nil
+ end
+
+ # Removes the cached inverse association that was found automatically
+ # and prevents this object from finding the inverse association
+ # automatically in the future.
+ def remove_automatic_inverse_of!
+ @automatic_inverse_of = false
+ end
+
def polymorphic_inverse_of(associated_class)
if has_inverse?
if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
@@ -361,7 +389,85 @@ module ActiveRecord
options.key? :polymorphic
end
+ VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
+
private
+ # Attempts to find the inverse association automatically.
+ # If it cannot find a suitable inverse association, it returns
+ # nil.
+ def find_inverse_of_automatically
+ if @automatic_inverse_of == false
+ nil
+ elsif @automatic_inverse_of.nil?
+ set_automatic_inverse_of
+ else
+ klass.reflect_on_association(@automatic_inverse_of)
+ end
+ end
+
+ # Sets the +@automatic_inverse_of+ instance variable, and returns
+ # either nil or the inverse association that it finds.
+ #
+ # This method caches the inverse association that is found so that
+ # future calls to +find_inverse_of_automatically+ have much less
+ # overhead.
+ def set_automatic_inverse_of
+ if can_find_inverse_of_automatically?(self)
+ inverse_name = active_record.name.downcase.to_sym
+
+ begin
+ reflection = klass.reflect_on_association(inverse_name)
+ rescue NameError
+ # Give up: we couldn't compute the klass type so we won't be able
+ # to find any associations either.
+ reflection = false
+ end
+
+ if valid_inverse_reflection?(reflection)
+ @automatic_inverse_of = inverse_name
+ reflection
+ else
+ @automatic_inverse_of = false
+ nil
+ end
+ else
+ @automatic_inverse_of = false
+ nil
+ end
+ end
+
+ # Checks if the inverse reflection that is returned from the
+ # +set_automatic_inverse_of+ method is a valid reflection. We must
+ # make sure that the reflection's active_record name matches up
+ # with the current reflection's klass name.
+ #
+ # Note: klass will always be valid because when there's a NameError
+ # from calling +klass+, +reflection+ will already be set to false.
+ def valid_inverse_reflection?(reflection)
+ reflection &&
+ klass.name == reflection.active_record.try(:name) &&
+ klass.primary_key == reflection.active_record_primary_key &&
+ can_find_inverse_of_automatically?(reflection)
+ end
+
+ # Checks to see if the reflection doesn't have any options that prevent
+ # us from being able to guess the inverse automatically. First, the
+ # <tt>inverse_of</tt> option cannot be set to false. Second, we must
+ # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
+ # Third, we must not have options such as <tt>:polymorphic</tt> or
+ # <tt>:foreign_key</tt> which prevent us from correctly guessing the
+ # inverse association.
+ #
+ # Anything with a scope can additionally ruin our attempt at finding an
+ # inverse, so we exclude reflections with scopes.
+ def can_find_inverse_of_automatically?(reflection)
+ reflection.options[:inverse_of] != false &&
+ VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
+ !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
+ !reflection.scope
+ end
+
def derive_class_name
class_name = name.to_s.camelize
class_name = class_name.singularize if collection?
@@ -393,7 +499,7 @@ module ActiveRecord
delegate :foreign_key, :foreign_type, :association_foreign_key,
:active_record_primary_key, :type, :to => :source_reflection
- # Gets the source of the through reflection. It checks both a singularized
+ # Returns the source of the through reflection. It checks both a singularized
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
#
# class Post < ActiveRecord::Base
@@ -401,6 +507,15 @@ module ActiveRecord
# has_many :tags, through: :taggings
# end
#
+ # class Tagging < ActiveRecord::Base
+ # belongs_to :post
+ # belongs_to :tag
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.source_reflection
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
+ #
def source_reflection
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
end
@@ -414,10 +529,11 @@ module ActiveRecord
# end
#
# tags_reflection = Post.reflect_on_association(:tags)
- # taggings_reflection = tags_reflection.through_reflection
+ # tags_reflection.through_reflection
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
#
def through_reflection
- @through_reflection ||= active_record.reflect_on_association(options[:through])
+ active_record.reflect_on_association(options[:through])
end
# Returns an array of reflections which are involved in this association. Each item in the
@@ -426,6 +542,17 @@ module ActiveRecord
# The chain is built by recursively calling #chain on the source reflection and the through
# reflection. The base case for the recursion is a normal association, which just returns
# [self] as its #chain.
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.chain
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
+ # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
+ #
def chain
@chain ||= begin
chain = source_reflection.chain + through_reflection.chain
@@ -496,12 +623,19 @@ module ActiveRecord
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
- # Gets an array of possible <tt>:through</tt> source reflection names:
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
#
- # [:singularized, :pluralized]
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.source_reflection_names
+ # # => [:tag, :tags]
#
def source_reflection_names
- @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
+ @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
end
def source_options
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 4d24654015..d37471e9ad 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
:extending]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
- :reverse_order, :distinct, :create_with]
+ :reverse_order, :distinct, :create_with, :uniq]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
@@ -26,7 +26,6 @@ module ActiveRecord
@klass = klass
@table = table
@values = values
- @implicit_readonly = nil
@loaded = false
@default_scoped = false
end
@@ -39,7 +38,7 @@ module ActiveRecord
reset
end
- def insert(values)
+ def insert(values) # :nodoc:
primary_key_value = nil
if primary_key && Hash === values
@@ -56,16 +55,7 @@ module ActiveRecord
im = arel.create_insert
im.into @table
- conn = @klass.connection
-
- substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
- binds = substitutes.map do |arel_attr, value|
- [@klass.columns_hash[arel_attr.name], value]
- end
-
- substitutes.each_with_index do |tuple, i|
- tuple[1] = conn.substitute_at(binds[i][0], i)
- end
+ substitutes, binds = substitute_values values
if values.empty? # empty insert
im.values = Arel.sql(connection.empty_insert_statement_value)
@@ -73,7 +63,7 @@ module ActiveRecord
im.insert substitutes
end
- conn.insert(
+ @klass.connection.insert(
im,
'SQL',
primary_key,
@@ -82,6 +72,29 @@ module ActiveRecord
binds)
end
+ def update_record(values, id, id_was) # :nodoc:
+ substitutes, binds = substitute_values values
+ um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes)
+
+ @klass.connection.update(
+ um,
+ 'SQL',
+ binds)
+ end
+
+ def substitute_values(values) # :nodoc:
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
+ binds = substitutes.map do |arel_attr, value|
+ [@klass.columns_hash[arel_attr.name], value]
+ end
+
+ substitutes.each_with_index do |tuple, i|
+ tuple[1] = @klass.connection.substitute_at(binds[i][0], i)
+ end
+
+ [substitutes, binds]
+ end
+
# Initializes new record from relation while maintaining the current
# scope.
#
@@ -141,34 +154,58 @@ module ActiveRecord
first || new(attributes, &block)
end
- # Finds the first record with the given attributes, or creates a record with the attributes
- # if one is not found.
+ # Finds the first record with the given attributes, or creates a record
+ # with the attributes if one is not found:
#
- # ==== Examples
- # # Find the first user named Penélope or create a new one.
+ # # Find the first user named "Penélope" or create a new one.
# User.find_or_create_by(first_name: 'Penélope')
- # # => <User id: 1, first_name: 'Penélope', last_name: nil>
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
#
- # # Find the first user named Penélope or create a new one.
+ # # Find the first user named "Penélope" or create a new one.
# # We already have one so the existing record will be returned.
# User.find_or_create_by(first_name: 'Penélope')
- # # => <User id: 1, first_name: 'Penélope', last_name: nil>
+ # # => #<User id: 1, first_name: "Penélope", last_name: nil>
#
- # # Find the first user named Scarlett or create a new one with a particular last name.
+ # # Find the first user named "Scarlett" or create a new one with
+ # # a particular last name.
# User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
- # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
#
- # # Find the first user named Scarlett or create a new one with a different last name.
- # # We already have one so the existing record will be returned.
+ # This method accepts a block, which is passed down to +create+. The last example
+ # above can be alternatively written this way:
+ #
+ # # Find the first user named "Scarlett" or create a new one with a
+ # # different last name.
# User.find_or_create_by(first_name: 'Scarlett') do |user|
- # user.last_name = "O'Hara"
+ # user.last_name = 'Johansson'
# end
- # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
+ #
+ # This method always returns a record, but if creation was attempted and
+ # failed due to validation errors it won't be persisted, you get what
+ # +create+ returns in such situation.
+ #
+ # Please note *this method is not atomic*, it runs first a SELECT, and if
+ # there are no results an INSERT is attempted. If there are other threads
+ # or processes there is a race condition between both calls and it could
+ # be the case that you end up with two similar records.
+ #
+ # Whether that is a problem or not depends on the logic of the
+ # application, but in the particular case in which rows have a UNIQUE
+ # constraint an exception may be raised, just retry:
+ #
+ # begin
+ # CreditAccount.find_or_create_by(user_id: user.id)
+ # rescue ActiveRecord::RecordNotUnique
+ # retry
+ # end
+ #
def find_or_create_by(attributes, &block)
find_by(attributes) || create(attributes, &block)
end
- # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
+ # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception
+ # is raised if the created record is invalid.
def find_or_create_by!(attributes, &block)
find_by(attributes) || create!(attributes, &block)
end
@@ -210,7 +247,7 @@ module ActiveRecord
def empty?
return @records.empty? if loaded?
- c = count
+ c = count(:all)
c.respond_to?(:zero?) ? c.zero? : c.empty?
end
@@ -475,11 +512,13 @@ module ActiveRecord
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
def where_values_hash
- equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
+ scope = with_default_scope
+ equalities = scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
node.left.relation.name == table_name
}
- binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
+ binds = Hash[scope.bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
+ binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }])
Hash[equalities.map { |where|
name = where.left.name
@@ -566,10 +605,7 @@ module ActiveRecord
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
- @records.each { |record| record.readonly! } if readonly
+ @records.each { |record| record.readonly! } if readonly_value
else
@records = default_scoped.to_a
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index be011b22af..4becf3980d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# Calculates the average value on a given column. Returns +nil+ if there's
# no row. See +calculate+ for examples with options.
#
- # Person.average('age') # => 35.8
+ # Person.average(:age) # => 35.8
def average(column_name, options = {})
calculate(:average, column_name, options)
end
@@ -36,7 +36,7 @@ module ActiveRecord
# with the same data type of the column, or +nil+ if there's no row. See
# +calculate+ for examples with options.
#
- # Person.minimum('age') # => 7
+ # Person.minimum(:age) # => 7
def minimum(column_name, options = {})
calculate(:minimum, column_name, options)
end
@@ -45,7 +45,7 @@ module ActiveRecord
# with the same data type of the column, or +nil+ if there's no row. See
# +calculate+ for examples with options.
#
- # Person.maximum('age') # => 93
+ # Person.maximum(:age) # => 93
def maximum(column_name, options = {})
calculate(:maximum, column_name, options)
end
@@ -54,17 +54,9 @@ module ActiveRecord
# with the same data type of the column, 0 if there's no row. See
# +calculate+ for examples with options.
#
- # Person.sum('age') # => 4562
+ # Person.sum(:age) # => 4562
def sum(*args)
- if block_given?
- ActiveSupport::Deprecation.warn(
- "Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \
- "If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`."
- )
- self.to_a.sum(*args) {|*block_args| yield(*block_args)}
- else
- calculate(:sum, *args)
- end
+ calculate(:sum, *args)
end
# This calculates aggregate values in the given column. Methods for count, sum, average,
@@ -82,7 +74,7 @@ module ActiveRecord
# puts values["Drake"]
# # => 43
#
- # drake = Family.find_by_last_name('Drake')
+ # drake = Family.find_by(last_name: 'Drake')
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
# # => 43
@@ -101,6 +93,10 @@ module ActiveRecord
def calculate(operation, column_name, options = {})
relation = with_default_scope
+ if column_name.is_a?(Symbol) && attribute_aliases.key?(column_name.to_s)
+ column_name = attribute_aliases[column_name.to_s].to_sym
+ end
+
if relation.equal?(self)
if has_include?(column_name)
construct_relation_for_association_calculations.calculate(operation, column_name, options)
@@ -110,8 +106,6 @@ module ActiveRecord
else
relation.calculate(operation, column_name, options)
end
- rescue ThrowResult
- 0
end
# Use <tt>pluck</tt> as a shortcut to select one or more attributes without
@@ -135,7 +129,7 @@ module ActiveRecord
# # SELECT people.id, people.name FROM people
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
#
- # Person.uniq.pluck(:role)
+ # Person.pluck('DISTINCT role')
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
#
@@ -149,11 +143,17 @@ module ActiveRecord
#
def pluck(*column_names)
column_names.map! do |column_name|
- if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
- "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
- else
- column_name
+ if column_name.is_a?(Symbol)
+ if attribute_aliases.key?(column_name.to_s)
+ column_name = attribute_aliases[column_name.to_s].to_sym
+ end
+
+ if self.columns_hash.key?(column_name.to_s)
+ column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
+ end
end
+
+ column_name
end
if has_include?(column_names.first)
@@ -207,15 +207,18 @@ module ActiveRecord
end
if operation == "count"
- column_name ||= (select_for_count || :all)
+ if select_values.present?
+ column_name ||= select_values.join(", ")
+ else
+ column_name ||= :all
+ end
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
distinct = true
end
column_name = primary_key if column_name == :all && distinct
-
- distinct = nil if column_name =~ /\s*DISTINCT\s+/i
+ distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
end
if group_values.any?
@@ -376,13 +379,6 @@ module ActiveRecord
column ? column.type_cast(value) : value
end
- def select_for_count
- if select_values.present?
- select = select_values.join(", ")
- select if select !~ /[,*]/
- end
- end
-
def build_count_subquery(relation, column_name, distinct)
column_alias = Arel.sql('count_column')
subquery_alias = Arel.sql('subquery_for_count')
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 1b6239eb38..8d6740246c 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -14,14 +14,14 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
- module ClassSpecificRelation
+ module ClassSpecificRelation # :nodoc:
extend ActiveSupport::Concern
included do
@delegation_mutex = Mutex.new
end
- module ClassMethods
+ module ClassMethods # :nodoc:
def name
superclass.name
end
@@ -70,7 +70,7 @@ module ActiveRecord
end
end
- module ClassMethods
+ module ClassMethods # :nodoc:
@@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
def new(klass, *args)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index e2685d0478..cbb2803593 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -130,8 +130,8 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Returns +true+ if a record exists in the table that matches the +id+ or
- # conditions given, or +false+ otherwise. The argument can take six forms:
+ # Returns truthy if a record exists in the table that matches the +id+ or
+ # conditions given, or falsy otherwise. The argument can take six forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
@@ -160,8 +160,9 @@ module ActiveRecord
conditions = conditions.id if Base === conditions
return false if !conditions
- join_dependency = construct_join_dependency_for_association_find
- relation = construct_relation_for_association_find(join_dependency)
+ relation = construct_relation_for_association_find(construct_join_dependency)
+ return false if ActiveRecord::NullRelation === relation
+
relation = relation.except(:select, :order).select("1 AS one").limit(1)
case conditions
@@ -171,66 +172,82 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
end
- connection.select_value(relation, "#{name} Exists", relation.bind_values)
- rescue ThrowResult
- false
+ relation = relation.with_default_scope
+ connection.select_value(relation.arel, "#{name} Exists", relation.bind_values)
+ end
+
+ # This method is called whenever no records are found with either a single
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
+ #
+ # The error message is different depending on whether a single id or
+ # multiple ids are provided. If multiple ids are provided, then the number
+ # of results obtained should be provided in the +result_size+ argument and
+ # the expected number of results should be provided in the +expected_size+
+ # argument.
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
+ conditions = arel.where_sql
+ conditions = " [#{conditions}]" if conditions
+
+ if Array(ids).size == 1
+ error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
+ else
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
+ end
+
+ raise RecordNotFound, error
end
protected
def find_with_associations
- join_dependency = construct_join_dependency_for_association_find
+ join_dependency = construct_join_dependency
relation = construct_relation_for_association_find(join_dependency)
- rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
- join_dependency.instantiate(rows)
- rescue ThrowResult
- []
+ if ActiveRecord::NullRelation === relation
+ []
+ else
+ rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
+ join_dependency.instantiate(rows)
+ end
end
- def construct_join_dependency_for_association_find
+ def construct_join_dependency(joins = [])
including = (eager_load_values + includes_values).uniq
- ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
end
def construct_relation_for_association_calculations
- including = (eager_load_values + includes_values).uniq
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
- relation = except(:includes, :eager_load, :preload)
- apply_join_dependency(relation, join_dependency)
+ apply_join_dependency(self, construct_join_dependency(arel.froms.first))
end
def construct_relation_for_association_find(join_dependency)
- relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
+ relation = except(:select).select(join_dependency.columns)
apply_join_dependency(relation, join_dependency)
end
def apply_join_dependency(relation, join_dependency)
- join_dependency.join_associations.each do |association|
- relation = association.join_relation(relation)
- end
-
- limitable_reflections = using_limitable_reflections?(join_dependency.reflections)
+ relation = relation.except(:includes, :eager_load, :preload)
+ relation = join_dependency.join_relation(relation)
- if !limitable_reflections && relation.limit_value
- limited_id_condition = construct_limited_ids_condition(relation.except(:select))
- relation = relation.where(limited_id_condition)
+ if using_limitable_reflections?(join_dependency.reflections)
+ relation
+ else
+ if relation.limit_value
+ limited_ids = limited_ids_for(relation)
+ limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
+ end
+ relation.except(:limit, :offset)
end
-
- relation = relation.except(:limit, :offset) unless limitable_reflections
-
- relation
end
- def construct_limited_ids_condition(relation)
- orders = relation.order_values.map { |val| val.presence }.compact
- values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
+ def limited_ids_for(relation)
+ values = @klass.connection.columns_for_distinct(
+ "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
- relation = relation.dup.select(values)
+ relation = relation.except(:select).select(values).distinct!
id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
- ids_array = id_rows.map {|row| row[primary_key]}
-
- ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
+ id_rows.map {|row| row[primary_key]}
end
def find_with_ids(*ids)
@@ -259,11 +276,7 @@ module ActiveRecord
relation.bind_values += [[column, id]]
record = relation.take
- unless record
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
- end
+ raise_record_not_found_exception!(id, 0, 1) unless record
record
end
@@ -286,12 +299,7 @@ module ActiveRecord
if result.size == expected_size
result
else
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
-
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
- raise RecordNotFound, error
+ raise_record_not_found_exception!(ids, result.size, expected_size)
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index eb23e92fb8..c114ea0c0d 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
class Merger # :nodoc:
- attr_reader :relation, :values
+ attr_reader :relation, :values, :other
def initialize(relation, other)
if other.default_scoped? && other.klass != relation.klass
@@ -48,11 +48,12 @@ module ActiveRecord
@relation = relation
@values = other.values
+ @other = other
end
NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
Relation::MULTI_VALUE_METHODS -
- [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
+ [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -66,15 +67,47 @@ module ActiveRecord
merge_multi_values
merge_single_values
+ merge_joins
relation
end
private
+ def merge_joins
+ return if values[:joins].blank?
+
+ if other.klass == relation.klass
+ relation.joins!(*values[:joins])
+ else
+ joins_dependency, rest = values[:joins].partition do |join|
+ case join
+ when Hash, Symbol, Array
+ true
+ else
+ false
+ end
+ end
+
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
+ joins_dependency,
+ [])
+ relation.joins! rest
+
+ @relation = join_dependency.join_relation(relation)
+ end
+ end
+
def merge_multi_values
- relation.where_values = merged_wheres
- relation.bind_values = merged_binds
+ lhs_wheres = relation.where_values
+ rhs_wheres = values[:where] || []
+ lhs_binds = relation.bind_values
+ rhs_binds = values[:bind] || []
+
+ removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
+
+ relation.where_values = kept + rhs_wheres
+ relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds
if values[:reordering]
# override any order specified in the original relation
@@ -97,35 +130,28 @@ module ActiveRecord
end
end
- def merged_binds
- if values[:bind]
- (relation.bind_values + values[:bind]).uniq(&:first)
- else
- relation.bind_values
- end
- end
+ def filter_binds(lhs_binds, removed_wheres)
+ return lhs_binds if removed_wheres.empty?
- def merged_wheres
- values[:where] ||= []
+ set = Set.new removed_wheres.map { |x| x.left.name }
+ lhs_binds.dup.delete_if { |col,_| set.include? col.name }
+ end
- if values[:where].empty? || relation.where_values.empty?
- relation.where_values + values[:where]
- else
- # Remove equalities from the existing relation with a LHS which is
- # present in the relation being merged in.
+ # Remove equalities from the existing relation with a LHS which is
+ # present in the relation being merged in.
+ # returns [things_to_remove, things_to_keep]
+ def partition_overwrites(lhs_wheres, rhs_wheres)
+ if lhs_wheres.empty? || rhs_wheres.empty?
+ return [[], lhs_wheres]
+ end
- seen = Set.new
- values[:where].each { |w|
- if w.respond_to?(:operator) && w.operator == :==
- seen << w.left
- end
- }
+ nodes = rhs_wheres.find_all do |w|
+ w.respond_to?(:operator) && w.operator == :==
+ end
+ seen = Set.new(nodes) { |node| node.left }
- relation.where_values.reject { |w|
- w.respond_to?(:operator) &&
- w.operator == :== &&
- seen.include?(w.left)
- } + values[:where]
+ lhs_wheres.partition do |w|
+ w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index f44d46d15b..b7609c97b5 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -6,6 +6,10 @@ module ActiveRecord
attributes.each do |column, value|
table = default_table
+ if column.is_a?(Symbol) && klass.attribute_aliases.key?(column.to_s)
+ column = klass.attribute_aliases[column.to_s]
+ end
+
if value.is_a?(Hash)
if value.empty?
queries << '1=0'
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 257221174b..ca1de2d4dc 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -34,7 +34,6 @@ module ActiveRecord
#
# User.where.not(name: "Jon", role: "admin")
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
- #
def not(opts, *rest)
where_value = @scope.send(:build_where, opts, rest).map do |rel|
case rel
@@ -341,6 +340,9 @@ module ActiveRecord
# User.where(name: "John", active: true).unscope(where: :name)
# == User.where(active: true)
#
+ # This method is applied before the default_scope is applied. So the conditions
+ # specified in default_scope will not be removed.
+ #
# Note that this method is more generalized than ActiveRecord::SpawnMethods#except
# because #except will only affect a particular relation's values. It won't wipe
# the order, grouping, etc. when that relation is merged. For example:
@@ -353,7 +355,7 @@ module ActiveRecord
spawn.unscope!(*args)
end
- def unscope!(*args)
+ def unscope!(*args) # :nodoc:
args.flatten!
args.each do |scope|
@@ -552,7 +554,6 @@ module ActiveRecord
# Order.having('SUM(price) > 30').group('user_id')
def having(opts, *rest)
opts.blank? ? self : spawn.having!(opts, *rest)
- spawn.having!(opts, *rest)
end
def having!(opts, *rest) # :nodoc:
@@ -863,8 +864,6 @@ module ActiveRecord
return [] if joins.empty?
- @implicit_readonly = true
-
joins.map do |join|
case join
when Array
@@ -934,9 +933,7 @@ module ActiveRecord
association_joins = buckets[:association_join] || []
stashed_association_joins = buckets[:stashed_join] || []
join_nodes = (buckets[:join_node] || []).uniq
- string_joins = (buckets[:string_join] || []).map { |x|
- x.strip
- }.uniq
+ string_joins = (buckets[:string_join] || []).map { |x| x.strip }.uniq
join_list = join_nodes + custom_join_ast(manager, string_joins)
@@ -948,8 +945,6 @@ module ActiveRecord
join_dependency.graft(*stashed_association_joins)
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
-
# FIXME: refactor this to build an AST
join_dependency.join_associations.each do |association|
association.join_to(manager)
@@ -962,7 +957,6 @@ module ActiveRecord
def build_select(arel, selects)
unless selects.empty?
- @implicit_readonly = false
arel.project(*selects)
else
arel.project(@klass.arel_table[Arel.star])
@@ -1016,7 +1010,7 @@ module ActiveRecord
end
def validate_order_args(args)
- args.select { |a| Hash === a }.each do |h|
+ args.grep(Hash) do |h|
unless (h.values - [:asc, :desc]).empty?
raise ArgumentError, 'Direction should be :asc or :desc'
end
diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb
new file mode 100644
index 0000000000..63e6738622
--- /dev/null
+++ b/activerecord/lib/active_record/runtime_registry.rb
@@ -0,0 +1,17 @@
+require 'active_support/per_thread_registry'
+
+module ActiveRecord
+ # This is a thread locals registry for Active Record. For example:
+ #
+ # ActiveRecord::RuntimeRegistry.connection_handler
+ #
+ # returns the connection handler local to the current thread.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class RuntimeRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_accessor :connection_handler, :sql_runtime, :connection_id
+ end
+end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 3c5b871e99..0ed97b66d6 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -89,7 +89,7 @@ module ActiveRecord
attrs = expand_hash_conditions_for_aggregates(attrs)
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
- PredicateBuilder.build_from_hash(self.class, attrs, table).map { |b|
+ PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
connection.visitor.accept b
}.join(' AND ')
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 3259dbbd80..4bfd0167a4 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -43,7 +43,7 @@ module ActiveRecord
unless info[:version].blank?
initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], migrations_paths)
+ connection.assume_migrated_upto_version(info[:version], migrations_paths)
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 10c6d272cd..1181cc739e 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -106,9 +106,12 @@ HEADER
end
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
- if columns.detect { |c| c.name == pk }
+ pkcol = columns.detect { |c| c.name == pk }
+ if pkcol
if pk != 'id'
tbl.print %Q(, primary_key: "#{pk}")
+ elsif pkcol.sql_type == 'uuid'
+ tbl.print ", id: :uuid"
end
else
tbl.print ", id: false"
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 6077144265..fee19b1096 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -4,31 +4,37 @@ require 'active_record/base'
module ActiveRecord
class SchemaMigration < ActiveRecord::Base
+ class << self
- def self.table_name
- "#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}"
- end
+ def table_name
+ "#{table_name_prefix}schema_migrations#{table_name_suffix}"
+ end
- def self.index_name
- "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
- end
+ def index_name
+ "#{table_name_prefix}unique_schema_migrations#{table_name_suffix}"
+ end
- def self.create_table(limit=nil)
- unless connection.table_exists?(table_name)
- version_options = {null: false}
- version_options[:limit] = limit if limit
+ def table_exists?
+ connection.table_exists?(table_name)
+ end
+
+ def create_table(limit=nil)
+ unless table_exists?
+ version_options = {null: false}
+ version_options[:limit] = limit if limit
- connection.create_table(table_name, id: false) do |t|
- t.column :version, :string, version_options
+ connection.create_table(table_name, id: false) do |t|
+ t.column :version, :string, version_options
+ end
+ connection.add_index table_name, :version, unique: true, name: index_name
end
- connection.add_index table_name, :version, unique: true, name: index_name
end
- end
- def self.drop_table
- if connection.table_exists?(table_name)
- connection.remove_index table_name, name: index_name
- connection.drop_table(table_name)
+ def drop_table
+ if table_exists?
+ connection.remove_index table_name, name: index_name
+ connection.drop_table(table_name)
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 9746b1c3c2..0cf3d59985 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -1,3 +1,5 @@
+require 'active_support/per_thread_registry'
+
module ActiveRecord
module Scoping
extend ActiveSupport::Concern
@@ -9,11 +11,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- Thread.current["#{self}_current_scope"]
+ ScopeRegistry.value_for(:current_scope, base_class.to_s)
end
def current_scope=(scope) #:nodoc:
- Thread.current["#{self}_current_scope"] = scope
+ ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
end
end
@@ -24,5 +26,57 @@ module ActiveRecord
send("#{att}=", value) if respond_to?("#{att}=")
end
end
+
+ # This class stores the +:current_scope+ and +:ignore_default_scope+ values
+ # for different classes. The registry is stored as a thread local, which is
+ # accessed through +ScopeRegistry.current+.
+ #
+ # This class allows you to store and get the scope values on different
+ # classes and different types of scopes. For example, if you are attempting
+ # to get the current_scope for the +Board+ model, then you would use the
+ # following code:
+ #
+ # registry = ActiveRecord::Scoping::ScopeRegistry
+ # registry.set_value_for(:current_scope, "Board", some_new_scope)
+ #
+ # Now when you run:
+ #
+ # registry.value_for(:current_scope, "Board")
+ #
+ # You will obtain whatever was defined in +some_new_scope+. The +value_for+
+ # and +set_value_for+ methods are delegated to the current +ScopeRegistry+
+ # object, so the above example code can also be called as:
+ #
+ # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
+ # "Board", some_new_scope)
+ class ScopeRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
+
+ def initialize
+ @registry = Hash.new { |hash, key| hash[key] = {} }
+ end
+
+ # Obtains the value for a given +scope_name+ and +variable_name+.
+ def value_for(scope_type, variable_name)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name]
+ end
+
+ # Sets the +value+ for a given +scope_type+ and +variable_name+.
+ def set_value_for(scope_type, variable_name, value)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name] = value
+ end
+
+ private
+
+ def raise_invalid_scope_type!(scope_type)
+ if !VALID_SCOPE_TYPES.include?(scope_type)
+ raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index cde4f29d08..d37d33d552 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -5,8 +5,17 @@ module ActiveRecord
included do
# Stores the default scope for the class.
- class_attribute :default_scopes, instance_writer: false
+ class_attribute :default_scopes, instance_writer: false, instance_predicate: false
+
self.default_scopes = []
+
+ def self.default_scopes?
+ ActiveSupport::Deprecation.warn(
+ "#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
+ )
+
+ !!self.default_scopes
+ end
end
module ClassMethods
@@ -111,11 +120,11 @@ module ActiveRecord
end
def ignore_default_scope? # :nodoc:
- Thread.current["#{self}_ignore_default_scope"]
+ ScopeRegistry.value_for(:ignore_default_scope, self)
end
def ignore_default_scope=(ignore) # :nodoc:
- Thread.current["#{self}_ignore_default_scope"] = ignore
+ ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 12317601b6..da73bead32 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -160,13 +160,8 @@ module ActiveRecord
singleton_class.send(:define_method, name) do |*args|
if body.respond_to?(:call)
- scope = extension ? body.call(*args).extending(extension) : body.call(*args)
-
- if scope
- default_scoped = scope.default_scoped
- scope = relation.merge(scope)
- scope.default_scoped = default_scoped
- end
+ scope = all.scoping { body.call(*args) }
+ scope = scope.extending(extension) if extension
else
scope = body
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
new file mode 100644
index 0000000000..dd4ee0c4a0
--- /dev/null
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -0,0 +1,26 @@
+module ActiveRecord
+
+ # Statement cache is used to cache a single statement in order to avoid creating the AST again.
+ # Initializing the cache is done by passing the statement in the initialization block:
+ #
+ # cache = ActiveRecord::StatementCache.new do
+ # Book.where(name: "my book").limit(100)
+ # end
+ #
+ # The cached statement is executed by using the +execute+ method:
+ #
+ # cache.execute
+ #
+ # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
+ # Database is queried when +to_a+ is called on the relation.
+ class StatementCache
+ def initialize
+ @relation = yield
+ raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
+ end
+
+ def execute
+ @relation.dup.to_a
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 4fa7cf8a7d..3e8b79c7a0 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -3,10 +3,41 @@ module ActiveRecord
class DatabaseAlreadyExists < StandardError; end # :nodoc:
class DatabaseNotSupported < StandardError; end # :nodoc:
- module DatabaseTasks # :nodoc:
+ # <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates
+ # logic behind common tasks used to manage database and migrations.
+ #
+ # The tasks defined here are used in rake tasks provided by Active Record.
+ #
+ # In order to use DatabaseTasks, a few config values need to be set. All the needed
+ # config values are set by Rails already, so it's necessary to do it only if you
+ # want to change the defaults or when you want to use Active Record outside of Rails
+ # (in such case after configuring the database tasks, you can also use the rake tasks
+ # defined in Active Record).
+ #
+ #
+ # The possible config values are:
+ #
+ # * +env+: current environment (like Rails.env).
+ # * +database_configuration+: configuration of your databases (as in +config/database.yml+).
+ # * +db_dir+: your +db+ directory.
+ # * +fixtures_path+: a path to fixtures directory.
+ # * +migrations_paths+: a list of paths to directories with migrations.
+ # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
+ #
+ # Example usage of +DatabaseTasks+ outside Rails could look as such:
+ #
+ # include ActiveRecord::Tasks
+ # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
+ # DatabaseTasks.db_dir = 'db'
+ # # other settings...
+ #
+ # DatabaseTasks.create_current('production')
+ module DatabaseTasks
extend self
attr_writer :current_config
+ attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir,
+ :fixtures_path, :env
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
@@ -15,12 +46,16 @@ module ActiveRecord
@tasks[pattern] = task
end
- register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
- register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
- register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+ register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
+ register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
+ register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+
+ register_task(/firebird/, ActiveRecord::Tasks::FirebirdDatabaseTasks)
+ register_task(/sqlserver/, ActiveRecord::Tasks::SqlserverDatabaseTasks)
+ register_task(/(oci|oracle)/, ActiveRecord::Tasks::OracleDatabaseTasks)
def current_config(options = {})
- options.reverse_merge! :env => Rails.env
+ options.reverse_merge! :env => env
if options.has_key?(:config)
@current_config = options[:config]
else
@@ -46,7 +81,7 @@ module ActiveRecord
each_local_configuration { |configuration| create configuration }
end
- def create_current(environment = Rails.env)
+ def create_current(environment = env)
each_current_configuration(environment) { |configuration|
create configuration
}
@@ -69,7 +104,7 @@ module ActiveRecord
each_local_configuration { |configuration| drop configuration }
end
- def drop_current(environment = Rails.env)
+ def drop_current(environment = env)
each_current_configuration(environment) { |configuration|
drop configuration
}
@@ -79,7 +114,7 @@ module ActiveRecord
drop database_url_config
end
- def charset_current(environment = Rails.env)
+ def charset_current(environment = env)
charset ActiveRecord::Base.configurations[environment]
end
@@ -88,7 +123,7 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).charset
end
- def collation_current(environment = Rails.env)
+ def collation_current(environment = env)
collation ActiveRecord::Base.configurations[environment]
end
@@ -113,6 +148,24 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
end
+ def check_schema_file(filename)
+ unless File.exists?(filename)
+ message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
+ message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails)
+ Kernel.abort message
+ end
+ end
+
+ def load_seed
+ if seed_loader
+ seed_loader.load_seed
+ else
+ raise "You tried to load seed data, but no seed loader is specified. Please specify seed " +
+ "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" +
+ "Seed loader should respond to load_seed method"
+ end
+ end
+
private
def database_url_config
@@ -130,7 +183,7 @@ module ActiveRecord
def each_current_configuration(environment)
environments = [environment]
- environments << 'test' if environment.development?
+ environments << 'test' if environment == 'development'
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
diff --git a/activerecord/lib/active_record/tasks/firebird_database_tasks.rb b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
new file mode 100644
index 0000000000..98014a38ea
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
@@ -0,0 +1,56 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class FirebirdDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.recreate_database!
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -a #{db_string} > #{filename}"
+ end
+
+ def structure_load(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -i #{filename} #{db_string}"
+ end
+
+ private
+
+ def set_firebird_env(config)
+ ENV['ISC_USER'] = config['username'].to_s if config['username']
+ ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
+ end
+
+ def firebird_db_string(config)
+ FireRuby::Database.db_string_for(config.symbolize_keys)
+ end
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/oracle_database_tasks.rb b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
new file mode 100644
index 0000000000..de3aa50e5e
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
@@ -0,0 +1,45 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class OracleDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.structure_drop.split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ establish_connection(configuration)
+ File.open(filename, "w:utf-8") { |f| f << connection.structure_dump }
+ end
+
+ def structure_load(filename)
+ establish_connection(configuration)
+ IO.read(filename).split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 0b1b030516..4413330fab 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -59,7 +59,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- Kernel.system("psql -f #{filename} #{configuration['database']}")
+ Kernel.system("psql -q -f #{filename} #{configuration['database']}")
end
private
diff --git a/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
new file mode 100644
index 0000000000..c718ee03a8
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
@@ -0,0 +1,48 @@
+require 'shellwords'
+
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SqlserverDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ test = configuration.deep_dup
+ test_database = test['database']
+ test['database'] = 'master'
+ establish_connection(test)
+ connection.recreate_database!(test_database)
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ Kernel.system("smoscript -s #{configuration['host']} -d #{configuration['database']} -u #{configuration['username']} -p #{configuration['password']} -f #{filename} -A -U")
+ end
+
+ def structure_load(filename)
+ Kernel.system("sqlcmd -S #{configuration['host']} -d #{configuration['database']} -U #{configuration['username']} -P #{configuration['password']} -i #{filename}")
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index e9142481a3..1b4c473bfc 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -35,8 +35,7 @@ module ActiveRecord
def assert_queries(num = 1, options = {})
ignore_none = options.fetch(:ignore_none) { num == :any }
SQLCounter.clear_log
- yield
- ensure
+ x = yield
the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
if num == :any
assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed."
@@ -44,6 +43,7 @@ module ActiveRecord
mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}"
assert_equal num, the_log.size, mesg
end
+ x
end
def assert_no_queries(&block)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4dbd668fcf..77634b40bb 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -10,7 +10,9 @@ module ActiveRecord
end
included do
- define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
+ define_callbacks :commit, :rollback,
+ terminator: ->(_, result) { result == false },
+ scope: [:kind, :name]
end
# = Active Record Transactions
@@ -339,8 +341,12 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
- @_start_transaction_state[:new_record] = @new_record
- @_start_transaction_state[:destroyed] = @destroyed
+ 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[:level] = (@_start_transaction_state[:level] || 0) + 1
@_start_transaction_state[:frozen?] = @attributes.frozen?
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 7f1972ccf9..b4785d3ba4 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -2,15 +2,15 @@ module ActiveRecord
module Validations
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
def validate_each(record, attribute, value)
- if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any?
+ if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
record.errors.add(attribute, :invalid, options.merge(:value => value))
end
end
end
module ClassMethods
- # Validates whether the associated object or objects are all valid
- # themselves. Works with any kind of association.
+ # Validates whether the associated object or objects are all valid.
+ # Works with any kind of association.
#
# class Book < ActiveRecord::Base
# has_many :pages
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index a705d8c2c4..52e46e1ffe 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -7,11 +7,7 @@ module ActiveRecord
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
end
super({ case_sensitive: true }.merge!(options))
- end
-
- # Unfortunately, we have to tie Uniqueness validators to a class.
- def setup(klass)
- @klass = klass
+ @klass = options[:class]
end
def validate_each(record, attribute, value)
@@ -34,7 +30,6 @@ module ActiveRecord
end
protected
-
# The check for an existing value should be run from a class that
# isn't abstract. This means working down from the current class
# (self), to the first non-abstract class. Since classes don't know
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index f2b041ad97..de5fd05468 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,7 +1,7 @@
module ActiveRecord
# Returns the version of the currently loaded ActiveRecord as a Gem::Version
def self.version
- Gem::Version.new "4.0.0.beta1"
+ Gem::Version.new "4.1.0.beta"
end
module VERSION #:nodoc:
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index b967bb6e0f..3968acba64 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -14,6 +14,10 @@ module ActiveRecord
protected
attr_reader :migration_action, :join_tables
+ # sets the default migration template that is being used for the generation of the migration
+ # depending on the arguments which would be sent out in the command line, the migration template
+ # and the table name instance variables are setup.
+
def set_local_assigns!
@migration_template = "migration.rb"
case file_name
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 40e134e626..7e8d68ce69 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -12,6 +12,9 @@ module ActiveRecord
class_option :parent, :type => :string, :desc => "The parent class for the generated model"
class_option :indexes, :type => :boolean, :default => true, :desc => "Add indexes for references and belongs_to columns"
+
+ # creates the migration file for the model.
+
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
@@ -39,6 +42,7 @@ module ActiveRecord
protected
+ # Used by the migration template to determine the parent name of the model
def parent_class_name
options[:parent] || "ActiveRecord::Base"
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index e6d0183b11..0878925a6c 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -2,25 +2,24 @@ require "cases/helper"
class ActiveSchemaTest < ActiveRecord::TestCase
def setup
- ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
+ @connection = ActiveRecord::Base.remove_connection
+ ActiveRecord::Base.establish_connection(@connection)
+
+ ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
- remove_method :execute
def execute(sql, name = nil) return sql end
end
end
def teardown
- ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
- remove_method :execute
- alias_method :execute, :execute_without_stub
- end
+ ActiveRecord::Base.remove_connection
+ ActiveRecord::Base.establish_connection(@connection)
end
def test_add_index
# add_index calls index_name_exists? which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*|
- false
- end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
@@ -58,8 +57,6 @@ class ActiveSchemaTest < ActiveRecord::TestCase
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
-
- ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?)
end
def test_drop_table
@@ -121,22 +118,20 @@ class ActiveSchemaTest < ActiveRecord::TestCase
private
def with_real_execute
- #we need to actually modify some data, so we make execute point to the original method
- ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
+ ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_with_stub, :execute
remove_method :execute
alias_method :execute, :execute_without_stub
end
+
yield
ensure
- #before finishing, we restore the alias to the mock-up method
- ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
+ ActiveRecord::Base.connection.singleton_class.class_eval do
remove_method :execute
alias_method :execute, :execute_with_stub
end
end
-
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
end
diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb
index 40af317ad1..f4e7a3ef0a 100644
--- a/activerecord/test/cases/adapters/mysql/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql/enum_test.rb
@@ -5,6 +5,6 @@ class MysqlEnumTest < ActiveRecord::TestCase
end
def test_enum_limit
- assert_equal 5, EnumTest.columns.first.limit
+ assert_equal 6, EnumTest.columns.first.limit
end
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index a75883cd3a..4a23287448 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -95,14 +95,27 @@ module ActiveRecord
assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq
end
+ def test_tinyint_integer_typecasting
+ @conn.exec_query('drop table if exists ex_with_non_boolean_tinyint_column')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex_with_non_boolean_tinyint_column` (
+ `status` TINYINT(4))
+ eosql
+ insert(@conn, { 'status' => 2 }, 'ex_with_non_boolean_tinyint_column')
+
+ result = @conn.exec_query('SELECT status FROM ex_with_non_boolean_tinyint_column')
+
+ assert_equal 2, result.column_types['status'].type_cast(result.last['status'])
+ end
+
private
- def insert(ctx, data)
+ def insert(ctx, data, table='ex')
binds = data.map { |name, value|
- [ctx.columns('ex').find { |x| x.name == name }, value]
+ [ctx.columns(table).find { |x| x.name == name }, value]
}
columns = binds.map(&:first).map(&:name)
- sql = "INSERT INTO ex (#{columns.join(", ")})
+ sql = "INSERT INTO #{table} (#{columns.join(", ")})
VALUES (#{(['?'] * columns.length).join(', ')})"
ctx.exec_insert(sql, 'SQL', binds)
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 8a2a7ef269..4ccf568406 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -2,25 +2,24 @@ require "cases/helper"
class ActiveSchemaTest < ActiveRecord::TestCase
def setup
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ @connection = ActiveRecord::Base.remove_connection
+ ActiveRecord::Base.establish_connection(@connection)
+
+ ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
- remove_method :execute
def execute(sql, name = nil) return sql end
end
end
def teardown
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
- remove_method :execute
- alias_method :execute, :execute_without_stub
- end
+ ActiveRecord::Base.remove_connection
+ ActiveRecord::Base.establish_connection(@connection)
end
def test_add_index
# add_index calls index_name_exists? which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
- false
- end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
@@ -58,8 +57,6 @@ class ActiveSchemaTest < ActiveRecord::TestCase
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
-
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
end
def test_drop_table
@@ -121,22 +118,20 @@ class ActiveSchemaTest < ActiveRecord::TestCase
private
def with_real_execute
- #we need to actually modify some data, so we make execute point to the original method
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_with_stub, :execute
remove_method :execute
alias_method :execute, :execute_without_stub
end
+
yield
ensure
- #before finishing, we restore the alias to the mock-up method
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ ActiveRecord::Base.connection.singleton_class.class_eval do
remove_method :execute
alias_method :execute, :execute_with_stub
end
end
-
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index f3a05e48ad..6dd9a5ec87 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -5,6 +5,6 @@ class Mysql2EnumTest < ActiveRecord::TestCase
end
def test_enum_limit
- assert_equal 5, EnumTest.columns.first.limit
+ assert_equal 6, EnumTest.columns.first.limit
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 16329689c0..22dd48e113 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -25,9 +25,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
def test_add_index
# add_index calls index_name_exists? which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*|
- false
- end
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.stubs(:index_name_exists?).returns(false)
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
@@ -51,8 +49,6 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
-
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 8774bf626f..61a3a2ba0f 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -81,6 +81,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_cycle(['1',nil,nil])
end
+ def test_insert_fixture
+ tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
+ @connection.insert_fixture({"tags" => tag_values}, "pg_arrays" )
+ assert_equal(PgArray.last.tags, tag_values)
+ end
+
private
def assert_cycle array
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
new file mode 100644
index 0000000000..489efac932
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -0,0 +1,104 @@
+# encoding: utf-8
+
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlByteaTest < ActiveRecord::TestCase
+ class ByteaDataType < ActiveRecord::Base
+ self.table_name = 'bytea_data_type'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ begin
+ @connection.transaction do
+ @connection.create_table('bytea_data_type') do |t|
+ t.binary 'payload'
+ t.binary 'serialized'
+ end
+ end
+ end
+ @column = ByteaDataType.columns.find { |c| c.name == 'payload' }
+ assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists bytea_data_type'
+ end
+
+ def test_column
+ assert_equal :binary, @column.type
+ end
+
+ def test_type_cast_binary_converts_the_encoding
+ assert @column
+
+ data = "\u001F\x8B"
+ assert_equal('UTF-8', data.encoding.name)
+ assert_equal('ASCII-8BIT', @column.type_cast(data).encoding.name)
+ end
+
+ def test_type_cast_binary_value
+ data = "\u001F\x8B".force_encoding("BINARY")
+ assert_equal(data, @column.type_cast(data))
+ end
+
+ def test_type_case_nil
+ assert_equal(nil, @column.type_cast(nil))
+ end
+
+ def test_read_value
+ data = "\u001F"
+ @connection.execute "insert into bytea_data_type (payload) VALUES ('#{data}')"
+ record = ByteaDataType.first
+ assert_equal(data, record.payload)
+ record.delete
+ end
+
+ def test_read_nil_value
+ @connection.execute "insert into bytea_data_type (payload) VALUES (null)"
+ record = ByteaDataType.first
+ assert_equal(nil, record.payload)
+ record.delete
+ end
+
+ def test_write_value
+ data = "\u001F"
+ record = ByteaDataType.create(payload: data)
+ refute record.new_record?
+ assert_equal(data, record.payload)
+ end
+
+ def test_write_binary
+ data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log'))
+ assert(data.size > 1)
+ record = ByteaDataType.create(payload: data)
+ refute record.new_record?
+ assert_equal(data, record.payload)
+ assert_equal(data, ByteaDataType.where(id: record.id).first.payload)
+ end
+
+ def test_write_nil
+ record = ByteaDataType.create(payload: nil)
+ refute record.new_record?
+ assert_equal(nil, record.payload)
+ assert_equal(nil, ByteaDataType.where(id: record.id).first.payload)
+ end
+
+ class Serializer
+ def load(str); str; end
+ def dump(str); str; end
+ end
+
+ def test_serialize
+ klass = Class.new(ByteaDataType) {
+ serialize :serialized, Serializer.new
+ }
+ obj = klass.new
+ obj.serialized = "hello world"
+ obj.save!
+ obj.reload
+ assert_equal "hello world", obj.serialized
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 1e6ae85a25..36d7294bc8 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -246,7 +246,7 @@ _SQL
assert_equal 2...10, @second_range.int4_range
assert_equal 2...Float::INFINITY, @third_range.int4_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range)
- assert_equal nil, @empty_range.int4_range
+ assert_nil @empty_range.int4_range
end
def test_int8range_values
@@ -255,7 +255,7 @@ _SQL
assert_equal 11...100, @second_range.int8_range
assert_equal 11...Float::INFINITY, @third_range.int8_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range)
- assert_equal nil, @empty_range.int8_range
+ assert_nil @empty_range.int8_range
end
def test_daterange_values
@@ -264,7 +264,7 @@ _SQL
assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range
assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
- assert_equal nil, @empty_range.date_range
+ assert_nil @empty_range.date_range
end
def test_numrange_values
@@ -273,7 +273,7 @@ _SQL
assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range
assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range
assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range
- assert_equal nil, @empty_range.num_range
+ assert_nil @empty_range.num_range
end
def test_tsrange_values
@@ -281,18 +281,16 @@ _SQL
tz = ::ActiveRecord::Base.default_timezone
assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range
assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range
- assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Float::INFINITY, @third_range.ts_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range)
- assert_equal nil, @empty_range.ts_range
+ assert_nil @empty_range.ts_range
end
def test_tstzrange_values
skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges?
assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range
assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range
- assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Float::INFINITY, @third_range.tstz_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range)
- assert_equal nil, @empty_range.tstz_range
+ assert_nil @empty_range.tstz_range
end
def test_money_values
@@ -316,11 +314,11 @@ _SQL
assert @first_range.tstz_range = new_tstzrange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.tstz_range, new_tstzrange
+ assert_equal new_tstzrange, @first_range.tstz_range
assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.tstz_range, nil
+ assert_nil @first_range.tstz_range
end
def test_create_tsrange
@@ -340,11 +338,11 @@ _SQL
assert @first_range.ts_range = new_tsrange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.ts_range, new_tsrange
+ assert_equal new_tsrange, @first_range.ts_range
assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0)
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.ts_range, nil
+ assert_nil @first_range.ts_range
end
def test_create_numrange
@@ -362,11 +360,11 @@ _SQL
assert @first_range.num_range = new_numrange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.num_range, new_numrange
+ assert_equal new_numrange, @first_range.num_range
assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5')
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.num_range, nil
+ assert_nil @first_range.num_range
end
def test_create_daterange
@@ -384,11 +382,11 @@ _SQL
assert @first_range.date_range = new_daterange
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.date_range, new_daterange
+ assert_equal new_daterange, @first_range.date_range
assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3)
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.date_range, nil
+ assert_nil @first_range.date_range
end
def test_create_int4range
@@ -406,11 +404,11 @@ _SQL
assert @first_range.int4_range = new_int4range
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int4_range, new_int4range
+ assert_equal new_int4range, @first_range.int4_range
assert @first_range.int4_range = 3...3
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int4_range, nil
+ assert_nil @first_range.int4_range
end
def test_create_int8range
@@ -428,11 +426,11 @@ _SQL
assert @first_range.int8_range = new_int8range
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int8_range, new_int8range
+ assert_equal new_int8range, @first_range.int8_range
assert @first_range.int8_range = 39999...39999
assert @first_range.save
assert @first_range.reload
- assert_equal @first_range.int8_range, nil
+ assert_nil @first_range.int8_range
end
def test_update_tsvector
@@ -443,7 +441,7 @@ _SQL
assert @first_tsvector.text_vector = new_text_vector
assert @first_tsvector.save
assert @first_tsvector.reload
- assert_equal @first_tsvector.text_vector, new_text_vector
+ assert_equal new_text_vector, @first_tsvector.text_vector
end
def test_number_values
@@ -484,11 +482,11 @@ _SQL
assert @first_array.commission_by_quarter = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.commission_by_quarter, new_value
+ assert_equal new_value, @first_array.commission_by_quarter
assert @first_array.commission_by_quarter = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.commission_by_quarter, new_value
+ assert_equal new_value, @first_array.commission_by_quarter
end
def test_update_text_array
@@ -496,11 +494,11 @@ _SQL
assert @first_array.nicknames = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.nicknames, new_value
+ assert_equal new_value, @first_array.nicknames
assert @first_array.nicknames = new_value
assert @first_array.save
assert @first_array.reload
- assert_equal @first_array.nicknames, new_value
+ assert_equal new_value, @first_array.nicknames
end
def test_update_money
@@ -518,15 +516,15 @@ _SQL
assert @first_number.double = new_double
assert @first_number.save
assert @first_number.reload
- assert_equal @first_number.single, new_single
- assert_equal @first_number.double, new_double
+ assert_equal new_single, @first_number.single
+ assert_equal new_double, @first_number.double
end
def test_update_time
assert @first_time.time_interval = '2 years 3 minutes'
assert @first_time.save
assert @first_time.reload
- assert_equal @first_time.time_interval, '2 years 00:03:00'
+ assert_equal '2 years 00:03:00', @first_time.time_interval
end
def test_update_network_address
@@ -545,13 +543,19 @@ _SQL
def test_update_bit_string
new_bit_string = '11111111'
- new_bit_string_varying = '11111110'
+ new_bit_string_varying = '0xFF'
assert @first_bit_string.bit_string = new_bit_string
assert @first_bit_string.bit_string_varying = new_bit_string_varying
assert @first_bit_string.save
assert @first_bit_string.reload
assert_equal new_bit_string, @first_bit_string.bit_string
- assert_equal new_bit_string_varying, @first_bit_string.bit_string_varying
+ assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
+ end
+
+ def test_invalid_hex_string
+ new_bit_string = 'FF'
+ @first_bit_string.bit_string = new_bit_string
+ assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save }
end
def test_update_oid
@@ -559,7 +563,7 @@ _SQL
assert @first_oid.obj_id = new_value
assert @first_oid.save
assert @first_oid.reload
- assert_equal @first_oid.obj_id, new_value
+ assert_equal new_value, @first_oid.obj_id
end
def test_timestamp_with_zone_values_with_rails_time_zone_support
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 619d581d5f..0b61f61572 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -22,13 +22,6 @@ module ActiveRecord
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
assert_match %(Seq Scan on audit_logs), explain
end
-
- def test_dont_explain_for_set_search_path
- queries = Thread.current[:available_queries_for_explain] = []
- ActiveRecord::Base.connection.schema_search_path = "public"
- assert queries.empty?
- end
-
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 6fc08ae4f0..f45c7afcc0 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -83,4 +83,18 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
x = JsonDataType.first
assert_equal(nil, x.payload)
end
+
+ def test_select_array_json_value
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
+ x = JsonDataType.first
+ assert_equal(['v0', {'k1' => 'v1'}], x.payload)
+ end
+
+ def test_rewrite_array_json_value
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
+ x = JsonDataType.first
+ x.payload = ['v1', {'k2' => 'v2'}, 'v3']
+ assert x.save!
+ end
+
end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 17d77c5454..fb88ab7c09 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -226,23 +226,51 @@ module ActiveRecord
end
def test_distinct_zero_orders
- assert_equal "DISTINCT posts.id",
- @connection.distinct("posts.id", [])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id",
+ @connection.distinct("posts.id", [])
+ end
+ end
+
+ def test_columns_for_distinct_zero_orders
+ assert_equal "posts.id",
+ @connection.columns_for_distinct("posts.id", [])
end
def test_distinct_one_order
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
- @connection.distinct("posts.id", ["posts.created_at desc"])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", ["posts.created_at desc"])
+ end
+ end
+
+ def test_columns_for_distinct_one_order
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @connection.columns_for_distinct("posts.id", ["posts.created_at desc"])
end
def test_distinct_few_orders
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
- @connection.distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ @connection.distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
+ end
+ end
+
+ def test_columns_for_distinct_few_orders
+ assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
end
def test_distinct_blank_not_nil_orders
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
- @connection.distinct("posts.id", ["posts.created_at desc", "", " "])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", ["posts.created_at desc", "", " "])
+ end
+ end
+
+ def test_columns_for_distinct_blank_not_nil_orders
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
end
def test_distinct_with_arel_order
@@ -250,13 +278,31 @@ module ActiveRecord
def order.to_sql
"posts.created_at desc"
end
- assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
- @connection.distinct("posts.id", [order])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", [order])
+ end
+ end
+
+ def test_columns_for_distinct_with_arel_order
+ order = Object.new
+ def order.to_sql
+ "posts.created_at desc"
+ end
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @connection.columns_for_distinct("posts.id", [order])
end
def test_distinct_with_nulls
- assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"])
- assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
+ assert_deprecated do
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"])
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
+ end
+ end
+
+ def test_columns_for_distinct_with_nulls
+ assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"])
+ assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"])
end
def test_raise_error_when_cannot_translate_exception
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index c0c0e8898c..b573d48807 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -50,4 +50,46 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
u.reload
assert_not_nil u.other_uuid
end
+
+ def test_pk_and_sequence_for_uuid_primary_key
+ pk, seq = @connection.pk_and_sequence_for('pg_uuids')
+ assert_equal 'id', pk
+ assert_equal nil, seq
+ end
+
+ def test_schema_dumper_for_uuid_primary_key
+ schema = StringIO.new
+ ActiveRecord::SchemaDumper.dump(@connection, schema)
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid\b/, schema.string)
+ end
+end
+
+class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
+ class UUID < ActiveRecord::Base
+ self.table_name = 'pg_uuids'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.reconnect!
+
+ @connection.transaction do
+ @connection.create_table('pg_uuids', id: false) do |t|
+ t.primary_key :id, :uuid, default: nil
+ t.string 'name'
+ end
+ end
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists pg_uuids'
+ end
+
+ def test_id_allows_default_override_via_nil
+ col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
+ FROM pg_attribute a
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
+ assert_nil col_desc["default"]
+ 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 a5a22bc30b..e78cb88562 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -54,7 +54,7 @@ class CopyTableTest < ActiveRecord::TestCase
end
def test_copy_table_with_id_col_that_is_not_primary_key
- test_copy_table('goofy_string_id', 'goofy_string_id2') do |from, to, options|
+ test_copy_table('goofy_string_id', 'goofy_string_id2') do
original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' }
copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' }
assert_equal original_id.type, copied_id.type
@@ -65,7 +65,7 @@ class CopyTableTest < ActiveRecord::TestCase
end
def test_copy_table_with_unconventional_primary_key
- test_copy_table('owners', 'owners_unconventional') do |from, to, options|
+ test_copy_table('owners', 'owners_unconventional') do
original_pk = @connection.primary_key('owners')
copied_pk = @connection.primary_key('owners_unconventional')
assert_equal original_pk, copied_pk
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index dcbec24acf..a8e5ab81e4 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -30,6 +30,10 @@ module ActiveRecord
assert @conn.valid_type?(column.type)
end
+ # sqlite databases should be able to support any type and not
+ # just the ones mentioned in the native_database_types.
+ # Therefore test_invalid column should always return true
+ # even if the type is not valid.
def test_invalid_column
assert @conn.valid_type?(:foobar)
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 10195e3ae4..5536702f58 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -141,7 +141,6 @@ class AggregationsTest < ActiveRecord::TestCase
end
class OverridingAggregationsTest < ActiveRecord::TestCase
- class Name; end
class DifferentName; end
class Person < ActiveRecord::Base
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 244e0b7179..500df52cd8 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -12,6 +12,8 @@ if ActiveRecord::Base.connection.supports_migrations?
def teardown
@connection.drop_table :fruits rescue nil
+ @connection.drop_table :nep_fruits rescue nil
+ @connection.drop_table :nep_schema_migrations rescue nil
ActiveRecord::SchemaMigration.delete_all rescue nil
end
@@ -30,6 +32,24 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 7, ActiveRecord::Migrator::current_version
end
+ def test_schema_define_w_table_name_prefix
+ table_name = ActiveRecord::SchemaMigration.table_name
+ ActiveRecord::Base.table_name_prefix = "nep_"
+ ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}"
+ ActiveRecord::Schema.define(:version => 7) do
+ create_table :fruits do |t|
+ t.column :color, :string
+ t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
+ t.column :texture, :string
+ t.column :flavor, :string
+ end
+ end
+ assert_equal 7, ActiveRecord::Migrator::current_version
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::SchemaMigration.table_name = table_name
+ end
+
def test_schema_raises_an_error_for_invalid_column_type
assert_raise NoMethodError do
ActiveRecord::Schema.define(:version => 8) do
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index f5316952b8..95896971a8 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -338,7 +338,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
topic.replies.create!(:title => "re: 37s", :content => "rails")
assert_equal 1, Topic.find(topic.id)[:replies_count]
- topic.update_columns(content: "rails is wonderfull")
+ topic.update_columns(content: "rails is wonderful")
assert_equal 1, Topic.find(topic.id)[:replies_count]
end
@@ -414,6 +414,26 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 15, topic.replies.size
end
+ def test_counter_cache_double_destroy
+ topic = Topic.create :title => "Zoom-zoom-zoom"
+
+ 5.times do
+ topic.replies.create(:title => "re: zoom", :content => "speedy quick!")
+ end
+
+ assert_equal 5, topic.reload[:replies_count]
+ assert_equal 5, topic.replies.size
+
+ reply = topic.replies.first
+
+ reply.destroy
+ assert_equal 4, topic.reload[:replies_count]
+
+ reply.destroy
+ assert_equal 4, topic.reload[:replies_count]
+ assert_equal 4, topic.replies.size
+ end
+
def test_custom_counter_cache
reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
assert_equal 0, reply[:replies_count]
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index d6850215b5..4aa6567d85 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -302,7 +302,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_foreign_keys
pets = Pet.all.merge!(:includes => :owner).to_a
- assert_equal 3, pets.length
+ assert_equal 4, pets.length
end
def test_eager_association_loading_with_belongs_to
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 781b87741d..9f64ecd845 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -755,6 +755,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal topic.replies.to_a.size, topic.replies_count
end
+ def test_pushing_association_updates_counter_cache
+ topic = Topic.order("id ASC").first
+ reply = Reply.create!
+
+ assert_difference "topic.reload.replies_count", 1 do
+ topic.replies << reply
+ end
+ end
+
def test_deleting_updates_counter_cache_without_dependent_option
post = posts(:welcome)
@@ -1463,6 +1472,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal david.essays, Essay.where(writer_id: "David")
end
+ def test_has_many_assignment_with_custom_primary_key
+ david = people(:david)
+
+ assert_equal ["A Modest Proposal"], david.essays.map(&:name)
+ david.essays = [Essay.create!(name: "Remote Work" )]
+ assert_equal ["Remote Work"], david.essays.map(&:name)
+ end
+
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
author = Author.new
assert !author.essays.loaded?
@@ -1730,12 +1747,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_deprecated { klass.has_many :foo, :counter_sql => 'lol' }
end
- test "sum calculation with block for array compatibility is deprecated" do
- assert_deprecated do
- posts(:welcome).comments.sum { |c| c.id }
- end
- end
-
test "has many associations on new records use null relations" do
post = Post.new
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 67d18f313a..70c6b489aa 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -583,7 +583,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys
- assert_equal 1, owners(:blackbeard).toys.count
+ assert_equal 2, owners(:blackbeard).toys.count
end
def test_find_on_has_many_association_collection_with_include_and_conditions
@@ -882,6 +882,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [tags(:general)], post.reload.tags
end
+ def test_has_many_through_obeys_order_on_through_association
+ owner = owners(:blackbeard)
+ assert owner.toys.to_sql.include?("pets.name desc")
+ assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
+ end
+
test "has many through associations on new records use null relations" do
person = Person.new
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 4ed09a3bf7..0e48fbca9c 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -522,4 +522,20 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
account = Account.find(2)
assert_queries { company.account = account }
end
+
+ def test_has_one_assignment_triggers_save_on_change
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ ship = pirate.build_ship(name: 'old name')
+ ship.save!
+
+ ship.name = 'new name'
+ assert ship.changed?
+ assert_queries(2) do
+ # One query for updating name and second query for updating pirate_id
+ pirate.ship = ship
+ end
+
+ assert_equal 'new name', pirate.ship.reload.name
+ 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 918783e8f1..9baf94399a 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -46,10 +46,10 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_equal 2, authors.count
end
- def test_find_with_implicit_inner_joins_honors_readonly_without_select
- authors = Author.joins(:posts).to_a
- assert !authors.empty?, "expected authors to be non-empty"
- assert authors.all? {|a| a.readonly? }, "expected all authors to be readonly"
+ def test_find_with_implicit_inner_joins_without_select_does_not_imply_readonly
+ authors = Author.joins(:posts)
+ assert_not authors.empty?, "expected authors to be non-empty"
+ assert authors.none? {|a| a.readonly? }, "expected no authors to be readonly"
end
def test_find_with_implicit_inner_joins_honors_readonly_with_select
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index c8cad84013..b1f0be3204 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -5,6 +5,88 @@ require 'models/interest'
require 'models/zine'
require 'models/club'
require 'models/sponsor'
+require 'models/rating'
+require 'models/comment'
+require 'models/car'
+require 'models/bulb'
+
+class AutomaticInverseFindingTests < ActiveRecord::TestCase
+ fixtures :ratings, :comments, :cars
+
+ def test_has_one_and_belongs_to_should_find_inverse_automatically
+ car_reflection = Car.reflect_on_association(:bulb)
+ bulb_reflection = Bulb.reflect_on_association(:car)
+
+ assert_respond_to car_reflection, :has_inverse?
+ assert car_reflection.has_inverse?, "The Car reflection should have an inverse"
+ assert_equal bulb_reflection, car_reflection.inverse_of, "The Car reflection's inverse should be the Bulb reflection"
+
+ assert_respond_to bulb_reflection, :has_inverse?
+ assert bulb_reflection.has_inverse?, "The Bulb reflection should have an inverse"
+ assert_equal car_reflection, bulb_reflection.inverse_of, "The Bulb reflection's inverse should be the Car reflection"
+ end
+
+ def test_has_many_and_belongs_to_should_find_inverse_automatically
+ comment_reflection = Comment.reflect_on_association(:ratings)
+ rating_reflection = Rating.reflect_on_association(:comment)
+
+ assert_respond_to comment_reflection, :has_inverse?
+ assert comment_reflection.has_inverse?, "The Comment reflection should have an inverse"
+ assert_equal rating_reflection, comment_reflection.inverse_of, "The Comment reflection's inverse should be the Rating reflection"
+ end
+
+ def test_has_one_and_belongs_to_automatic_inverse_shares_objects
+ car = Car.first
+ bulb = Bulb.create!(car: car)
+
+ assert_equal car.bulb, bulb, "The Car's bulb should be the original bulb"
+
+ car.bulb.color = "Blue"
+ assert_equal car.bulb.color, bulb.color, "Changing the bulb's color on the car association should change the bulb's color"
+
+ bulb.color = "Red"
+ assert_equal bulb.color, car.bulb.color, "Changing the bulb's color should change the bulb's color on the car association"
+ end
+
+ def test_has_many_and_belongs_to_automatic_inverse_shares_objects_on_rating
+ comment = Comment.first
+ rating = Rating.create!(comment: comment)
+
+ assert_equal rating.comment, comment, "The Rating's comment should be the original Comment"
+
+ rating.comment.body = "Brogramming is the act of programming, like a bro."
+ assert_equal rating.comment.body, comment.body, "Changing the Comment's body on the association should change the original Comment's body"
+
+ comment.body = "Broseiden is the king of the sea of bros."
+ assert_equal comment.body, rating.comment.body, "Changing the original Comment's body should change the Comment's body on the association"
+ end
+
+ def test_has_many_and_belongs_to_automatic_inverse_shares_objects_on_comment
+ rating = Rating.create!
+ comment = Comment.first
+ rating.comment = comment
+
+ assert_equal rating.comment, comment, "The Rating's comment should be the original Comment"
+
+ rating.comment.body = "Brogramming is the act of programming, like a bro."
+ assert_equal rating.comment.body, comment.body, "Changing the Comment's body on the association should change the original Comment's body"
+
+ comment.body = "Broseiden is the king of the sea of bros."
+ assert_equal comment.body, rating.comment.body, "Changing the original Comment's body should change the Comment's body on the association"
+ end
+
+ def test_polymorphic_and_has_many_through_relationships_should_not_have_inverses
+ sponsor_reflection = Sponsor.reflect_on_association(:sponsorable)
+
+ assert_respond_to sponsor_reflection, :has_inverse?
+ assert !sponsor_reflection.has_inverse?, "A polymorphic association should not find an inverse automatically"
+
+ club_reflection = Club.reflect_on_association(:members)
+
+ assert_respond_to club_reflection, :has_inverse?
+ assert !club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically"
+ end
+end
class InverseAssociationTests < ActiveRecord::TestCase
def test_should_allow_for_inverse_of_options_in_associations
@@ -235,6 +317,22 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
end
+ def test_parent_instance_should_be_shared_within_create_block_of_new_child
+ man = Man.first
+ interest = man.interests.build do |i|
+ assert i.man.equal?(man), "Man of child should be the same instance as a parent"
+ end
+ assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
+ end
+
+ def test_parent_instance_should_be_shared_within_build_block_of_new_child
+ man = Man.first
+ interest = man.interests.build do |i|
+ assert i.man.equal?(man), "Man of child should be the same instance as a parent"
+ end
+ assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
+ end
+
def test_parent_instance_should_be_shared_with_poked_in_child
m = men(:gordon)
i = Interest.create(:topic => 'Industrial Revolution Re-enactment')
@@ -303,6 +401,22 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the child name is changed"
end
+ def test_raise_record_not_found_error_when_invalid_ids_are_passed
+ man = Man.create!
+
+ invalid_id = 2394823094892348920348523452345
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_id) }
+
+ invalid_ids = [8432342, 2390102913, 2453245234523452]
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_ids) }
+ end
+
+ def test_raise_record_not_found_error_when_no_ids_are_passed
+ man = Man.create!
+
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find() }
+ end
+
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index a06bacafca..c3b728296e 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -18,6 +18,8 @@ require 'models/ship'
require 'models/liquid'
require 'models/molecule'
require 'models/electron'
+require 'models/man'
+require 'models/interest'
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
@@ -215,7 +217,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal post.body, "More cool stuff!"
end
- def test_reload_returns_assocition
+ def test_reload_returns_association
david = developers(:david)
assert_nothing_raised do
assert_equal david.projects, david.projects.reload.reload
@@ -242,10 +244,20 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = developers(:david)
assert david.projects.equal?(david.projects)
end
+
+ test "inverses get set of subsets of the association" do
+ man = Man.create
+ man.interests.create
+
+ man = Man.find(man.id)
+
+ assert_queries(1) do
+ assert_equal man, man.interests.where("1=1").first.man
+ end
+ end
end
class OverridingAssociationsTest < ActiveRecord::TestCase
- class Person < ActiveRecord::Base; end
class DifferentPerson < ActiveRecord::Base; end
class PeopleList < ActiveRecord::Base
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 648596b828..ee0150558d 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -27,6 +27,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
+ def test_attribute_for_inspect
+ t = topics(:first)
+ t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
+
+ assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
+ assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
+ end
+
def test_attribute_present
t = Topic.new
t.title = "hello there!"
@@ -69,7 +77,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_boolean_attributes
- assert ! Topic.find(1).approved?
+ assert !Topic.find(1).approved?
assert Topic.find(2).approved?
end
@@ -130,6 +138,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal '10', keyboard.id_before_type_cast
assert_equal nil, keyboard.read_attribute_before_type_cast('id')
assert_equal '10', keyboard.read_attribute_before_type_cast('key_number')
+ assert_equal '10', keyboard.read_attribute_before_type_cast(:key_number)
end
# Syck calls respond_to? before actually calling initialize
@@ -141,13 +150,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_respond_to topic, :title
end
- # IRB inspects the return value of "MyModel.allocate"
- # by inspecting it.
+ # IRB inspects the return value of "MyModel.allocate".
def test_allocated_object_can_be_inspected
topic = Topic.allocate
- topic.instance_eval { @attributes = nil }
- assert_nothing_raised { topic.inspect }
- assert topic.inspect, "#<Topic not initialized>"
+ assert_equal "#<Topic not initialized>", topic.inspect
end
def test_array_content
@@ -713,6 +719,15 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
end
+ def test_bulk_update_raise_unknown_attribute_errro
+ error = assert_raises(ActiveRecord::UnknownAttributeError) {
+ @target.new(:hello => "world")
+ }
+ assert @target, error.record
+ assert "hello", error.attribute
+ assert "unknown attribute: hello", error.message
+ end
+
def test_read_attribute_overwrites_private_method_not_considered_implemented
# simulate a model with a db column that shares its name an inherited
# private method (e.g. Object#system)
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 536ff4882c..580aa96ecd 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -566,7 +566,7 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
end
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_fixtures = false
def setup
super
@@ -764,6 +764,20 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
assert_equal 2, @pirate.birds.reload.length
end
+ def test_should_save_new_record_that_has_same_value_as_existing_record_marked_for_destruction_on_field_that_has_unique_index
+ Bird.connection.add_index :birds, :name, unique: true
+
+ 3.times { |i| @pirate.birds.create(name: "unique_birds_#{i}") }
+
+ @pirate.birds[0].mark_for_destruction
+ @pirate.birds.build(name: @pirate.birds[0].name)
+ @pirate.save!
+
+ assert_equal 3, @pirate.birds.reload.length
+ ensure
+ Bird.connection.remove_index :birds, column: :name
+ end
+
# Add and remove callbacks tests for association collections.
%w{ method proc }.each do |callback_type|
define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do
@@ -846,8 +860,10 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.parrots.each { |parrot| parrot.mark_for_destruction }
assert @pirate.save
- assert_queries(0) do
- assert @pirate.save
+ Pirate.transaction do
+ assert_queries(0) do
+ assert @pirate.save
+ end
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 08223902c7..395f28f280 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -21,7 +21,6 @@ require 'models/parrot'
require 'models/person'
require 'models/edge'
require 'models/joke'
-require 'models/bulb'
require 'models/bird'
require 'models/car'
require 'models/bulb'
@@ -582,12 +581,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "changed", post.body
end
- def test_attr_readonly_is_class_level_setting
- post = ReadonlyTitlePost.new
- assert_raise(NoMethodError) { post._attr_readonly = [:title] }
- assert_deprecated { post._attr_readonly }
- end
-
def test_non_valid_identifier_column_name
weird = Weird.create('a$b' => 'value')
weird.reload
@@ -1229,93 +1222,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_no_queries { assert true }
end
- def test_to_param_should_return_string
- assert_kind_of String, Client.first.to_param
- end
-
- def test_to_param_returns_id_even_if_not_persisted
- client = Client.new
- client.id = 1
- assert_equal "1", client.to_param
- end
-
- def test_inspect_class
- assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
- assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
- assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
- end
-
- def test_inspect_instance
- topic = topics(:first)
- assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
- end
-
- def test_inspect_new_instance
- assert_match(/Topic id: nil/, Topic.new.inspect)
- end
-
- def test_inspect_limited_select_instance
- assert_equal %(#<Topic id: 1>), Topic.all.merge!(:select => 'id', :where => 'id = 1').first.inspect
- assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.all.merge!(:select => 'id, title', :where => 'id = 1').first.inspect
- end
-
- def test_inspect_class_without_table
- assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
- end
-
- def test_attribute_for_inspect
- t = topics(:first)
- t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
-
- assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
- assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
- end
-
- def test_becomes
- assert_kind_of Reply, topics(:first).becomes(Reply)
- assert_equal "The First Topic", topics(:first).becomes(Reply).title
- end
-
- def test_becomes_includes_errors
- company = Company.new(:name => nil)
- assert !company.valid?
- original_errors = company.errors
- client = company.becomes(Client)
- assert_equal original_errors, client.errors
- end
-
- def test_silence_sets_log_level_to_error_in_block
- original_logger = ActiveRecord::Base.logger
-
- assert_deprecated do
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::DEBUG
- ActiveRecord::Base.silence do
- ActiveRecord::Base.logger.warn "warn"
- ActiveRecord::Base.logger.error "error"
- end
- assert_equal "error\n", log.string
- end
- ensure
- ActiveRecord::Base.logger = original_logger
- end
-
- def test_silence_sets_log_level_back_to_level_before_yield
- original_logger = ActiveRecord::Base.logger
-
- assert_deprecated do
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::WARN
- ActiveRecord::Base.silence do
- end
- assert_equal Logger::WARN, ActiveRecord::Base.logger.level
- end
- ensure
- ActiveRecord::Base.logger = original_logger
- end
-
def test_benchmark_with_log_level
original_logger = ActiveRecord::Base.logger
log = StringIO.new
@@ -1378,9 +1284,9 @@ class BasicsTest < ActiveRecord::TestCase
UnloadablePost.send(:current_scope=, UnloadablePost.all)
UnloadablePost.unloadable
- assert_not_nil Thread.current[:UnloadablePost_current_scope]
+ assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil Thread.current[:UnloadablePost_current_scope]
+ assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
@@ -1432,62 +1338,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal [], AbstractCompany.attribute_names
end
- def test_cache_key_for_existing_record_is_not_timezone_dependent
- ActiveRecord::Base.time_zone_aware_attributes = true
-
- Time.zone = "UTC"
- utc_key = Developer.first.cache_key
-
- Time.zone = "EST"
- est_key = Developer.first.cache_key
-
- assert_equal utc_key, est_key
- end
-
- 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(:nsec)}", dev.cache_key
- end
-
- def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format
- dev = CachedDeveloper.first
- assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
- end
-
- def test_cache_key_changes_when_child_touched
- car = Car.create
- Bulb.create(car: car)
-
- key = car.cache_key
- car.bulb.touch
- car.reload
- assert_not_equal key, car.cache_key
- end
-
- def test_cache_key_format_for_existing_record_with_nil_updated_timestamps
- dev = Developer.first
- dev.update_columns(updated_at: nil, updated_on: nil)
- assert_match(/\/#{dev.id}$/, dev.cache_key)
- end
-
- def test_cache_key_for_updated_on
- dev = Developer.first
- dev.updated_at = nil
- assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
- end
-
- def test_cache_key_for_newer_updated_at
- dev = Developer.first
- dev.updated_at += 3600
- assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
- end
-
- def test_cache_key_for_newer_updated_on
- dev = Developer.first
- dev.updated_on += 3600
- assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
- end
-
def test_touch_should_raise_error_on_a_new_object
company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals")
assert_raises(ActiveRecord::ActiveRecordError) do
@@ -1495,13 +1345,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_cache_key_format_is_precise_enough
- dev = Developer.first
- key = dev.cache_key
- dev.touch
- assert_not_equal key, dev.cache_key
- end
-
def test_uniq_delegates_to_scoped
scope = stub
Bird.stubs(:all).returns(mock(:uniq => scope))
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index ba6b0b1362..e09fa95756 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -12,7 +12,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_each_should_execute_one_query_per_batch
- assert_queries(Post.count + 1) do
+ assert_queries(@total + 1) do
Post.find_each(:batch_size => 1) do |post|
assert_kind_of Post, post
end
@@ -51,7 +51,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_return_batches
- assert_queries(Post.count + 1) do
+ assert_queries(@total + 1) do
Post.find_in_batches(:batch_size => 1) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -60,7 +60,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_start_from_the_start_option
- assert_queries(Post.count) do
+ assert_queries(@total) do
Post.find_in_batches(:batch_size => 1, :start => 2) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -69,14 +69,12 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_shouldnt_execute_query_unless_needed
- post_count = Post.count
-
assert_queries(2) do
- Post.find_in_batches(:batch_size => post_count) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(:batch_size => @total) {|batch| assert_kind_of Array, batch }
end
assert_queries(1) do
- Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(:batch_size => @total + 1) {|batch| assert_kind_of Array, batch }
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index c645523905..0f3f9aecfc 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -6,6 +6,7 @@ require 'models/edge'
require 'models/organization'
require 'models/possession'
require 'models/topic'
+require 'models/reply'
require 'models/minivan'
require 'models/speedometer'
require 'models/ship_part'
@@ -28,6 +29,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 53.0, value
end
+ def test_should_resolve_aliased_attributes
+ assert_equal 318, Account.sum(:available_credit)
+ end
+
def test_should_return_decimal_average_of_integer_field
value = Account.average(:id)
assert_equal 3.5, value
@@ -96,25 +101,24 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_order_by_grouped_field
- c = Account.all.merge!(:group => :firm_id, :order => "firm_id").sum(:credit_limit)
+ c = Account.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.all.merge!(:group => :firm_id, :order => "sum_credit_limit desc, firm_id").sum(:credit_limit)
+ c = Account.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.all.merge!(:where => "firm_id IS NOT NULL",
- :group => :firm_id, :order => "firm_id", :limit => 2).sum(:credit_limit)
+ c = Account.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.all.merge!(:where => "firm_id IS NOT NULL", :group => :firm_id,
- :order => "firm_id", :limit => 2, :offset => 1).sum(:credit_limit)
+ c = Account.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
@@ -163,9 +167,17 @@ class CalculationsTest < ActiveRecord::TestCase
assert_no_match(/OFFSET/, queries.first)
end
+ def test_count_on_invalid_columns_raises
+ e = assert_raises(ActiveRecord::StatementInvalid) {
+ Account.select("credit_limit, firm_name").count
+ }
+
+ assert_match "accounts", e.message
+ assert_match "credit_limit, firm_name", e.message
+ end
+
def test_should_group_by_summed_field_having_condition
- c = Account.all.merge!(:group => :firm_id,
- :having => 'sum(credit_limit) > 50').sum(:credit_limit)
+ c = Account.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]
@@ -200,17 +212,15 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_with_conditions
- c = Account.all.merge!(:where => 'firm_id > 1',
- :group => :firm_id).sum(:credit_limit)
+ c = Account.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.all.merge!(:where => 'firm_id > 1',
- :group => :firm_id,
- :having => 'sum(credit_limit) > 60').sum(:credit_limit)
+ c = Account.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]
@@ -322,7 +332,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_scoped_select
Account.update_all("credit_limit = NULL")
- assert_equal 0, Account.all.merge!(:select => "credit_limit").count
+ assert_equal 0, Account.select("credit_limit").count
end
def test_should_count_scoped_select_with_options
@@ -330,11 +340,11 @@ class CalculationsTest < ActiveRecord::TestCase
Account.last.update_columns('credit_limit' => 49)
Account.first.update_columns('credit_limit' => 51)
- assert_equal 1, Account.all.merge!(:select => "credit_limit").where('credit_limit >= 50').count
+ assert_equal 1, Account.select("credit_limit").where('credit_limit >= 50').count
end
def test_should_count_manual_select_with_include
- assert_equal 6, Account.all.merge!(:select => "DISTINCT accounts.id", :includes => :firm).count
+ assert_equal 6, Account.select("DISTINCT accounts.id").includes(:firm).count
end
def test_count_with_column_parameter
@@ -356,6 +366,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 4, Account.select(:credit_limit).uniq.count
end
+ def test_count_with_aliased_attribute
+ assert_equal 6, Account.count(:available_credit)
+ end
+
def test_count_with_column_and_options_parameter
assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id)
end
@@ -366,7 +380,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_count_field_in_joined_table_with_group_by
- c = Account.all.merge!(:group => 'accounts.firm_id', :joins => :firm).count('companies.id')
+ c = Account.group('accounts.firm_id').joins(:firm).count('companies.id')
[1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) }
end
@@ -406,12 +420,6 @@ class CalculationsTest < ActiveRecord::TestCase
Account.where("credit_limit > 50").from('accounts').sum(:credit_limit)
end
- def test_sum_array_compatibility_deprecation
- assert_deprecated do
- assert_equal Account.sum(:credit_limit), Account.sum(&:credit_limit)
- end
- end
-
def test_average_with_from_option
assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit)
assert_equal Account.where("credit_limit > 50").average(:credit_limit),
@@ -474,6 +482,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [1,2,3,4], Topic.order(:id).pluck(:id)
end
+ def test_pluck_without_column_names
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]],
+ Company.order(:id).limit(1).pluck
+ end
+
def test_pluck_type_cast
topic = topics(:first)
relation = Topic.where(:id => topic.id)
@@ -492,6 +505,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [contract.id], company.contracts.pluck(:id)
end
+ def test_pluck_on_aliased_attribute
+ assert_equal 'The First Topic', Topic.order(:id).pluck(:heading).first
+ end
+
def test_pluck_with_serialization
t = Topic.create!(:content => { :foo => :bar })
assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content)
@@ -531,6 +548,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal Company.all.map(&:id).sort, Company.ids.sort
end
+ def test_pluck_with_includes_limit_and_empty_result
+ assert_equal [], Topic.includes(:replies).limit(0).pluck(:id)
+ assert_equal [], Topic.includes(:replies).limit(1).where('0 = 1').pluck(:id)
+ end
+
def test_pluck_not_auto_table_name_prefix_if_column_included
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
ids = Company.includes(:contracts).pluck(:developer_id)
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 187cad9599..c8f56e3c73 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -43,7 +43,7 @@ class CallbackDeveloper < ActiveRecord::Base
end
class CallbackDeveloperWithFalseValidation < CallbackDeveloper
- before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false }
+ before_validation proc { |model| model.history << [:before_validation, :returning_false]; false }
before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
end
diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb
index d91646efca..5e43082c33 100644
--- a/activerecord/test/cases/clone_test.rb
+++ b/activerecord/test/cases/clone_test.rb
@@ -29,5 +29,12 @@ module ActiveRecord
topic.author_name = 'Aaron'
assert_equal 'Aaron', cloned.author_name
end
+
+ def test_freezing_a_cloned_model_does_not_freeze_clone
+ cloned = Topic.new
+ clone = cloned.clone
+ cloned.freeze
+ assert_not clone.frozen?
+ end
end
end
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index b874adc081..b72c54f97b 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -43,10 +43,20 @@ module ActiveRecord
assert_equal [], coder.load([])
end
- def test_load_swallows_yaml_exceptions
+ def test_load_doesnt_swallow_yaml_exceptions
coder = YAMLColumn.new
bad_yaml = '--- {'
- assert_equal bad_yaml, coder.load(bad_yaml)
+ assert_raises(Psych::SyntaxError) do
+ coder.load(bad_yaml)
+ end
+ end
+
+ def test_load_doesnt_handle_undefined_class_or_module
+ coder = YAMLColumn.new
+ missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n'
+ assert_raises(ArgumentError) do
+ coder.load(missing_class_yaml)
+ end
end
end
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index adbe51f430..3a4f414ae8 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -6,6 +6,9 @@ module ActiveRecord
class ColumnTest < ActiveRecord::TestCase
def test_type_cast_boolean
column = Column.new("field", nil, "boolean")
+ assert column.type_cast('').nil?
+ assert column.type_cast(nil).nil?
+
assert column.type_cast(true)
assert column.type_cast(1)
assert column.type_cast('1')
@@ -15,15 +18,21 @@ module ActiveRecord
assert column.type_cast('TRUE')
assert column.type_cast('on')
assert column.type_cast('ON')
- assert !column.type_cast(false)
- assert !column.type_cast(0)
- assert !column.type_cast('0')
- assert !column.type_cast('f')
- assert !column.type_cast('F')
- assert !column.type_cast('false')
- assert !column.type_cast('FALSE')
- assert !column.type_cast('off')
- assert !column.type_cast('OFF')
+
+ # explicitly check for false vs nil
+ assert_equal false, column.type_cast(false)
+ assert_equal false, column.type_cast(0)
+ assert_equal false, column.type_cast('0')
+ assert_equal false, column.type_cast('f')
+ assert_equal false, column.type_cast('F')
+ assert_equal false, column.type_cast('false')
+ assert_equal false, column.type_cast('FALSE')
+ assert_equal false, column.type_cast('off')
+ assert_equal false, column.type_cast('OFF')
+ assert_equal false, column.type_cast(' ')
+ assert_equal false, column.type_cast("\u3000\r\n")
+ assert_equal false, column.type_cast("\u0000")
+ assert_equal false, column.type_cast('SOMETHING RANDOM')
end
def test_type_cast_integer
@@ -65,8 +74,9 @@ module ActiveRecord
def test_type_cast_time
column = Column.new("field", nil, "time")
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
time_string = Time.now.utc.strftime("%T")
assert_equal time_string, column.type_cast(time_string).strftime("%T")
@@ -74,8 +84,10 @@ module ActiveRecord
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(nil)
assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
datetime_string = Time.now.utc.strftime("%FT%T")
assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
@@ -84,8 +96,10 @@ module ActiveRecord
def test_type_cast_date
column = Column.new("field", nil, "date")
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
date_string = Time.now.utc.strftime("%F")
assert_equal date_string, column.type_cast(date_string).strftime("%F")
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
index 1fd64dd0af..eb2fe5639b 100644
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
@@ -2,6 +2,15 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
+ class ConnectionPool
+ def insert_connection_for_test!(c)
+ synchronize do
+ @connections << c
+ @available.add c
+ end
+ end
+ end
+
class AbstractAdapterTest < ActiveRecord::TestCase
attr_reader :adapter
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
new file mode 100644
index 0000000000..2a52bf574c
--- /dev/null
+++ b/activerecord/test/cases/core_test.rb
@@ -0,0 +1,33 @@
+require 'cases/helper'
+require 'models/person'
+require 'models/topic'
+
+class NonExistentTable < ActiveRecord::Base; end
+
+class CoreTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_inspect_class
+ assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
+ assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
+ assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
+ end
+
+ def test_inspect_instance
+ topic = topics(:first)
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
+ end
+
+ def test_inspect_new_instance
+ assert_match(/Topic id: nil/, Topic.new.inspect)
+ end
+
+ def test_inspect_limited_select_instance
+ assert_equal %(#<Topic id: 1>), Topic.all.merge!(:select => 'id', :where => 'id = 1').first.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.all.merge!(:select => 'id, title', :where => 'id = 1').first.inspect
+ end
+
+ def test_inspect_class_without_table
+ assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
+ end
+end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index ac093251a5..61f9d4cdae 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -51,6 +51,18 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test 'reset multiple association counters' do
+ Topic.increment_counter(:replies_count, @topic.id)
+ assert_difference '@topic.reload.replies_count', -1 do
+ Topic.reset_counters(@topic.id, :replies, :unique_replies)
+ end
+
+ Topic.increment_counter(:unique_replies_count, @topic.id)
+ assert_difference '@topic.reload.unique_replies_count', -1 do
+ Topic.reset_counters(@topic.id, :replies, :unique_replies)
+ end
+ end
+
test "reset counters with string argument" do
Topic.increment_counter('replies_count', @topic.id)
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 7b2034dadf..36b87033ae 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -213,9 +213,11 @@ class DirtyTest < ActiveRecord::TestCase
topic = target.create
assert_nil topic.written_on
- topic.written_on = ""
- assert_nil topic.written_on
- assert !topic.written_on_changed?
+ ["", nil].each do |value|
+ topic.written_on = value
+ assert_nil topic.written_on
+ assert !topic.written_on_changed?
+ end
end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index b425967678..b00e2744b9 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -1,55 +1,59 @@
require 'cases/helper'
+require 'active_record/explain_subscriber'
+require 'active_record/explain_registry'
if ActiveRecord::Base.connection.supports_explain?
class ExplainSubscriberTest < ActiveRecord::TestCase
SUBSCRIBER = ActiveRecord::ExplainSubscriber.new
- def test_collects_nothing_if_available_queries_for_explain_is_nil
- with_queries(nil) do
- SUBSCRIBER.finish(nil, nil, {})
- assert_nil Thread.current[:available_queries_for_explain]
- end
+ def setup
+ ActiveRecord::ExplainRegistry.reset
+ ActiveRecord::ExplainRegistry.collect = true
end
def test_collects_nothing_if_the_payload_has_an_exception
- with_queries([]) do |queries|
- SUBSCRIBER.finish(nil, nil, :exception => Exception.new)
- assert queries.empty?
- end
+ SUBSCRIBER.finish(nil, nil, exception: Exception.new)
+ assert queries.empty?
end
def test_collects_nothing_for_ignored_payloads
- with_queries([]) do |queries|
- ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
- SUBSCRIBER.finish(nil, nil, :name => ip)
- end
- assert queries.empty?
+ ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
+ SUBSCRIBER.finish(nil, nil, name: ip)
end
+ assert queries.empty?
+ end
+
+ def test_collects_nothing_if_collect_is_false
+ ActiveRecord::ExplainRegistry.collect = false
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select 1 from users', binds: [1, 2])
+ assert queries.empty?
end
def test_collects_pairs_of_queries_and_binds
sql = 'select 1 from users'
binds = [1, 2]
- with_queries([]) do |queries|
- SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds)
- assert_equal 1, queries.size
- assert_equal sql, queries[0][0]
- assert_equal binds, queries[0][1]
- end
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds)
+ assert_equal 1, queries.size
+ assert_equal sql, queries[0][0]
+ assert_equal binds, queries[0][1]
end
- def test_collects_nothing_if_unexplained_sqls
- with_queries([]) do |queries|
- SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length')
- assert queries.empty?
- end
+ def test_collects_nothing_if_the_statement_is_not_whitelisted
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length')
+ assert queries.empty?
+ end
+
+ def test_collects_nothing_if_the_statement_is_only_partially_matched
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select_db yo_mama')
+ assert queries.empty?
+ end
+
+ def teardown
+ ActiveRecord::ExplainRegistry.reset
end
- def with_queries(queries)
- Thread.current[:available_queries_for_explain] = queries
- yield queries
- ensure
- Thread.current[:available_queries_for_explain] = nil
+ def queries
+ ActiveRecord::ExplainRegistry.queries
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index e505fe9f18..6f0de42aef 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -31,6 +31,13 @@ class FinderTest < ActiveRecord::TestCase
assert_equal(topics(:first).title, Topic.find(1).title)
end
+ def test_symbols_table_ref
+ Post.first # warm up
+ x = Symbol.all_symbols.count
+ Post.where("title" => {"xxxqqqq" => "bar"})
+ assert_equal x, Symbol.all_symbols.count
+ end
+
# find should handle strings that come from URLs
# (example: Category.find(params[:id]))
def test_find_with_string
@@ -40,7 +47,8 @@ class FinderTest < ActiveRecord::TestCase
def test_exists
assert Topic.exists?(1)
assert Topic.exists?("1")
- assert Topic.exists?(:author_name => "David")
+ assert Topic.exists?(title: "The First Topic")
+ assert Topic.exists?(heading: "The First Topic")
assert Topic.exists?(:author_name => "Mary", :approved => true)
assert Topic.exists?(["parent_id = ?", 1])
assert !Topic.exists?(45)
@@ -90,6 +98,18 @@ class FinderTest < ActiveRecord::TestCase
assert !Topic.includes(:replies).limit(1).where('0 = 1').exists?
end
+ def test_exists_with_distinct_association_includes_and_limit
+ author = Author.first
+ assert !author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
+ assert author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
+ end
+
+ def test_exists_with_distinct_association_includes_limit_and_order
+ author = Author.first
+ assert !author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
+ assert author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
+ end
+
def test_exists_with_empty_table_and_no_args_given
Topic.delete_all
assert !Topic.exists?
@@ -833,6 +853,8 @@ class FinderTest < ActiveRecord::TestCase
rescue ActiveRecord::RecordNotFound => e
assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
end
+ ensure
+ Toy.reset_primary_key
end
def test_finder_with_offset_string
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 99d54e7526..a9be132801 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -194,6 +194,10 @@ class InheritanceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') }
end
+ def test_new_with_complex_inheritance
+ assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
+ end
+
def test_new_with_autoload_paths
path = File.expand_path('../../models/autoloadable', __FILE__)
ActiveSupport::Dependencies.autoload_paths << path
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
new file mode 100644
index 0000000000..b0a7cda2f3
--- /dev/null
+++ b/activerecord/test/cases/integration_test.rb
@@ -0,0 +1,87 @@
+require 'cases/helper'
+require 'models/company'
+require 'models/developer'
+require 'models/car'
+require 'models/bulb'
+
+class IntegrationTest < ActiveRecord::TestCase
+ fixtures :companies, :developers
+
+ def test_to_param_should_return_string
+ assert_kind_of String, Client.first.to_param
+ end
+
+ def test_to_param_returns_nil_if_not_persisted
+ client = Client.new
+ assert_equal nil, client.to_param
+ end
+
+ def test_to_param_returns_id_if_not_persisted_but_id_is_set
+ client = Client.new
+ client.id = 1
+ assert_equal '1', client.to_param
+ end
+
+ def test_cache_key_for_existing_record_is_not_timezone_dependent
+ ActiveRecord::Base.time_zone_aware_attributes = true
+
+ Time.zone = 'UTC'
+ utc_key = Developer.first.cache_key
+
+ Time.zone = 'EST'
+ est_key = Developer.first.cache_key
+
+ assert_equal utc_key, est_key
+ end
+
+ 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(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format
+ dev = CachedDeveloper.first
+ assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
+ end
+
+ def test_cache_key_changes_when_child_touched
+ car = Car.create
+ Bulb.create(car: car)
+
+ key = car.cache_key
+ car.bulb.touch
+ car.reload
+ assert_not_equal key, car.cache_key
+ end
+
+ def test_cache_key_format_for_existing_record_with_nil_updated_timestamps
+ dev = Developer.first
+ dev.update_columns(updated_at: nil, updated_on: nil)
+ assert_match(/\/#{dev.id}$/, dev.cache_key)
+ end
+
+ def test_cache_key_for_updated_on
+ dev = Developer.first
+ dev.updated_at = nil
+ assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_for_newer_updated_at
+ dev = Developer.first
+ dev.updated_at += 3600
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_for_newer_updated_on
+ dev = Developer.first
+ dev.updated_on += 3600
+ assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_format_is_precise_enough
+ dev = Developer.first
+ key = dev.cache_key
+ dev.touch
+ assert_not_equal key, dev.cache_key
+ end
+end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index be59ffc4ab..428145d00b 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -58,6 +58,24 @@ module ActiveRecord
end
end
+ class RemoveIndexMigration1 < SilentMigration
+ def self.up
+ create_table("horses") do |t|
+ t.column :name, :string
+ t.column :color, :string
+ t.index [:name, :color]
+ end
+ end
+ end
+
+ class RemoveIndexMigration2 < SilentMigration
+ def change
+ change_table("horses") do |t|
+ t.remove_index [:name, :color]
+ end
+ end
+ end
+
class LegacyMigration < ActiveRecord::Migration
def self.up
create_table("horses") do |t|
@@ -104,6 +122,16 @@ module ActiveRecord
end
end
+ def test_exception_on_removing_index_without_column_option
+ RemoveIndexMigration1.new.migrate(:up)
+ migration = RemoveIndexMigration2.new
+ migration.migrate(:up)
+
+ assert_raises(IrreversibleMigration) do
+ migration.migrate(:down)
+ end
+ end
+
def test_migrate_up
migration = InvertibleMigration.new
migration.migrate(:up)
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 77891b9156..0030f1b464 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -242,7 +242,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
car = Car.create!
assert_difference 'car.wheels.count' do
- car.wheels << Wheel.create!
+ car.wheels << Wheel.create!
end
assert_difference 'car.wheels.count', -1 do
car.destroy
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index cad759bba9..e37dca856d 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -74,6 +74,35 @@ module ActiveRecord
assert_equal "hello", five.default unless mysql
end
+ def test_add_column_with_array
+ if current_adapter?(:PostgreSQLAdapter)
+ connection.create_table :testings
+ connection.add_column :testings, :foo, :string, :array => true
+
+ columns = connection.columns(:testings)
+ array_column = columns.detect { |c| c.name == "foo" }
+
+ assert array_column.array
+ else
+ skip "array option only supported in PostgreSQLAdapter"
+ end
+ end
+
+ def test_create_table_with_array_column
+ if current_adapter?(:PostgreSQLAdapter)
+ connection.create_table :testings do |t|
+ t.string :foo, :array => true
+ end
+
+ columns = connection.columns(:testings)
+ array_column = columns.detect { |c| c.name == "foo" }
+
+ assert array_column.array
+ else
+ skip "array option only supported in PostgreSQLAdapter"
+ end
+ end
+
def test_create_table_with_limits
connection.create_table :testings do |t|
t.column :foo, :string, :limit => 255
@@ -235,7 +264,7 @@ module ActiveRecord
end
end
- def test_keeping_default_and_notnull_constaint_on_change
+ def test_keeping_default_and_notnull_constraints_on_change
connection.create_table :testings do |t|
t.column :title, :string
end
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 52906c8a01..2d7a7ec73a 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -55,10 +55,10 @@ module ActiveRecord
default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
assert_equal 70000, default_before
- rename_column "test_models", "salary", "anual_salary"
+ rename_column "test_models", "salary", "annual_salary"
- assert TestModel.column_names.include?("anual_salary")
- default_after = connection.columns("test_models").find { |c| c.name == "anual_salary" }.default
+ assert TestModel.column_names.include?("annual_salary")
+ default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
assert_equal 70000, default_after
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 0e375af6e8..04521a5f5a 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -109,16 +109,6 @@ module ActiveRecord
end
end
- def test_deprecated_type_argument
- message = "Passing a string as third argument of `add_index` is deprecated and will" +
- " be removed in Rails 4.1." +
- " Use add_index(:testings, [:foo, :bar], unique: true) instead"
-
- assert_deprecated message do
- connection.add_index :testings, [:foo, :bar], "UNIQUE"
- end
- end
-
def test_unique_index_exists
connection.add_index :testings, :foo, :unique => true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f8afb7c591..e99312c245 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -258,6 +258,32 @@ class MigrationTest < ActiveRecord::TestCase
"On error, the Migrator should revert schema changes but it did not."
end
+ def test_migrator_one_up_with_exception_and_rollback_using_run
+ unless ActiveRecord::Base.connection.supports_ddl_transactions?
+ skip "not supported on #{ActiveRecord::Base.connection.class}"
+ end
+
+ assert_not Person.column_methods_hash.include?(:last_name)
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ raise 'Something broke'
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ e = assert_raise(StandardError) { migrator.run }
+
+ assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message
+
+ Person.reset_column_information
+ assert_not Person.column_methods_hash.include?(:last_name),
+ "On error, the Migrator should revert schema changes but it did not."
+ end
+
def test_migration_without_transaction
unless ActiveRecord::Base.connection.supports_ddl_transactions?
skip "not supported on #{ActiveRecord::Base.connection.class}"
@@ -823,4 +849,13 @@ class CopyMigrationsTest < ActiveRecord::TestCase
ensure
clear
end
+
+ def test_check_pending_with_stdlib_logger
+ old, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ::Logger.new($stdout)
+ quietly do
+ assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new {}).call({}) }
+ end
+ ensure
+ ActiveRecord::Base.logger = old
+ end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index b6e140b912..6fe81e0d96 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -131,6 +131,20 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert_equal 's1', ship.reload.name
end
+ def test_reuse_already_built_new_record
+ pirate = Pirate.new
+ ship_built_first = pirate.build_ship
+ pirate.ship_attributes = { name: 'Ship 1' }
+ assert_equal ship_built_first.object_id, pirate.ship.object_id
+ end
+
+ def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ pirate.build_ship
+ pirate.ship_attributes = { name: 'Ship 1', pirate_id: pirate.id + 1 }
+ assert_equal pirate.id, pirate.ship.pirate_id
+ end
+
def test_reject_if_with_a_proc_which_returns_true_always_for_has_many
Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }
man = Man.create(name: "John")
@@ -786,7 +800,9 @@ module NestedAttributesOnACollectionAssociationTests
def test_validate_presence_of_parent_fails_without_inverse_of
Man.accepts_nested_attributes_for(:interests)
Man.reflect_on_association(:interests).options.delete(:inverse_of)
+ Man.reflect_on_association(:interests).clear_inverse_of_cache!
Interest.reflect_on_association(:man).options.delete(:inverse_of)
+ Interest.reflect_on_association(:man).clear_inverse_of_cache!
repair_validations(Interest) do
Interest.validates_presence_of(:man)
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 572431ee87..30dc2a34c6 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -12,13 +12,13 @@ require 'models/minimalistic'
require 'models/warehouse_thing'
require 'models/parrot'
require 'models/minivan'
+require 'models/owner'
require 'models/person'
require 'models/pet'
require 'models/toy'
require 'rexml/document'
-class PersistencesTest < ActiveRecord::TestCase
-
+class PersistenceTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys
# Oracle UPDATE does not support ORDER BY
@@ -139,6 +139,19 @@ class PersistencesTest < ActiveRecord::TestCase
end
end
+ def test_becomes
+ assert_kind_of Reply, topics(:first).becomes(Reply)
+ assert_equal "The First Topic", topics(:first).becomes(Reply).title
+ end
+
+ def test_becomes_includes_errors
+ company = Company.new(:name => nil)
+ assert !company.valid?
+ original_errors = company.errors
+ client = company.becomes(Client)
+ assert_equal original_errors, client.errors
+ end
+
def test_delete_many
original_count = Topic.count
Topic.delete(deleting = [1, 2])
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 8e5379cb1f..aa125c70c5 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -205,7 +205,7 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- def test_primaery_key_method_with_ansi_quotes
+ def test_primary_key_method_with_ansi_quotes
con = ActiveRecord::Base.connection
con.execute("SET SESSION sql_mode='ANSI_QUOTES'")
assert_equal "id", con.primary_key("topics")
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index df076c97b4..2afd25c989 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/author'
require 'models/post'
require 'models/comment'
require 'models/developer'
@@ -7,7 +8,7 @@ require 'models/reader'
require 'models/person'
class ReadOnlyTest < ActiveRecord::TestCase
- fixtures :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
+ fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
def test_cant_save_readonly_record
dev = Developer.find(1)
@@ -34,15 +35,12 @@ class ReadOnlyTest < ActiveRecord::TestCase
Developer.readonly.each { |d| assert d.readonly? }
end
+ def test_find_with_joins_option_does_not_imply_readonly
+ Developer.joins(' ').each { |d| assert_not d.readonly? }
+ Developer.joins(' ').readonly(true).each { |d| assert d.readonly? }
- def test_find_with_joins_option_implies_readonly
- # Blank joins don't count.
- Developer.joins(' ').each { |d| assert !d.readonly? }
- Developer.joins(' ').readonly(false).each { |d| assert !d.readonly? }
-
- # Others do.
- Developer.joins(', projects').each { |d| assert d.readonly? }
- Developer.joins(', projects').readonly(false).each { |d| assert !d.readonly? }
+ Developer.joins(', projects').each { |d| assert_not d.readonly? }
+ Developer.joins(', projects').readonly(true).each { |d| assert d.readonly? }
end
def test_has_many_find_readonly
@@ -87,7 +85,7 @@ class ReadOnlyTest < ActiveRecord::TestCase
# conflicting column names
unless current_adapter?(:OracleAdapter)
Post.joins(', developers').scoping do
- assert Post.find(1).readonly?
+ assert_not Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index a9d46f4fba..b5314bc9be 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -35,18 +35,18 @@ class ReflectionTest < ActiveRecord::TestCase
def test_read_attribute_names
assert_equal(
- %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort,
+ %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort,
@first.attribute_names.sort
)
end
def test_columns
- assert_equal 17, Topic.columns.length
+ assert_equal 18, Topic.columns.length
end
def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name }
- assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
end
def test_content_columns
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index c43c7601a2..d333be3560 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -5,6 +5,7 @@ require 'models/treasure'
require 'models/post'
require 'models/comment'
require 'models/edge'
+require 'models/topic'
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
@@ -80,6 +81,13 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_aliased_attribute
+ expected = Topic.where(heading: 'The First Topic')
+ actual = Topic.where(title: 'The First Topic')
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
def test_where_error
assert_raises(ActiveRecord::StatementInvalid) do
Post.where(:id => { 'posts.author_id' => 10 }).first
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 9ca980fdf6..55fd068d37 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -1,19 +1,18 @@
require "cases/helper"
require 'models/post'
require 'models/comment'
+require 'models/author'
+require 'models/rating'
module ActiveRecord
class RelationTest < ActiveRecord::TestCase
- fixtures :posts, :comments
+ fixtures :posts, :comments, :authors
class FakeKlass < Struct.new(:table_name, :name)
end
def test_construction
- relation = nil
- assert_nothing_raised do
- relation = Relation.new FakeKlass, :b
- end
+ relation = Relation.new FakeKlass, :b
assert_equal FakeKlass, relation.klass
assert_equal :b, relation.table
assert !relation.loaded, 'relation is not loaded'
@@ -176,6 +175,21 @@ module ActiveRecord
relation.merge!(where: ['foo = ?', 'bar'])
assert_equal ['foo = bar'], relation.where_values
end
+
+ def test_relation_merging_with_merged_joins
+ special_comments_with_ratings = SpecialComment.joins(:ratings)
+ posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
+ assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
+ end
+
+ def test_respond_to_for_non_selected_element
+ post = Post.select(:title).first
+ assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
+
+ silence_warnings { post = Post.select("'title' as post_title").first }
+ assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
+ end
+
end
class RelationMutationTest < ActiveSupport::TestCase
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 9008c2785e..e746ca2805 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -278,8 +278,9 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_calculations_methods
assert_no_queries do
- assert_equal 0, Developer.none.count
- assert_equal nil, Developer.none.calculate(:average, 'salary')
+ assert_equal 0, Developer.none.count
+ assert_equal 0, Developer.none.calculate(:count, nil, {})
+ assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
@@ -1299,6 +1300,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
end
+ def test_doesnt_add_having_values_if_options_are_blank
+ scope = Post.having('')
+ assert_equal [], scope.having_values
+
+ scope = Post.having([])
+ assert_equal [], scope.having_values
+ end
+
def test_references_triggers_eager_loading
scope = Post.includes(:comments)
assert !scope.eager_loading?
@@ -1538,4 +1547,26 @@ class RelationTest < ActiveRecord::TestCase
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
+
+ def test_merging_removes_rhs_bind_parameters
+ left = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ column = Post.columns_hash['id']
+ left.bind_values += [[column, 20]]
+ right = Post.where(id: 10)
+
+ merged = left.merge(right)
+ assert_equal [], merged.bind_values
+ end
+
+ def test_merging_keeps_lhs_bind_parameters
+ column = Post.columns_hash['id']
+ binds = [[column, 20]]
+
+ right = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ right.bind_values += binds
+ left = Post.where(id: 10)
+
+ merged = left.merge(right)
+ assert_equal binds, merged.bind_values
+ end
end
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 817897ceac..082570c55b 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -5,6 +5,15 @@ class SanitizeTest < ActiveRecord::TestCase
def setup
end
+ def test_sanitize_sql_hash_handles_associations
+ quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
+ quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name")
+ quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals")
+ expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}"
+
+ assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}})
+ end
+
def test_sanitize_sql_array_handles_string_interpolation
quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"])
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 6e03a2bd67..a48ae1036f 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -177,10 +177,10 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) or current_adapter?(:PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
- else
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
+ if current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
+ else
+ assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
end
@@ -188,7 +188,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
- elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ elsif current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
else
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
@@ -242,6 +242,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
if current_adapter?(:PostgreSQLAdapter)
+ def test_schema_dump_includes_bigint_default
+ output = standard_dump
+ assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
+ end
+
def test_schema_dump_includes_extensions
connection = ActiveRecord::Base.connection
skip unless connection.supports_extensions?
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index e2af66ff9d..0f69443839 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -1,334 +1,6 @@
-require "cases/helper"
+require 'cases/helper'
require 'models/post'
-require 'models/author'
require 'models/developer'
-require 'models/project'
-require 'models/comment'
-require 'models/category'
-require 'models/person'
-require 'models/reference'
-
-class RelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
-
- def test_reverse_order
- assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order
- end
-
- def test_reverse_order_with_arel_node
- assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order
- end
-
- def test_reverse_order_with_multiple_arel_nodes
- assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order
- end
-
- def test_reverse_order_with_arel_nodes_and_strings
- assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order
- end
-
- def test_double_reverse_order_produces_original_order
- assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order
- end
-
- def test_scoped_find
- Developer.where("name = 'David'").scoping do
- assert_nothing_raised { Developer.find(1) }
- end
- end
-
- def test_scoped_find_first
- developer = Developer.find(10)
- Developer.where("salary = 100000").scoping do
- assert_equal developer, Developer.order("name").first
- end
- end
-
- def test_scoped_find_last
- highest_salary = Developer.order("salary DESC").first
-
- Developer.order("salary").scoping do
- assert_equal highest_salary, Developer.last
- end
- end
-
- def test_scoped_find_last_preserves_scope
- 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
- assert_equal lowest_salary, Developer.first
- end
- end
-
- def test_scoped_find_combines_and_sanitizes_conditions
- Developer.where("salary = 9000").scoping do
- assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
- end
- end
-
- def test_scoped_find_all
- Developer.where("name = 'David'").scoping do
- assert_equal [developers(:david)], Developer.all
- end
- end
-
- def test_scoped_find_select
- Developer.select("id, name").scoping do
- developer = Developer.where("name = 'David'").first
- assert_equal "David", developer.name
- assert !developer.has_attribute?(:salary)
- end
- end
-
- def test_scope_select_concatenates
- Developer.select("id, name").scoping do
- developer = Developer.select('salary').where("name = 'David'").first
- 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.where("name = 'David'").scoping do
- assert_equal 1, Developer.count
- end
-
- Developer.where('salary = 100000').scoping do
- assert_equal 8, Developer.count
- assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
- end
- end
-
- def test_scoped_find_include
- # with the include, will retrieve only developers for the given project
- scoped_developers = Developer.includes(:projects).scoping do
- Developer.where('projects.id' => 2).to_a
- 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.joins('JOIN developers_projects ON id = developer_id').scoping do
- Developer.where('developers_projects.project_id = 2').to_a
- 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_create_with_where
- new_comment = VerySpecialComment.where(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_create_with
- new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_create_with_has_higher_priority
- new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_ensure_that_method_scoping_is_correctly_restored
- begin
- Developer.where("name = 'Jamis'").scoping do
- raise "an exception"
- end
- rescue
- end
-
- assert !Developer.all.where_values.include?("name = 'Jamis'")
- end
-
- def test_default_scope_filters_on_joins
- assert_equal 1, DeveloperFilteredOnJoins.all.count
- assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins)
- end
-
- def test_update_all_default_scope_filters_on_joins
- DeveloperFilteredOnJoins.update_all(:salary => 65000)
- assert_equal 65000, Developer.find(developers(:david).id).salary
-
- # has not changed jamis
- assert_not_equal 65000, Developer.find(developers(:jamis).id).salary
- end
-
- def test_delete_all_default_scope_filters_on_joins
- assert_not_equal [], DeveloperFilteredOnJoins.all
-
- DeveloperFilteredOnJoins.delete_all()
-
- assert_equal [], DeveloperFilteredOnJoins.all
- assert_not_equal [], Developer.all
- end
-end
-
-class NestedRelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts
-
- def test_merge_options
- Developer.where('salary = 80000').scoping do
- Developer.limit(10).scoping do
- devs = Developer.all
- assert_match '(salary = 80000)', devs.to_sql
- assert_equal 10, devs.taken
- end
- end
- end
-
- def test_merge_inner_scope_has_priority
- Developer.limit(5).scoping do
- Developer.limit(10).scoping do
- assert_equal 10, Developer.all.size
- end
- end
- end
-
- def test_replace_options
- Developer.where(:name => 'David').scoping do
- Developer.unscoped do
- assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
- end
-
- assert_equal 'David', Developer.first[:name]
- end
- end
-
- def test_three_level_nested_exclusive_scoped_find
- Developer.where("name = 'Jamis'").scoping do
- assert_equal 'Jamis', Developer.first.name
-
- Developer.unscoped.where("name = 'David'") do
- assert_equal 'David', Developer.first.name
-
- Developer.unscoped.where("name = 'Maiha'") do
- assert_equal nil, Developer.first
- end
-
- # ensure that scoping is restored
- assert_equal 'David', Developer.first.name
- end
-
- # ensure that scoping is restored
- assert_equal 'Jamis', Developer.first.name
- end
- end
-
- def test_nested_scoped_create
- comment = Comment.create_with(:post_id => 1).scoping do
- Comment.create_with(:post_id => 2).scoping do
- 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 = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
- Comment.unscoped.create_with(:post_id => 1).scoping do
- assert Comment.new.body.blank?
- Comment.create :body => "Hey guys"
- end
- end
-
- assert_equal 1, comment.post_id
- assert_equal 'Hey guys', comment.body
- end
-end
-
-class HasManyScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts, :people, :references
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a comment...', Comment.what_are_you
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- def test_forwarding_to_scoped
- assert_equal 4, Comment.search_by_type('Comment').size
- assert_equal 2, @welcome.comments.search_by_type('Comment').size
- end
-
- def test_nested_scope_finder
- Comment.where('1=0').scoping do
- assert_equal 0, @welcome.comments.count
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- Comment.where('1=1').scoping do
- assert_equal 2, @welcome.comments.count
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
- end
-
- def test_should_maintain_default_scope_on_associations
- magician = BadReference.find(1)
- assert_equal [magician], people(:michael).bad_references
- end
-
- def test_should_default_scope_on_associations_is_overridden_by_association_conditions
- reference = references(:michael_unicyclist).becomes(BadReference)
- assert_equal [reference], people(:michael).fixed_bad_references
- end
-
- def test_should_maintain_default_scope_on_eager_loaded_associations
- michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
- magician = BadReference.find(1)
- assert_equal [magician], michael.bad_references
- end
-end
-
-class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
- fixtures :posts, :categories, :categories_posts
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a category...', Category.what_are_you
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- def test_nested_scope_finder
- Category.where('1=0').scoping do
- assert_equal 0, @welcome.categories.count
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- Category.where('1=1').scoping do
- assert_equal 2, @welcome.categories.count
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
- end
-end
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts
@@ -526,6 +198,16 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_unscope_and_scope
+ developer_klass = Class.new(Developer) do
+ scope :by_name, -> name { unscope(where: :name).where(name: name) }
+ end
+
+ expected = developer_klass.where(name: 'Jamis').collect { |dev| [dev.name, dev.id] }
+ received = developer_klass.where(name: 'David').by_name('Jamis').collect { |dev| [dev.name, dev.id] }
+ assert_equal expected, received
+ end
+
def test_unscope_errors_with_invalid_value
assert_raises(ArgumentError) do
Developer.includes(:projects).where(name: "Jamis").unscope(:stupidly_incorrect_value)
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 1eb3bc4177..afe32af1d1 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -6,7 +6,7 @@ require 'models/reply'
require 'models/author'
require 'models/developer'
-class NamedScopeTest < ActiveRecord::TestCase
+class NamedScopingTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
@@ -459,4 +459,9 @@ class NamedScopeTest < ActiveRecord::TestCase
end
assert_equal [posts(:welcome).title], klass.all.map(&:title)
end
+
+ def test_subclass_merges_scopes_properly
+ assert_equal 1, SpecialComment.where(body: 'go crazy').created.count
+ end
+
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
new file mode 100644
index 0000000000..0018fc06f2
--- /dev/null
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -0,0 +1,331 @@
+require "cases/helper"
+require 'models/post'
+require 'models/author'
+require 'models/developer'
+require 'models/project'
+require 'models/comment'
+require 'models/category'
+require 'models/person'
+require 'models/reference'
+
+class RelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+
+ def test_reverse_order
+ assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order
+ end
+
+ def test_reverse_order_with_arel_node
+ assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order
+ end
+
+ def test_reverse_order_with_multiple_arel_nodes
+ assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order
+ end
+
+ def test_reverse_order_with_arel_nodes_and_strings
+ assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order
+ end
+
+ def test_double_reverse_order_produces_original_order
+ assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order
+ end
+
+ def test_scoped_find
+ Developer.where("name = 'David'").scoping do
+ assert_nothing_raised { Developer.find(1) }
+ end
+ end
+
+ def test_scoped_find_first
+ developer = Developer.find(10)
+ Developer.where("salary = 100000").scoping do
+ assert_equal developer, Developer.order("name").first
+ end
+ end
+
+ def test_scoped_find_last
+ highest_salary = Developer.order("salary DESC").first
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ end
+ end
+
+ def test_scoped_find_last_preserves_scope
+ 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
+ assert_equal lowest_salary, Developer.first
+ end
+ end
+
+ def test_scoped_find_combines_and_sanitizes_conditions
+ Developer.where("salary = 9000").scoping do
+ assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
+ end
+ end
+
+ def test_scoped_find_all
+ Developer.where("name = 'David'").scoping do
+ assert_equal [developers(:david)], Developer.all
+ end
+ end
+
+ def test_scoped_find_select
+ Developer.select("id, name").scoping do
+ developer = Developer.where("name = 'David'").first
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scope_select_concatenates
+ Developer.select("id, name").scoping do
+ developer = Developer.select('salary').where("name = 'David'").first
+ 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.where("name = 'David'").scoping do
+ assert_equal 1, Developer.count
+ end
+
+ Developer.where('salary = 100000').scoping do
+ assert_equal 8, Developer.count
+ assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
+ end
+ end
+
+ def test_scoped_find_include
+ # with the include, will retrieve only developers for the given project
+ scoped_developers = Developer.includes(:projects).scoping do
+ Developer.where('projects.id' => 2).to_a
+ 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.joins('JOIN developers_projects ON id = developer_id').scoping do
+ Developer.where('developers_projects.project_id = 2').to_a
+ 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_create_with_where
+ new_comment = VerySpecialComment.where(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with
+ new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with_has_higher_priority
+ new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_ensure_that_method_scoping_is_correctly_restored
+ begin
+ Developer.where("name = 'Jamis'").scoping do
+ raise "an exception"
+ end
+ rescue
+ end
+
+ assert !Developer.all.where_values.include?("name = 'Jamis'")
+ end
+
+ def test_default_scope_filters_on_joins
+ assert_equal 1, DeveloperFilteredOnJoins.all.count
+ assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins)
+ end
+
+ def test_update_all_default_scope_filters_on_joins
+ DeveloperFilteredOnJoins.update_all(:salary => 65000)
+ assert_equal 65000, Developer.find(developers(:david).id).salary
+
+ # has not changed jamis
+ assert_not_equal 65000, Developer.find(developers(:jamis).id).salary
+ end
+
+ def test_delete_all_default_scope_filters_on_joins
+ assert_not_equal [], DeveloperFilteredOnJoins.all
+
+ DeveloperFilteredOnJoins.delete_all()
+
+ assert_equal [], DeveloperFilteredOnJoins.all
+ assert_not_equal [], Developer.all
+ end
+end
+
+class NestedRelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts
+
+ def test_merge_options
+ Developer.where('salary = 80000').scoping do
+ Developer.limit(10).scoping do
+ devs = Developer.all
+ assert_match '(salary = 80000)', devs.to_sql
+ assert_equal 10, devs.taken
+ end
+ end
+ end
+
+ def test_merge_inner_scope_has_priority
+ Developer.limit(5).scoping do
+ Developer.limit(10).scoping do
+ assert_equal 10, Developer.all.size
+ end
+ end
+ end
+
+ def test_replace_options
+ Developer.where(:name => 'David').scoping do
+ Developer.unscoped do
+ assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
+ end
+
+ assert_equal 'David', Developer.first[:name]
+ end
+ end
+
+ def test_three_level_nested_exclusive_scoped_find
+ Developer.where("name = 'Jamis'").scoping do
+ assert_equal 'Jamis', Developer.first.name
+
+ Developer.unscoped.where("name = 'David'") do
+ assert_equal 'David', Developer.first.name
+
+ Developer.unscoped.where("name = 'Maiha'") do
+ assert_equal nil, Developer.first
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'David', Developer.first.name
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'Jamis', Developer.first.name
+ end
+ end
+
+ def test_nested_scoped_create
+ comment = Comment.create_with(:post_id => 1).scoping do
+ Comment.create_with(:post_id => 2).scoping do
+ 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 = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
+ Comment.unscoped.create_with(:post_id => 1).scoping do
+ assert Comment.new.body.blank?
+ Comment.create :body => "Hey guys"
+ end
+ end
+
+ assert_equal 1, comment.post_id
+ assert_equal 'Hey guys', comment.body
+ end
+end
+
+class HasManyScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts, :people, :references
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a comment...', Comment.what_are_you
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ def test_forwarding_to_scoped
+ assert_equal 4, Comment.search_by_type('Comment').size
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
+ end
+
+ def test_nested_scope_finder
+ Comment.where('1=0').scoping do
+ assert_equal 0, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ Comment.where('1=1').scoping do
+ assert_equal 2, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+ end
+
+ def test_should_maintain_default_scope_on_associations
+ magician = BadReference.find(1)
+ assert_equal [magician], people(:michael).bad_references
+ end
+
+ def test_should_default_scope_on_associations_is_overridden_by_association_conditions
+ reference = references(:michael_unicyclist).becomes(BadReference)
+ assert_equal [reference], people(:michael).fixed_bad_references
+ end
+
+ def test_should_maintain_default_scope_on_eager_loaded_associations
+ michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
+ magician = BadReference.find(1)
+ assert_equal [magician], michael.bad_references
+ end
+end
+
+class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
+ fixtures :posts, :categories, :categories_posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a category...', Category.what_are_you
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ def test_nested_scope_finder
+ Category.where('1=0').scoping do
+ assert_equal 0, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ Category.where('1=1').scoping do
+ assert_equal 2, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+ end
+end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 726338db14..b49c27bc78 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
require 'models/topic'
+require 'models/reply'
require 'models/person'
require 'models/traffic_light'
require 'bcrypt'
@@ -18,12 +19,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal %w(content), Topic.serialized_attributes.keys
end
- def test_serialized_attributes_are_class_level_settings
- topic = Topic.new
- assert_raise(NoMethodError) { topic.serialized_attributes = [] }
- assert_deprecated { topic.serialized_attributes }
- end
-
def test_serialized_attribute
Topic.serialize("content", MyObject)
@@ -241,4 +236,17 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal [], light.state
assert_equal [], light.long_state
end
+
+ def test_serialized_column_should_not_be_wrapped_twice
+ Topic.serialize(:content, MyObject)
+
+ myobj = MyObject.new('value1', 'value2')
+ Topic.create(content: myobj)
+ Topic.create(content: myobj)
+
+ Topic.all.each do |topic|
+ type = topic.instance_variable_get("@columns_hash")["content"]
+ assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type)
+ end
+ end
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
new file mode 100644
index 0000000000..76da49707f
--- /dev/null
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -0,0 +1,64 @@
+require 'cases/helper'
+require 'models/book'
+require 'models/liquid'
+require 'models/molecule'
+require 'models/electron'
+
+module ActiveRecord
+ class StatementCacheTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_statement_cache_with_simple_statement
+ cache = ActiveRecord::StatementCache.new do
+ Book.where(name: "my book").where("author_id > 3")
+ end
+
+ Book.create(name: "my book", author_id: 4)
+
+ books = cache.execute
+ assert_equal "my book", books[0].name
+ end
+
+ def test_statement_cache_with_nil_statement_raises_error
+ assert_raise(ArgumentError) do
+ ActiveRecord::StatementCache.new do
+ nil
+ end
+ end
+ end
+
+ def test_statement_cache_with_complex_statement
+ cache = ActiveRecord::StatementCache.new do
+ Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton')
+ end
+
+ salty = Liquid.create(name: 'salty')
+ molecule = salty.molecules.create(name: 'dioxane')
+ molecule.electrons.create(name: 'lepton')
+
+ liquids = cache.execute
+ assert_equal "salty", liquids[0].name
+ end
+
+ def test_statement_cache_values_differ
+ cache = ActiveRecord::StatementCache.new do
+ Book.where(name: "my book")
+ end
+
+ 3.times do
+ Book.create(name: "my book")
+ end
+
+ first_books = cache.execute
+
+ 3.times do
+ Book.create(name: "my book")
+ end
+
+ additional_books = cache.execute
+ assert first_books != additional_books
+ end
+ end
+end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 3e32d866ee..c2c56abacd 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -150,9 +150,4 @@ class StoreTest < ActiveRecord::TestCase
test "all stored attributes are returned" do
assert_equal [:color, :homepage, :favorite_food], Admin::User.stored_attributes[:settings]
end
-
- test "stores_attributes are class level settings" do
- assert_raise(NoMethodError) { @john.stored_attributes = Hash.new }
- assert_raise(NoMethodError) { @john.stored_attributes }
- end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 3bfbc92afd..e9000fef25 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -305,4 +305,11 @@ module ActiveRecord
end
end
end
+
+ class DatabaseTasksCheckSchemaFileTest < ActiveRecord::TestCase
+ def test_check_schema_file
+ Kernel.expects(:abort).with(regexp_matches(/awesome-file.sql/))
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file("awesome-file.sql")
+ end
+ end
end
diff --git a/activerecord/test/cases/tasks/firebird_rake_test.rb b/activerecord/test/cases/tasks/firebird_rake_test.rb
new file mode 100644
index 0000000000..c54989ae34
--- /dev/null
+++ b/activerecord/test/cases/tasks/firebird_rake_test.rb
@@ -0,0 +1,100 @@
+require 'cases/helper'
+
+unless defined?(FireRuby::Database)
+module FireRuby
+ module Database; end
+end
+end
+
+module ActiveRecord
+ module FirebirdSetupper
+ def setup
+ @database = 'db.firebird'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'firebird',
+ 'database' => @database
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::FirebirdDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::FirebirdAdapter
+ end
+ end
+
+ class FirebirdDBCreateTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdDBDropTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdStructureDumpTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def setup
+ super
+ FireRuby::Database.stubs(:db_string_for).returns(@database)
+ end
+
+ def test_structure_dump
+ filename = "filebird.sql"
+ Kernel.expects(:system).with("isql -a #{@database} > #{filename}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+
+ class FirebirdStructureLoadTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def setup
+ super
+ FireRuby::Database.stubs(:db_string_for).returns(@database)
+ end
+
+ def test_structure_load
+ filename = "firebird.sql"
+ Kernel.expects(:system).with("isql -i #{filename} #{@database}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/oracle_rake_test.rb b/activerecord/test/cases/tasks/oracle_rake_test.rb
new file mode 100644
index 0000000000..5f840febbc
--- /dev/null
+++ b/activerecord/test/cases/tasks/oracle_rake_test.rb
@@ -0,0 +1,93 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module OracleSetupper
+ def setup
+ @database = 'db.oracle'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'oracle',
+ 'database' => @database
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::OracleDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::OracleAdapter
+ end
+ end
+
+ class OracleDBCreateTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleDBDropTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleStructureDumpTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def setup
+ super
+ @connection.stubs(:structure_dump).returns("select sysdate from dual;")
+ end
+
+ def test_structure_dump
+ filename = "oracle.sql"
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm_f(filename)
+ end
+ end
+
+ class OracleStructureLoadTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_structure_load
+ filename = "oracle.sql"
+
+ open(filename, 'w') { |f| f.puts("select sysdate from dual;") }
+ @connection.stubs(:execute).with("select sysdate from dual;\n")
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ ensure
+ FileUtils.rm_f(filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 3006a87589..f31896bc7f 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -225,9 +225,9 @@ module ActiveRecord
Kernel.stubs(:system)
end
- def test_structure_dump
+ def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with("psql -f #{filename} my-app-db")
+ Kernel.expects(:system).with("psql -q -f #{filename} my-app-db")
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/cases/tasks/sqlserver_rake_test.rb b/activerecord/test/cases/tasks/sqlserver_rake_test.rb
new file mode 100644
index 0000000000..0f1264b8ce
--- /dev/null
+++ b/activerecord/test/cases/tasks/sqlserver_rake_test.rb
@@ -0,0 +1,87 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module SqlserverSetupper
+ def setup
+ @database = 'db.sqlserver'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlserver',
+ 'database' => @database,
+ 'host' => 'localhost',
+ 'username' => 'username',
+ 'password' => 'password',
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::SqlserverDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::SQLServerAdapter
+ end
+ end
+
+ class SqlserverDBCreateTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverDBDropTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverStructureDumpTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_structure_dump
+ filename = "sqlserver.sql"
+ Kernel.expects(:system).with("smoscript -s localhost -d #{@database} -u username -p password -f #{filename} -A -U")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+
+ class SqlserverStructureLoadTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_structure_load
+ filename = "sqlserver.sql"
+ Kernel.expects(:system).with("sqlcmd -S localhost -d #{@database} -U username -P password -i #{filename}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 777a2b70dd..ff1b01556d 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -176,6 +176,106 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal time, owner.updated_at
end
+ def test_touching_a_record_touches_polymorphic_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ end
+
+ wheel_klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Wheel'; end
+ belongs_to :wheelable, :polymorphic => true, :touch => true
+ end
+
+ toy = klass.first
+ time = 3.days.ago
+ toy.update_columns(updated_at: time)
+
+ wheel = wheel_klass.new
+ wheel.wheelable = toy
+ wheel.save
+ wheel.touch
+
+ assert_not_equal time, toy.updated_at
+ end
+
+ def test_changing_parent_of_a_record_touches_both_new_and_old_parent_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, touch: true
+ end
+
+ toy1 = klass.find(1)
+ old_pet = toy1.pet
+
+ toy2 = klass.find(2)
+ new_pet = toy2.pet
+ time = 3.days.ago.at_beginning_of_hour
+
+ old_pet.update_columns(updated_at: time)
+ new_pet.update_columns(updated_at: time)
+
+ toy1.pet = new_pet
+ toy1.save!
+
+ old_pet.reload
+ new_pet.reload
+
+ assert_not_equal time, new_pet.updated_at
+ assert_not_equal time, old_pet.updated_at
+ end
+
+ def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ end
+
+ wheel_klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Wheel'; end
+ belongs_to :wheelable, :polymorphic => true, :touch => true
+ end
+
+ toy1 = klass.find(1)
+ toy2 = klass.find(2)
+
+ wheel = wheel_klass.new
+ wheel.wheelable = toy1
+ wheel.save!
+
+ time = 3.days.ago.at_beginning_of_hour
+
+ toy1.update_columns(updated_at: time)
+ toy2.update_columns(updated_at: time)
+
+ wheel.wheelable = toy2
+ wheel.save!
+
+ toy1.reload
+ toy2.reload
+
+ assert_not_equal time, toy1.updated_at
+ assert_not_equal time, toy2.updated_at
+ end
+
+ def test_clearing_association_touches_the_old_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, touch: true
+ end
+
+ toy = klass.find(1)
+ pet = toy.pet
+ time = 3.days.ago.at_beginning_of_hour
+
+ pet.update_columns(updated_at: time)
+
+ toy.pet = nil
+ toy.save!
+
+ pet.reload
+
+ assert_not_equal time, pet.updated_at
+ end
+
def test_timestamp_attributes_for_create
toy = Toy.first
assert_equal toy.send(:timestamp_attributes_for_create), [:created_at, :created_on]
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 766a5c0c90..9485de88a6 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -281,38 +281,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
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
-
class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 7ac34bc71e..7e92a2b127 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -118,21 +118,4 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
end
- def test_validates_associated_models_in_the_same_context
- Topic.validates_presence_of :title, :on => :custom_context
- Topic.validates_associated :replies
- Reply.validates_presence_of :title, :on => :custom_context
-
- t = Topic.new('title' => '')
- r = t.replies.new('title' => '')
-
- assert t.valid?
- assert !t.valid?(:custom_context)
-
- t.title = "Longer"
- assert !t.valid?(:custom_context), "Should NOT be valid if the associated object is not valid in the same context."
-
- r.title = "Longer"
- assert t.valid?(:custom_context), "Should be valid if the associated object is not valid in the same context."
- end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 57457359b1..2b33f01783 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -268,7 +268,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
- Topic.validates_uniqueness_of(:title, :case_sensitve => true)
+ Topic.validates_uniqueness_of(:title, :case_sensitive => true)
Topic.create!('title' => 101)
t2 = Topic.new('title' => 101)
diff --git a/activerecord/test/fixtures/pets.yml b/activerecord/test/fixtures/pets.yml
index a1601a53f0..2ec4f53e6d 100644
--- a/activerecord/test/fixtures/pets.yml
+++ b/activerecord/test/fixtures/pets.yml
@@ -12,3 +12,8 @@ mochi:
pet_id: 3
name: mochi
owner_id: 2
+
+bulbul:
+ pet_id: 4
+ name: bulbul
+ owner_id: 1
diff --git a/activerecord/test/fixtures/toys.yml b/activerecord/test/fixtures/toys.yml
index 037e335e0a..ae9044ec62 100644
--- a/activerecord/test/fixtures/toys.yml
+++ b/activerecord/test/fixtures/toys.yml
@@ -2,3 +2,13 @@ bone:
toy_id: 1
name: Bone
pet_id: 1
+
+doll:
+ toy_id: 2
+ name: Doll
+ pet_id: 2
+
+bulbuli:
+ toy_id: 3
+ name: Bulbuli
+ pet_id: 4
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 24a65b0f2f..816c5e6937 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -1,6 +1,6 @@
class Club < ActiveRecord::Base
has_one :membership
- has_many :memberships
+ has_many :memberships, :inverse_of => false
has_many :members, :through => :memberships
has_many :current_memberships
has_one :sponsor
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 3ca8f69646..dcda62e71d 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -206,6 +206,8 @@ class Account < ActiveRecord::Base
belongs_to :firm, :class_name => 'Company'
belongs_to :unautosaved_firm, :foreign_key => "firm_id", :class_name => "Firm", :autosave => false
+ alias_attribute :available_credit, :credit_limit
+
def self.destroyed_account_ids
@destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] }
end
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 1134b09d8b..cc47c7bc18 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -9,7 +9,7 @@ class Member < ActiveRecord::Base
has_one :hairy_club, -> { where :clubs => {:name => "Moustache and Eyebrow Fancier Club"} }, :through => :membership, :source => :club
has_one :sponsor, :as => :sponsorable
has_one :sponsor_club, :through => :sponsor
- has_one :member_detail
+ has_one :member_detail, :inverse_of => false
has_one :organization, :through => :member_detail
belongs_to :member_type
diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb
index fe619f8732..9d253aa126 100644
--- a/activerecord/test/models/member_detail.rb
+++ b/activerecord/test/models/member_detail.rb
@@ -1,5 +1,5 @@
class MemberDetail < ActiveRecord::Base
- belongs_to :member
+ belongs_to :member, :inverse_of => false
belongs_to :organization
has_one :member_type, :through => :member
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index fea55f4535..1c7ed4aa3e 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -1,5 +1,5 @@
class Owner < ActiveRecord::Base
self.primary_key = :owner_id
- has_many :pets
+ has_many :pets, -> { order 'pets.name desc' }
has_many :toys, :through => :pets
end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 2985160d28..1a282dbce4 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -32,6 +32,7 @@ class Person < ActiveRecord::Base
has_many :agents_posts, :through => :agents, :source => :posts
has_many :agents_posts_authors, :through => :agents_posts, :source => :author
+ has_many :essays, primary_key: "first_name", foreign_key: "writer_id"
scope :males, -> { where(:gender => 'M') }
scope :females, -> { where(:gender => 'F') }
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index 3cd5bceed5..f7970d7aab 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -1,5 +1,4 @@
class Pet < ActiveRecord::Base
-
attr_accessor :current_user
self.primary_key = :pet_id
@@ -13,5 +12,4 @@ class Pet < ActiveRecord::Base
after_destroy do |record|
Pet.after_destroy_output = record.current_user
end
-
end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index c88262580e..3e82e55d89 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -7,6 +7,7 @@ class Reply < Topic
end
class UniqueReply < Reply
+ belongs_to :topic, :foreign_key => 'parent_id', :counter_cache => true
validates_uniqueness_of :content, :scope => 'parent_id'
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 1b1457ab9c..a9a6514c9d 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -52,7 +52,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('true','false')
+ enum_column ENUM('text','blob','tiny','medium','long')
)
SQL
end
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index ecdce1519b..f2cffca52c 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -63,7 +63,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('true','false')
+ enum_column ENUM('text','blob','tiny','medium','long')
)
SQL
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index d8271ac8d1..6b7012a172 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -32,6 +32,7 @@ ActiveRecord::Schema.define do
char3 text default 'a text field',
positive_integer integer default 1,
negative_integer integer default -1,
+ bigint_default bigint default 0::bigint,
decimal_number decimal(3,2) default 2.78,
multiline_default text DEFAULT '--- []
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 8beb58f3fc..188a3f0164 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -675,6 +675,7 @@ ActiveRecord::Schema.define do
end
t.boolean :approved, :default => true
t.integer :replies_count, :default => 0
+ t.integer :unique_replies_count, :default => 0
t.integer :parent_id
t.string :parent_title
t.string :type
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 08ec528419..1863eec947 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,493 +1,60 @@
-## Rails 4.0.0 (unreleased) ##
-
-* `ActiveSupport::Notifications::Instrumenter#instrument` should yield
- its payload.
-
- *stopdropandrew*
-
-* `ActiveSupport::TimeWithZone` raises `NoMethodError` in proper context.
- Fixes #9772.
-
- *Yves Senn*
-
-* Fix deletion of empty directories in `ActiveSupport::Cache::FileStore`.
-
- *Charles Jones*
-
-
-## Rails 4.0.0.beta1 (February 25, 2013) ##
-
-* Improve singularizing a singular for multiple cases.
- Fixes #2608 #1825 #2395.
-
- Example:
-
- # Before
- 'address'.singularize # => 'addres'
-
- # After
- 'address'.singularize # => 'address'
-
- *Mark McSpadden*
-
-* Prevent `DateTime#change` from truncating the second fraction, when seconds
- do not need to be changed.
-
- *Chris Baynes*
-
-* Added `ActiveSupport::TimeWithZone#to_r` for `Time#at` compatibility.
-
- Before this change:
-
- Time.zone = 'Tokyo'
- time = Time.zone.now
- time == Time.at(time) # => false
-
- After the change:
-
- Time.zone = 'Tokyo'
- time = Time.zone.now
- time == Time.at(time) # => true
-
- *stopdropandrew*
-
-* `ActiveSupport::NumberHelper#number_to_human` returns the number unaltered when
- the given units hash does not contain the needed key, e.g. when the number provided
- is less than the largest key provided.
- Fixes #9269.
-
- Examples:
-
- number_to_human(123, units: {}) # => 123
- number_to_human(123, units: { thousand: 'k' }) # => 123
-
- *Michael Hoffman*
-
-* Added `beginning_of_minute` support to core ext calculations for `Time` and `DateTime`.
-
- *Gagan Awhad*
-
-* Add `:nsec` date format.
-
- *Jamie Gaskins*
-
-* `ActiveSupport::Gzip.compress` allows two optional arguments for compression
- level and strategy.
-
- *Beyond*
-
-* Modify `TimeWithZone#as_json` to include 3 decimal places of sub-second accuracy
- by default, which is optional as per the ISO8601 spec, but extremely useful. Also
- the default behaviour of `Date#toJSON()` in recent versions of Chrome, Safari and
- Firefox.
-
- *James Harton*
-
-* Improve `String#squish` to handle Unicode whitespace. *Antoine Lyset*
-
-* Standardise on `to_time` returning an instance of `Time` in the local system timezone
- across `String`, `Time`, `Date`, `DateTime` and `ActiveSupport::TimeWithZone`.
+* Add `DateTime#usec` and `DateTime#nsec` so that `ActiveSupport::TimeWithZone` keeps
+ sub-second resolution when wrapping a `DateTime` value.
+ Fixes #10855
*Andrew White*
-* Extract `ActiveSupport::Testing::Performance` into https://github.com/rails/rails-perftest
- You can add the gem to your `Gemfile` to keep using performance tests.
-
- gem 'rails-perftest'
-
- *Yves Senn*
-
-* `Hash.from_xml` raises when it encounters `type="symbol"` or `type="yaml"`.
- Use `Hash.from_trusted_xml` to parse this XML.
-
- CVE-2013-0156
-
- *Jeremy Kemper*
-
-* Deprecate `assert_present` and `assert_blank` in favor of
- `assert object.blank?` and `assert object.present?`
-
- *Yves Senn*
-
-* Change `String#to_date` to use `Date.parse`. This gives more consistent error
- messages and allows the use of partial dates.
+* Fix `ActiveSupport::Dependencies::Loadable#load_dependency` calling
+ `#blame_file!` on Exceptions that do not have the Blamable mixin
- "gibberish".to_date => Argument Error: invalid date
- "3rd Feb".to_date => Sun, 03 Feb 2013
+ *Andrew Kreiling*
- *Kelly Stannard*
-
-* It's now possible to compare `Date`, `DateTime`, `Time` and `TimeWithZone`
- with `Float::INFINITY`. This allows to create date/time ranges with one infinite bound.
- Example:
-
- range = Range.new(Date.today, Float::INFINITY)
-
- Also it's possible to check inclusion of date/time in range with conversion.
-
- range.include?(Time.now + 1.year) # => true
- range.include?(DateTime.now + 1.year) # => true
-
- *Alexander Grebennik*
-
-* Remove meaningless `ActiveSupport::FrozenObjectError`, which was just an alias of `RuntimeError`.
-
- *Akira Matsuda*
-
-* Introduce `assert_not` to replace warty `assert !foo`. *Jeremy Kemper*
-
-* Prevent `Callbacks#set_callback` from setting the same callback twice.
-
- before_save :foo, :bar, :foo
-
- will at first call `bar`, then `foo`. `foo` will no more be called
- twice.
-
- *Dmitriy Kiriyenko*
-
-* Add `ActiveSupport::Logger#silence` that works the same as the old `Logger#silence` extension.
-
- *DHH*
-
-* Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode`
- The encoding scheme was broken for unicode characters outside the basic multilingual plane;
- since json is assumed to be UTF-8, and we already force the encoding to UTF-8,
- simply pass through the un-encoded characters.
-
- *Brett Carter*
-
-* Deprecate `Time.time_with_date_fallback`, `Time.utc_time` and `Time.local_time`.
- These methods were added to handle the limited range of Ruby's native `Time`
- implementation. Those limitations no longer apply so we are deprecating them in 4.0
- and they will be removed in 4.1.
+* Override `Time.at` to support the passing of Time-like values when called with a single argument.
*Andrew White*
-* Deprecate `Date#to_time_in_current_zone` and add `Date#in_time_zone`. *Andrew White*
-
-* Add `String#in_time_zone` method to convert a string to an `ActiveSupport::TimeWithZone`. *Andrew White*
-
-* Deprecate `ActiveSupport::BasicObject` in favor of `ActiveSupport::ProxyObject`.
- This class is used for proxy classes. It avoids confusion with Ruby's `BasicObject`
- class.
-
- *Francesco Rodriguez*
-
-* Patched `Marshal#load` to work with constant autoloading. Fixes autoloading
- with cache stores that rely on `Marshal` (`MemCacheStore` and `FileStore`).
- Fixes #8167.
-
- *Uriel Katz*
-
-* Make `Time.zone.parse` to work with JavaScript format date strings. *Andrew White*
-
-* Add `DateTime#seconds_until_end_of_day` and `Time#seconds_until_end_of_day`
- as a complement for `seconds_from_midnight`; useful when setting expiration
- times for caches, e.g.:
-
- <% cache('dashboard', expires_in: Date.current.seconds_until_end_of_day) do %>
- ...
-
- *Olek Janiszewski*
-
-* No longer proxy `ActiveSupport::Multibyte#class`. *Steve Klabnik*
-
-* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from minitest instead. *Carlos Antonio da Silva*
-
-* `XmlMini.with_backend` now may be safely used with threads:
-
- Thread.new do
- XmlMini.with_backend("REXML") { rexml_power }
- end
- Thread.new do
- XmlMini.with_backend("LibXML") { libxml_power }
- end
-
- Each thread will use it's own backend.
-
- *Nikita Afanasenko*
-
-* Dependencies no longer trigger `Kernel#autoload` in `remove_constant`. Fixes #8213. *Xavier Noria*
-
-* Simplify `mocha` integration and remove monkey-patches, bumping `mocha` to 0.13.0. *James Mead*
-
-* `#as_json` isolates options when encoding a hash. Fixes #8182.
+* Prevent side effects to hashes inside arrays when
+ `Hash#with_indifferent_access` is called.
+ Fixes #10526
*Yves Senn*
-* Deprecate `Hash#diff` in favor of minitest's #diff. *Steve Klabnik*
-
-* `Kernel#capture` can catch output from subprocesses. *Dmitry Vorotilin*
-
-* `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`.
-
- *Nikita Afanasenko*
-
-* Fixed timezone mapping of the Solomon Islands. *Steve Klabnik*
-
-* Make callstack attribute optional in `ActiveSupport::Deprecation::Reporting`
- methods `warn` and `deprecation_warning`.
-
- *Alexey Gaziev*
-
-* Implement `HashWithIndifferentAccess#replace` so `key?` works correctly. *David Graham*
-
-* Handle the possible permission denied errors `atomic.rb` might trigger due to its `chown`
- and `chmod` calls.
-
- *Daniele Sluijters*
+* Raise an error when multiple `included` blocks are defined for a Concern.
+ The old behavior would silently discard previously defined blocks, running
+ only the last one.
-* `Hash#extract!` returns only those keys that present in the receiver.
+ *Mike Dillon*
- {a: 1, b: 2}.extract!(:a, :x) # => {:a => 1}
+* Replace `multi_json` with `json`.
- *Mikhail Dieterle*
+ Since Rails requires Ruby 1.9 and since Ruby 1.9 includes `json` in the standard library,
+ `multi_json` is no longer necessary.
-* `Hash#extract!` returns the same subclass, that the receiver is. I.e.
- `HashWithIndifferentAccess#extract!` returns a `HashWithIndifferentAccess` instance.
+ *Erik Michaels-Ober*
- *Mikhail Dieterle*
+* Added escaping of U+2028 and U+2029 inside the json encoder.
+ These characters are legal in JSON but break the Javascript interpreter.
+ After escaping them, the JSON is still legal and can be parsed by Javascript.
-* Optimize `ActiveSupport::Cache::Entry` to reduce memory and processing overhead. *Brian Durand*
+ *Mario Caropreso + Viktor Kelemen + zackham*
-* Tests tag the Rails log with the current test class and test case:
+* Fix skipping object callbacks using metadata fetched via callback chain
+ inspection methods (`_*_callbacks`)
- [SessionsControllerTest] [test_0002_sign in] Processing by SessionsController#create as HTML
- [SessionsControllerTest] [test_0002_sign in] ...
+ *Sean Walbran*
- *Jeremy Kemper*
+* Add a `fetch_multi` method to the cache stores. The method provides
+ an easy to use API for fetching multiple values from the cache.
-* Add `logger.push_tags` and `.pop_tags` to complement `logger.tagged`:
-
- class Job
- def before
- Rails.logger.push_tags :jobs, self.class.name
- end
-
- def after
- Rails.logger.pop_tags 2
- end
- end
-
- *Jeremy Kemper*
-
-* Allow delegation to the class using the `:class` keyword, replacing
- `self.class` usage:
-
- class User
- def self.hello
- "world"
- end
-
- delegate :hello, to: :class
- end
-
- *Marc-Andre Lafortune*
-
-* `Date.beginning_of_week` thread local and `beginning_of_week` application
- config option added (default is Monday).
-
- *Innokenty Mikhailov*
-
-* An optional block can be passed to `config_accessor` to set its default value
-
- class User
- include ActiveSupport::Configurable
-
- config_accessor :hair_colors do
- [:brown, :black, :blonde, :red]
- end
- end
-
- User.hair_colors # => [:brown, :black, :blonde, :red]
-
- *Larry Lv*
-
-* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of
- thread safety. It will be removed without replacement in Rails 4.1.
-
- *Steve Klabnik*
-
-* An optional block can be passed to `Hash#deep_merge`. The block will be invoked
- for each duplicated key and used to resolve the conflict.
-
- *Pranas Kiziela*
-
-* `ActiveSupport::Deprecation` is now a class. It is possible to create an instance
- of deprecator. Backwards compatibility has been preserved.
-
- You can choose which instance of the deprecator will be used.
-
- deprecate :method_name, deprecator: deprecator_instance
-
- You can use `ActiveSupport::Deprecation` in your gem.
-
- require 'active_support/deprecation'
- require 'active_support/core_ext/module/deprecation'
-
- class MyGem
- def self.deprecator
- ActiveSupport::Deprecation.new('2.0', 'MyGem')
- end
-
- def old_method
- end
-
- def new_method
- end
-
- deprecate old_method: :new_method, deprecator: deprecator
- end
-
- MyGem.new.old_method
- # => DEPRECATION WARNING: old_method is deprecated and will be removed from MyGem 2.0 (use new_method instead). (called from <main> at file.rb:18)
-
- *Piotr Niełacny & Robert Pankowecki*
-
-* `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov*
-
-* `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks.
- Using the `filter` method like this:
-
- before_filter MyFilter.new
-
- class MyFilter
- def filter(controller)
- end
- end
-
- Is now deprecated with recommendation to use the corresponding filter type
- (`#before`, `#after` or `#around`):
-
- before_filter MyFilter.new
-
- class MyFilter
- def before(controller)
- end
- end
-
- *Bogdan Gusiev*
-
-* An optional block can be passed to `HashWithIndifferentAccess#update` and `#merge`.
- The block will be invoked for each duplicated key, and used to resolve the conflict,
- thus replicating the behaviour of the corresponding methods on the `Hash` class.
-
- *Leo Cassarani*
-
-* Remove `j` alias for `ERB::Util#json_escape`.
- The `j` alias is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`
- and both modules are included in the view context that would confuse the developers.
-
- *Akira Matsuda*
-
-* Replace deprecated `memcache-client` gem with `dalli` in `ActiveSupport::Cache::MemCacheStore`.
-
- *Guillermo Iguaran*
-
-* Add default values to all `ActiveSupport::NumberHelper` methods, to avoid
- errors with empty locales or missing values.
-
- *Carlos Antonio da Silva*
-
-* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and
- `#encode_json` methods for custom JSON string literals.
-
- *Erich Menge*
-
-* Add `String#indent`. *fxn & Ace Suares*
-
-* Inflections can now be defined per locale. `singularize` and `pluralize`
- accept locale as an extra argument.
-
- *David Celis*
-
-* `Object#try` will now return `nil` instead of raise a `NoMethodError` if the
- receiving object does not implement the method, but you can still get the
- old behavior by using the new `Object#try!`.
-
- *DHH*
-
-* `ERB::Util.html_escape` now escapes single quotes. *Santiago Pastorino*
-
-* `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White*
-
-* `ActiveSupport::Callbacks`: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev*
-
-* Add `Time#prev_quarter` and `Time#next_quarter` short-hands for `months_ago(3)` and `months_since(3)`. *SungHee Kang*
-
-* Remove obsolete and unused `require_association` method from dependencies. *fxn*
-
-* Add `:instance_accessor` option for `config_accessor`.
+ Example:
- class User
- include ActiveSupport::Configurable
- config_accessor :allowed_access, instance_accessor: false
+ # Calculating scores is expensive, so we only do it for posts
+ # that have been updated. Cache keys are automatically extracted
+ # from objects that define a #cache_key method.
+ scores = Rails.cache.fetch_multi(*posts) do |post|
+ calculate_score(post)
end
- User.new.allowed_access = true # => NoMethodError
- User.new.allowed_access # => NoMethodError
-
- *Francesco Rodriguez*
-
-* `ActionView::Helpers::NumberHelper` methods have been moved to `ActiveSupport::NumberHelper` and are now available via
- `Numeric#to_s`. `Numeric#to_s` now accepts the formatting options `:phone`, `:currency`, `:percentage`, `:delimited`,
- `:rounded`, `:human`, and `:human_size`.
-
- *Andrew Mutz*
-
-* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden*
-
-* Changed XML type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri*
-
-* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov*
-
-* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White*
-
-* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a `Hash` instance into strings. *Lucas Húngaro*
-
-* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a `Hash` instance into symbols. *Lucas Húngaro*
-
-* `Object#try` can't call private methods. *Vasiliy Ermolovich*
-
-* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez*
-
-* `deep_dup` works more expectedly now and duplicates also values in `Hash` instances and elements in `Array` instances. *Alexey Gaziev*
-
-* Inflector no longer applies ice -> ouse to words like "slice", "police", etc. *Wes Morgan*
-
-* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations. *twinturbo*
-
-* Make `Module#delegate` stop using `send` - can no longer delegate to private methods. *dasch*
-
-* `ActiveSupport::Callbacks`: deprecate `:rescuable` option. *Bogdan Gusiev*
-
-* Adds `Integer#ordinal` to get the ordinal suffix string of an integer. *Tim Gildea*
-
-* `ActiveSupport::Callbacks`: `:per_key` option is no longer supported. *Bogdan Gusiev*
-
-* `ActiveSupport::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. *Bogdan Gusiev*
-
-* Add `html_escape_once` to `ERB::Util`, and delegate the `escape_once` tag helper to it. *Carlos Antonio da Silva*
-
-* Deprecates the compatibility method `Module#local_constant_names`,
- use `Module#local_constants` instead (which returns symbols). *Xavier Noria*
-
-* Deletes the compatibility method `Module#method_names`,
- use `Module#methods` from now on (which returns symbols). *Xavier Noria*
-
-* Deletes the compatibility method `Module#instance_method_names`,
- use `Module#instance_methods` from now on (which returns symbols). *Xavier Noria*
-
-* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger
- from the Ruby standard library.
-
- *Aaron Patterson*
-
-* Unicode database updated to 6.1.0. *Norman Clarke*
-
-* Adds `encode_big_decimal_as_string` option to force JSON serialization of `BigDecimal` as numeric instead
- of wrapping them in strings for safety.
-
-* Optimize log subscribers to check log level before doing any processing. *Brian Durand*
+ *Daniel Schierbeck*
-Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index e3788ed54f..f50225a9f0 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -9,13 +9,16 @@ Rake::TestTask.new do |t|
t.verbose = true
end
+
namespace :test do
- Rake::TestTask.new(:isolated) do |t|
- t.pattern = 'test/ts_isolated.rb'
+ task :isolated do
+ ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
+ Dir.glob("test/**/*_test.rb").all? do |file|
+ sh(ruby, '-Ilib:test', file)
+ end or raise "Failures"
end
end
-
spec = eval(File.read('activesupport.gemspec'))
Gem::PackageTask.new(spec) do |p|
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index b46a331f6a..ffc2c2074e 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -21,8 +21,8 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
s.add_dependency('i18n', '~> 0.6', '>= 0.6.4')
- s.add_dependency 'multi_json', '~> 1.3'
+ s.add_dependency 'json', '~> 1.7'
s.add_dependency 'tzinfo', '~> 0.3.37'
- s.add_dependency 'minitest', '~> 4.2'
+ s.add_dependency 'minitest', '~> 5.0'
s.add_dependency 'thread_safe','~> 0.1'
end
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 6413502b53..805b7a714f 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -45,15 +45,5 @@ module ActiveSupport
yield
end
end
-
- # Silence the logger during the execution of the block.
- def silence
- message = "ActiveSupport::Benchmarkable#silence is deprecated. It will be removed from Rails 4.1."
- ActiveSupport::Deprecation.warn message
- old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
- yield
- ensure
- logger.level = old_logger_level if logger
- end
end
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 6bfac15289..b1ab5570a8 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -56,16 +56,7 @@ module ActiveSupport
case store
when Symbol
- store_class_name = store.to_s.camelize
- store_class =
- begin
- require "active_support/cache/#{store}"
- rescue LoadError => e
- raise "Could not find cache store adapter for #{store} (#{e})"
- else
- ActiveSupport::Cache.const_get(store_class_name)
- end
- store_class.new(*parameters)
+ retrieve_store_class(store).new(*parameters)
when nil
ActiveSupport::Cache::MemoryStore.new
else
@@ -73,6 +64,18 @@ module ActiveSupport
end
end
+ # Expands out the +key+ argument into a key that can be used for the
+ # cache store. Optionally accepts a namespace, and all keys will be
+ # scoped within that namespace.
+ #
+ # If the +key+ argument provided is an array, or responds to +to_a+, then
+ # each of elements in the array will be turned into parameters/keys and
+ # concatenated into a single key. For example:
+ #
+ # expand_cache_key([:foo, :bar]) # => "foo/bar"
+ # expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
+ #
+ # The +key+ argument can also respond to +cache_key+ or +to_param+.
def expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
@@ -94,6 +97,16 @@ module ActiveSupport
else key.to_param
end.to_s
end
+
+ # Obtains the specified cache store class, given the name of the +store+.
+ # Raises an error when the store class cannot be found.
+ def retrieve_store_class(store)
+ require "active_support/cache/#{store}"
+ rescue LoadError => e
+ raise "Could not find cache store adapter for #{store} (#{e})"
+ else
+ ActiveSupport::Cache.const_get(store.to_s.camelize)
+ end
end
# An abstract cache store class. There are multiple cache store
@@ -339,6 +352,34 @@ module ActiveSupport
results
end
+ # Fetches data from the cache, using the given keys. If there is data in
+ # the cache with the given keys, then that data is returned. Otherwise,
+ # the supplied block is called for each key for which there was no data,
+ # and the result will be written to the cache and returned.
+ #
+ # Options are passed to the underlying cache implementation.
+ #
+ # Returns an array with the data for each of the names. For example:
+ #
+ # cache.write("bim", "bam")
+ # cache.fetch_multi("bim", "boom") {|key| key * 2 }
+ # #=> ["bam", "boomboom"]
+ #
+ def fetch_multi(*names)
+ options = names.extract_options!
+ options = merged_options(options)
+
+ results = read_multi(*names, options)
+
+ names.map do |name|
+ results.fetch(name) do
+ value = yield name
+ write(name, value, options)
+ value
+ end
+ end
+ end
+
# Writes the value to the cache, with the key.
#
# Options are passed to the underlying cache implementation.
@@ -522,7 +563,7 @@ module ActiveSupport
def handle_expired_entry(entry, key, options)
if entry && entry.expired?
race_ttl = options[:race_condition_ttl].to_i
- if race_ttl && (Time.now - entry.expires_at <= race_ttl)
+ if race_ttl && (Time.now.to_f - entry.expires_at <= race_ttl)
# When an entry has :race_condition_ttl defined, put the stale entry back into the cache
# for a brief period while the entry is begin recalculated.
entry.expires_at = Time.now + race_ttl
@@ -562,38 +603,38 @@ module ActiveSupport
# +:compress+, +:compress_threshold+, and +:expires_in+.
def initialize(value, options = {})
if should_compress?(value, options)
- @v = compress(value)
- @c = true
+ @value = compress(value)
+ @compressed = true
else
- @v = value
- end
- if expires_in = options[:expires_in]
- @x = (Time.now + expires_in).to_i
+ @value = value
end
+ @created_at = Time.now.to_f
+ @expires_in = options[:expires_in]
+ @expires_in = @expires_in.to_f if @expires_in
end
def value
- convert_version_3_entry! if defined?(@value)
- compressed? ? uncompress(@v) : @v
+ convert_version_4beta1_entry! if defined?(@v)
+ compressed? ? uncompress(@value) : @value
end
# Check if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
- convert_version_3_entry! if defined?(@value)
- if defined?(@x)
- @x && @x < Time.now.to_i
- else
- false
- end
+ convert_version_4beta1_entry! if defined?(@value)
+ @expires_in && @created_at + @expires_in <= Time.now.to_f
end
def expires_at
- Time.at(@x) if defined?(@x)
+ @expires_in ? @created_at + @expires_in : nil
end
def expires_at=(value)
- @x = value.to_i
+ if value
+ @expires_in = value.to_f - @created_at
+ else
+ @expires_in = nil
+ end
end
# Returns the size of the cached value. This could be less than
@@ -606,9 +647,9 @@ module ActiveSupport
when NilClass
0
when String
- @v.bytesize
+ @value.bytesize
else
- @s = Marshal.dump(@v).bytesize
+ @s = Marshal.dump(@value).bytesize
end
end
end
@@ -616,12 +657,12 @@ module ActiveSupport
# Duplicate the value in a class. This is used by cache implementations that don't natively
# serialize entries to protect against accidental cache modifications.
def dup_value!
- convert_version_3_entry! if defined?(@value)
- if @v && !compressed? && !(@v.is_a?(Numeric) || @v == true || @v == false)
- if @v.is_a?(String)
- @v = @v.dup
+ convert_version_4beta1_entry! if defined?(@v)
+ if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
+ if @value.is_a?(String)
+ @value = @value.dup
else
- @v = Marshal.load(Marshal.dump(@v))
+ @value = Marshal.load(Marshal.dump(@value))
end
end
end
@@ -637,7 +678,7 @@ module ActiveSupport
end
def compressed?
- defined?(@c) ? @c : false
+ defined?(@compressed) ? @compressed : false
end
def compress(value)
@@ -650,19 +691,19 @@ module ActiveSupport
# The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
# to ensure that cache entries created under the old version still work with the new class definition.
- def convert_version_3_entry!
- if defined?(@value)
- @v = @value
- remove_instance_variable(:@value)
+ def convert_version_4beta1_entry!
+ if defined?(@v)
+ @value = @v
+ remove_instance_variable(:@v)
end
- if defined?(@compressed)
- @c = @compressed
- remove_instance_variable(:@compressed)
+ if defined?(@c)
+ @compressed = @c
+ remove_instance_variable(:@c)
end
- if defined?(@expires_in) && defined?(@created_at) && @expires_in && @created_at
- @x = (@created_at + @expires_in).to_i
- remove_instance_variable(:@created_at)
- remove_instance_variable(:@expires_in)
+ if defined?(@x) && @x
+ @created_at ||= Time.now.to_f
+ @expires_in = @x - @created_at
+ remove_instance_variable(:@x)
end
end
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index db5f228a70..fb42c4a41e 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -8,6 +8,23 @@ module ActiveSupport
# duration of a block. Repeated calls to the cache for the same key will hit the
# in-memory cache for faster access.
module LocalCache
+ # Class for storing and registering the local caches.
+ class LocalCacheRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ def initialize
+ @registry = {}
+ end
+
+ def cache_for(local_cache_key)
+ @registry[local_cache_key]
+ end
+
+ def set_cache_for(local_cache_key, value)
+ @registry[local_cache_key] = value
+ end
+ end
+
# Simple memory backed cache. This cache is not thread safe and is intended only
# for serving as a temporary memory cache for a single thread.
class LocalStore < Store
@@ -41,24 +58,18 @@ module ActiveSupport
# Use a local cache for the duration of block.
def with_local_cache
- save_val = Thread.current[thread_local_key]
- begin
- Thread.current[thread_local_key] = LocalStore.new
- yield
- ensure
- Thread.current[thread_local_key] = save_val
- end
+ use_temporary_local_cache(LocalStore.new) { yield }
end
#--
# This class wraps up local storage for middlewares. Only the middleware method should
# construct them.
class Middleware # :nodoc:
- attr_reader :name, :thread_local_key
+ attr_reader :name, :local_cache_key
- def initialize(name, thread_local_key)
+ def initialize(name, local_cache_key)
@name = name
- @thread_local_key = thread_local_key
+ @local_cache_key = local_cache_key
@app = nil
end
@@ -68,10 +79,10 @@ module ActiveSupport
end
def call(env)
- Thread.current[thread_local_key] = LocalStore.new
+ LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
@app.call(env)
ensure
- Thread.current[thread_local_key] = nil
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil)
end
end
@@ -80,7 +91,7 @@ module ActiveSupport
def middleware
@middleware ||= Middleware.new(
"ActiveSupport::Cache::Strategy::LocalCache",
- thread_local_key)
+ local_cache_key)
end
def clear(options = nil) # :nodoc:
@@ -95,29 +106,13 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- if local_cache
- local_cache.mute do
- if value
- local_cache.write(name, value, options)
- else
- local_cache.delete(name, options)
- end
- end
- end
+ increment_or_decrement(value, name, amount, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- if local_cache
- local_cache.mute do
- if value
- local_cache.write(name, value, options)
- else
- local_cache.delete(name, options)
- end
- end
- end
+ increment_or_decrement(value, name, amount, options)
value
end
@@ -146,21 +141,37 @@ module ActiveSupport
end
private
- def thread_local_key
- @thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
+ def increment_or_decrement(value, name, amount, options)
+ if local_cache
+ local_cache.mute do
+ if value
+ local_cache.write(name, value, options)
+ else
+ local_cache.delete(name, options)
+ end
+ end
+ end
+ end
+
+ def local_cache_key
+ @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
end
def local_cache
- Thread.current[thread_local_key]
+ LocalCacheRegistry.cache_for(local_cache_key)
end
def bypass_local_cache
- save_cache = Thread.current[thread_local_key]
+ use_temporary_local_cache(nil) { yield }
+ end
+
+ def use_temporary_local_cache(temporary_cache)
+ save_cache = LocalCacheRegistry.cache_for(local_cache_key)
begin
- Thread.current[thread_local_key] = nil
+ LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache)
yield
ensure
- Thread.current[thread_local_key] = save_cache
+ LocalCacheRegistry.set_cache_for(local_cache_key, save_cache)
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index f2d9df6d13..2cffa342ef 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,9 +1,9 @@
-require 'thread_safe'
require 'active_support/concern'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'thread'
module ActiveSupport
# Callbacks are code hooks that are run at key points in an object's lifecycle.
@@ -61,6 +61,8 @@ module ActiveSupport
extend ActiveSupport::DescendantsTracker
end
+ CALLBACK_FILTER_TYPES = [:before, :after, :around]
+
# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
@@ -74,8 +76,14 @@ module ActiveSupport
# save
# end
def run_callbacks(kind, &block)
- runner_name = self.class.__define_callbacks(kind, self)
- send(runner_name, &block)
+ cbs = send("_#{kind}_callbacks")
+ if cbs.empty?
+ yield if block_given?
+ else
+ runner = cbs.compile
+ e = Filters::Environment.new(self, false, nil, block)
+ runner.call(e).value
+ end
end
private
@@ -86,159 +94,315 @@ module ActiveSupport
def halted_callback_hook(filter)
end
- class Callback #:nodoc:#
- @@_callback_sequence = 0
-
- attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
+ module Conditionals # :nodoc:
+ class Value
+ def initialize(&block)
+ @block = block
+ end
+ def call(target, value); @block.call(value); end
+ end
+ end
- def initialize(chain, filter, kind, options, klass)
- @chain, @kind, @klass = chain, kind, klass
- deprecate_per_key_option(options)
- normalize_options!(options)
+ module Filters
+ Environment = Struct.new(:target, :halted, :value, :run_block)
- @raw_filter, @options = filter, options
- @filter = _compile_filter(filter)
- recompile_options!
+ class End
+ def call(env)
+ block = env.run_block
+ env.value = !env.halted && (!block || block.call)
+ env
+ end
end
+ ENDING = End.new
+
+ class Before
+ def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
+ halted_lambda = chain_config[:terminator]
- def deprecate_per_key_option(options)
- if options[:per_key]
- raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
+ if chain_config.key?(:terminator) && user_conditions.any?
+ halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ elsif chain_config.key? :terminator
+ halting(next_callback, user_callback, halted_lambda, filter)
+ elsif user_conditions.any?
+ conditional(next_callback, user_callback, user_conditions)
+ else
+ simple next_callback, user_callback
+ end
end
- end
- def clone(chain, klass)
- obj = super()
- obj.chain = chain
- obj.klass = klass
- obj.options = @options.dup
- obj.options[:if] = @options[:if].dup
- obj.options[:unless] = @options[:unless].dup
- obj
- end
+ private
- def normalize_options!(options)
- options[:if] = Array(options[:if])
- options[:unless] = Array(options[:unless])
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ lambda { |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ result = user_callback.call target, value
+ env.halted = halted_lambda.call(target, result)
+ if env.halted
+ target.send :halted_callback_hook, filter
+ end
+ end
+ next_callback.call env
+ }
+ end
+
+ def self.halting(next_callback, user_callback, halted_lambda, filter)
+ lambda { |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ unless halted
+ result = user_callback.call target, value
+ env.halted = halted_lambda.call(target, result)
+ if env.halted
+ target.send :halted_callback_hook, filter
+ end
+ end
+ next_callback.call env
+ }
+ end
+
+ def self.conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ target = env.target
+ value = env.value
+
+ if user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
+ next_callback.call env
+ }
+ end
+
+ def self.simple(next_callback, user_callback)
+ lambda { |env|
+ user_callback.call env.target, env.value
+ next_callback.call env
+ }
+ end
end
- def name
- chain.name
+ class After
+ def self.build(next_callback, user_callback, user_conditions, chain_config)
+ if chain_config[:skip_after_callbacks_if_terminated]
+ if chain_config.key?(:terminator) && user_conditions.any?
+ halting_and_conditional(next_callback, user_callback, user_conditions)
+ elsif chain_config.key?(:terminator)
+ halting(next_callback, user_callback)
+ elsif user_conditions.any?
+ conditional next_callback, user_callback, user_conditions
+ else
+ simple next_callback, user_callback
+ end
+ else
+ if user_conditions.any?
+ conditional next_callback, user_callback, user_conditions
+ else
+ simple next_callback, user_callback
+ end
+ end
+ end
+
+ private
+
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ env = next_callback.call env
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
+ env
+ }
+ end
+
+ def self.halting(next_callback, user_callback)
+ lambda { |env|
+ env = next_callback.call env
+ unless env.halted
+ user_callback.call env.target, env.value
+ end
+ env
+ }
+ end
+
+ def self.conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ env = next_callback.call env
+ target = env.target
+ value = env.value
+
+ if user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
+ env
+ }
+ end
+
+ def self.simple(next_callback, user_callback)
+ lambda { |env|
+ env = next_callback.call env
+ user_callback.call env.target, env.value
+ env
+ }
+ end
end
- def next_id
- @@_callback_sequence += 1
+ class Around
+ def self.build(next_callback, user_callback, user_conditions, chain_config)
+ if chain_config.key?(:terminator) && user_conditions.any?
+ halting_and_conditional(next_callback, user_callback, user_conditions)
+ elsif chain_config.key? :terminator
+ halting(next_callback, user_callback)
+ elsif user_conditions.any?
+ conditional(next_callback, user_callback, user_conditions)
+ else
+ simple(next_callback, user_callback)
+ end
+ end
+
+ private
+
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call(target, value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ else
+ next_callback.call env
+ end
+ }
+ end
+
+ def self.halting(next_callback, user_callback)
+ lambda { |env|
+ target = env.target
+ value = env.value
+
+ unless env.halted
+ user_callback.call(target, value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ else
+ next_callback.call env
+ end
+ }
+ end
+
+ def self.conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ target = env.target
+ value = env.value
+
+ if user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call(target, value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ else
+ next_callback.call env
+ end
+ }
+ end
+
+ def self.simple(next_callback, user_callback)
+ lambda { |env|
+ user_callback.call(env.target, env.value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ }
+ end
end
+ end
- def matches?(_kind, _filter)
- @kind == _kind && @filter == _filter
+ class Callback #:nodoc:#
+ def self.build(chain, filter, kind, options)
+ new chain.name, filter, kind, options, chain.config
end
- def duplicates?(other)
- matches?(other.kind, other.filter)
+ attr_accessor :kind, :name
+ attr_reader :chain_config
+
+ def initialize(name, filter, kind, options, chain_config)
+ @chain_config = chain_config
+ @name = name
+ @kind = kind
+ @filter = filter
+ @key = compute_identifier filter
+ @if = Array(options[:if])
+ @unless = Array(options[:unless])
end
- def _update_filter(filter_options, new_options)
- filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
- filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
+ def filter; @key; end
+ def raw_filter; @filter; end
+
+ def merge(chain, new_options)
+ options = {
+ :if => @if.dup,
+ :unless => @unless.dup
+ }
+
+ options[:if].concat Array(new_options.fetch(:unless, []))
+ options[:unless].concat Array(new_options.fetch(:if, []))
+
+ self.class.build chain, @filter, @kind, options
end
- def recompile!(_options)
- deprecate_per_key_option(_options)
- _update_filter(self.options, _options)
+ def matches?(_kind, _filter)
+ @kind == _kind && filter == _filter
+ end
- recompile_options!
+ def duplicates?(other)
+ case @filter
+ when Symbol, String
+ matches?(other.kind, other.filter)
+ else
+ false
+ end
end
# Wraps code with filter
- def apply(code)
- case @kind
+ def apply(next_callback)
+ user_conditions = conditions_lambdas
+ user_callback = make_lambda @filter
+
+ case kind
when :before
- <<-RUBY_EVAL
- if !halted && #{@compiled_options}
- # This double assignment is to prevent warnings in 1.9.3 as
- # the `result` variable is not always used except if the
- # terminator code refers to it.
- result = result = #{@filter}
- halted = (#{chain.config[:terminator]})
- if halted
- halted_callback_hook(#{@raw_filter.inspect.inspect})
- end
- end
- #{code}
- RUBY_EVAL
+ Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
when :after
- <<-RUBY_EVAL
- #{code}
- if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
- #{@filter}
- end
- RUBY_EVAL
+ Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
when :around
- name = define_conditional_callback
- <<-RUBY_EVAL
- #{name}(halted) do
- #{code}
- value
- end
- RUBY_EVAL
+ Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
end
end
private
- # Compile around filters with conditions into proxy methods
- # that contain the conditions.
- #
- # For `set_callback :save, :around, :filter_name, if: :condition':
- #
- # def _conditional_callback_save_17
- # if condition
- # filter_name do
- # yield self
- # end
- # else
- # yield self
- # end
- # end
- def define_conditional_callback
- name = "_conditional_callback_#{@kind}_#{next_id}"
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}(halted)
- if #{@compiled_options} && !halted
- #{@filter} do
- yield self
- end
- else
- yield self
- end
- end
- RUBY_EVAL
- name
- end
-
- # Options support the same options as filters themselves (and support
- # symbols, string, procs, and objects), so compile a conditional
- # expression based on the options.
- def recompile_options!
- conditions = ["true"]
-
- unless options[:if].empty?
- conditions << Array(_compile_filter(options[:if]))
- end
-
- unless options[:unless].empty?
- conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
- end
-
- @compiled_options = conditions.flatten.join(" && ")
+ def invert_lambda(l)
+ lambda { |*args, &blk| !l.call(*args, &blk) }
end
# Filters support:
#
- # Arrays:: Used in conditions. This is used to specify
- # multiple conditions. Used internally to
- # merge conditions from skip_* filters.
# Symbols:: A method to call.
# Strings:: Some content to evaluate.
# Procs:: A proc to call with the object.
@@ -247,90 +411,106 @@ module ActiveSupport
# All of these objects are compiled into methods and handled
# the same after this point:
#
- # Arrays:: Merged together into a single filter.
# Symbols:: Already methods.
- # Strings:: class_eval'ed into methods.
- # Procs:: define_method'ed into methods.
+ # Strings:: class_eval'd into methods.
+ # Procs:: using define_method compiled into methods.
# Objects::
# a method is created that calls the before_foo method
# on the object.
- def _compile_filter(filter)
+ def make_lambda(filter)
case filter
- when Array
- filter.map {|f| _compile_filter(f)}
when Symbol
- filter
+ lambda { |target, _, &blk| target.send filter, &blk }
when String
- "(#{filter})"
- when Proc
- method_name = "_callback_#{@kind}_#{next_id}"
- @klass.send(:define_method, method_name, &filter)
- return method_name if filter.arity <= 0
+ l = eval "lambda { |value| #{filter} }"
+ lambda { |target, value| target.instance_exec(value, &l) }
+ when Conditionals::Value then filter
+ when ::Proc
+ if filter.arity > 1
+ return lambda { |target, _, &block|
+ raise ArgumentError unless block
+ target.instance_exec(target, block, &filter)
+ }
+ end
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
+ if filter.arity <= 0
+ lambda { |target, _| target.instance_exec(&filter) }
+ else
+ lambda { |target, _| target.instance_exec(target, &filter) }
+ end
else
- method_name = "_callback_#{@kind}_#{next_id}"
- @klass.send(:define_method, "#{method_name}_object") { filter }
-
- _normalize_legacy_filter(kind, filter)
- scopes = Array(chain.config[:scope])
- method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
+ scopes = Array(chain_config[:scope])
+ method_to_call = scopes.map{ |s| public_send(s) }.join("_")
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{method_name}(&blk)
- #{method_name}_object.send(:#{method_to_call}, self, &blk)
- end
- RUBY_EVAL
-
- method_name
+ lambda { |target, _, &blk|
+ filter.public_send method_to_call, target, &blk
+ }
end
end
- def _normalize_legacy_filter(kind, filter)
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
- message = "Filter object with #filter method is deprecated. Define method corresponding " \
- "to filter type (#before, #after or #around)."
- ActiveSupport::Deprecation.warn message
- filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{kind}(context, &block) filter(context, &block) end
- RUBY_EVAL
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around)
- message = "Filter object with #before and #after methods is deprecated. Define #around method instead."
- ActiveSupport::Deprecation.warn message
- def filter.around(context)
- should_continue = before(context)
- yield if should_continue
- after(context)
- end
+ def compute_identifier(filter)
+ case filter
+ when String, ::Proc
+ filter.object_id
+ else
+ filter
end
end
+
+ def conditions_lambdas
+ @if.map { |c| make_lambda c } +
+ @unless.map { |c| invert_lambda make_lambda c }
+ end
end
# An Array with a compile method.
- class CallbackChain < Array #:nodoc:#
+ class CallbackChain #:nodoc:#
+ include Enumerable
+
attr_reader :name, :config
def initialize(name, config)
@name = name
@config = {
- :terminator => "false",
:scope => [ :kind ]
- }.merge(config)
+ }.merge!(config)
+ @chain = []
+ @callbacks = nil
+ @mutex = Mutex.new
end
- def compile
- method = []
- method << "value = nil"
- method << "halted = false"
+ def each(&block); @chain.each(&block); end
+ def index(o); @chain.index(o); end
+ def empty?; @chain.empty?; end
- callbacks = "value = !halted && (!block_given? || yield)"
- reverse_each do |callback|
- callbacks = callback.apply(callbacks)
- end
- method << callbacks
+ def insert(index, o)
+ @callbacks = nil
+ @chain.insert(index, o)
+ end
+
+ def delete(o)
+ @callbacks = nil
+ @chain.delete(o)
+ end
+
+ def clear
+ @callbacks = nil
+ @chain.clear
+ self
+ end
- method << "value"
- method.join("\n")
+ def initialize_copy(other)
+ @callbacks = nil
+ @chain = other.chain.dup
+ @mutex = Mutex.new
+ end
+
+ def compile
+ @callbacks || @mutex.synchronize do
+ @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
+ callback.apply chain
+ end
+ end
end
def append(*callbacks)
@@ -341,69 +521,45 @@ module ActiveSupport
callbacks.each { |c| prepend_one(c) }
end
+ protected
+ def chain; @chain; end
+
private
def append_one(callback)
+ @callbacks = nil
remove_duplicates(callback)
- push(callback)
+ @chain.push(callback)
end
def prepend_one(callback)
+ @callbacks = nil
remove_duplicates(callback)
- unshift(callback)
+ @chain.unshift(callback)
end
def remove_duplicates(callback)
- delete_if { |c| callback.duplicates?(c) }
+ @callbacks = nil
+ @chain.delete_if { |c| callback.duplicates?(c) }
end
end
module ClassMethods
- # This method defines callback chain method for the given kind
- # if it was not yet defined.
- # This generated method plays caching role.
- def __define_callbacks(kind, object) #:nodoc:
- name = __callback_runner_name(kind)
- unless object.respond_to?(name, true)
- str = object.send("_#{kind}_callbacks").compile
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}() #{str} end
- protected :#{name}
- RUBY_EVAL
- end
- name
- end
-
- def __reset_runner(symbol)
- name = __callback_runner_name(symbol)
- undef_method(name) if method_defined?(name)
- end
-
- def __callback_runner_name_cache
- @__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
- end
-
- def __generate_callback_runner_name(kind)
- "_run__#{self.name.hash.abs}__#{kind}__callbacks"
- end
-
- def __callback_runner_name(kind)
- __callback_runner_name_cache[kind]
+ def normalize_callback_params(filters, block) # :nodoc:
+ type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
+ options = filters.last.is_a?(Hash) ? filters.pop : {}
+ filters.unshift(block) if block
+ [type, filters, options.dup]
end
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
- def __update_callbacks(name, filters = [], block = nil) #:nodoc:
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
- options = filters.last.is_a?(Hash) ? filters.pop : {}
- filters.unshift(block) if block
-
+ def __update_callbacks(name) #:nodoc:
([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)
+ chain = target.get_callbacks name
+ yield target, chain.dup
end
end
@@ -443,16 +599,15 @@ module ActiveSupport
# * <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
-
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
- mapped ||= filters.map do |filter|
- Callback.new(chain, filter, type, options.dup, self)
- end
+ type, filters, options = normalize_callback_params(filter_list, block)
+ self_chain = get_callbacks name
+ mapped = filters.map do |filter|
+ Callback.build(self_chain, filter, type, options)
+ end
+ __update_callbacks(name) do |target, chain|
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
-
- target.send("_#{name}_callbacks=", chain)
+ target.set_callbacks name, chain
end
end
@@ -464,36 +619,34 @@ module ActiveSupport
# skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
# end
def skip_callback(name, *filter_list, &block)
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
+ type, filters, options = normalize_callback_params(filter_list, block)
+
+ __update_callbacks(name) do |target, chain|
filters.each do |filter|
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
- new_filter = filter.clone(chain, self)
+ new_filter = filter.merge(chain, options)
chain.insert(chain.index(filter), new_filter)
- new_filter.recompile!(options)
end
chain.delete(filter)
end
- target.send("_#{name}_callbacks=", chain)
+ target.set_callbacks name, chain
end
end
# Remove all set callbacks for the given event.
def reset_callbacks(symbol)
- callbacks = send("_#{symbol}_callbacks")
+ callbacks = get_callbacks symbol
ActiveSupport::DescendantsTracker.descendants(self).each do |target|
- chain = target.send("_#{symbol}_callbacks").dup
+ chain = target.get_callbacks(symbol).dup
callbacks.each { |c| chain.delete(c) }
- target.send("_#{symbol}_callbacks=", chain)
- target.__reset_runner(symbol)
+ target.set_callbacks symbol, chain
end
- self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
-
- __reset_runner(symbol)
+ self.set_callbacks symbol, callbacks.dup.clear
end
# Define sets of events in the object lifecycle that support callbacks.
@@ -505,7 +658,7 @@ module ActiveSupport
#
# * <tt>:terminator</tt> - Determines when a before filter will halt the
# callback chain, preventing following callbacks from being called and
- # the event from being triggered. This is a string to be eval'ed. The
+ # the event from being triggered. This is a string to be eval'd. The
# result of the callback is available in the +result+ variable.
#
# define_callbacks :validate, terminator: 'result == false'
@@ -565,11 +718,28 @@ module ActiveSupport
# would call <tt>Audit#save</tt>.
def define_callbacks(*callbacks)
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
+ if config.key?(:terminator) && String === config[:terminator]
+ ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda"
+ value = config[:terminator]
+ l = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__
+ config[:terminator] = lambda { |target, result| target.instance_exec(result, &l) }
+ end
+
callbacks.each do |callback|
class_attribute "_#{callback}_callbacks"
- send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
+ set_callbacks callback, CallbackChain.new(callback, config)
end
end
+
+ protected
+
+ def get_callbacks(name)
+ send "_#{name}_callbacks"
+ end
+
+ def set_callbacks(name, callbacks)
+ send "_#{name}_callbacks=", callbacks
+ end
end
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index eeeba60839..b6ae86b583 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -98,6 +98,12 @@ module ActiveSupport
# include Bar # works, Bar takes care now of its dependencies
# end
module Concern
+ class MultipleIncludedBlocks < StandardError #:nodoc:
+ def initialize
+ super "Cannot define multiple 'included' blocks for a Concern"
+ end
+ end
+
def self.extended(base) #:nodoc:
base.instance_variable_set("@_dependencies", [])
end
@@ -117,6 +123,8 @@ module ActiveSupport
def included(base = nil, &block)
if base.nil?
+ raise MultipleIncludedBlocks if instance_variable_defined?("@_included_block")
+
@_included_block = block
else
super
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 430a35fbaf..3807ee63b1 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -12,7 +12,7 @@ class Array
# pass an option key that doesn't exist in the list below, it will raise an
# <tt>ArgumentError</tt>.
#
- # Options:
+ # ==== Options
#
# * <tt>:words_connector</tt> - The sign or word used to join the elements
# in arrays with two or more elements (default: ", ").
@@ -24,6 +24,8 @@ class Array
# the connector options defined on the 'support.array' namespace in the
# corresponding dictionary file.
#
+ # ==== Examples
+ #
# [].to_sentence # => ""
# ['one'].to_sentence # => "one"
# ['one', 'two'].to_sentence # => "one and two"
@@ -38,10 +40,10 @@ class Array
# ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
# # => "one or two or at least three"
#
- # Examples using <tt>:locale</tt> option:
+ # Using <tt>:locale</tt> option:
#
# # Given this locale dictionary:
- # #
+ # #
# # es:
# # support:
# # array:
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 640e6e9328..dbddc7a7b4 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -25,7 +25,7 @@ class Array
# subtracting from number gives how many to add;
# modulo number ensures we don't add group of just fill.
padding = (number - size % number) % number
- collection = dup.concat([fill_with] * padding)
+ collection = dup.concat(Array.new(padding, fill_with))
end
if block_given?
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 ca3b7748cd..23573c97de 100644
--- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb
+++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb
@@ -1,5 +1,5 @@
class Array
- # *DEPRECATED*: Use +Array#uniq+ instead.
+ # *DEPRECATED*: Use <tt>Array#uniq</tt> instead.
#
# Returns a unique array based on the criteria in the block.
#
@@ -9,7 +9,7 @@ class Array
uniq(&block)
end
- # *DEPRECATED*: Use +Array#uniq!+ instead.
+ # *DEPRECATED*: Use <tt>Array#uniq!</tt> instead.
#
# Same as +uniq_by+, but modifies +self+.
def uniq_by!(&block)
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index 1245768870..152eb02218 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -15,12 +15,12 @@ class Array
#
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
- # such a +nil+ right away.
+ # +nil+ right away.
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
- # * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
+ # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+.
#
- # The last point is particularly worth comparing for some enumerables:
+ # The second point is easily explained with some enumerables:
#
# Array(foo: :bar) # => [[:foo, :bar]]
# Array.wrap(foo: :bar) # => [{:foo=>:bar}]
@@ -29,10 +29,10 @@ class Array
#
# [*object]
#
- # which for +nil+ returns <tt>[]</tt>, and calls to <tt>Array(object)</tt> otherwise.
+ # which returns <tt>[]</tt> for +nil+, but calls to <tt>Array(object)</tt> otherwise.
#
- # Thus, in this case the behavior may be different for +nil+, and the differences with
- # <tt>Kernel#Array</tt> explained above apply to the rest of <tt>object</tt>s.
+ # The differences with <tt>Kernel#Array</tt> explained above
+ # apply to the rest of <tt>object</tt>s.
def self.wrap(object)
if object.nil?
[]
diff --git a/activesupport/lib/active_support/core_ext/benchmark.rb b/activesupport/lib/active_support/core_ext/benchmark.rb
index 2d110155a5..eb25b2bc44 100644
--- a/activesupport/lib/active_support/core_ext/benchmark.rb
+++ b/activesupport/lib/active_support/core_ext/benchmark.rb
@@ -1,6 +1,13 @@
require 'benchmark'
class << Benchmark
+ # Benchmark realtime in milliseconds.
+ #
+ # Benchmark.realtime { User.all }
+ # # => 8.0e-05
+ #
+ # Benchmark.ms { User.all }
+ # # => 0.074
def ms
1000 * realtime { yield }
end
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 5dc5710c53..39b8cea807 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,4 +1,5 @@
require 'bigdecimal'
+require 'bigdecimal/util'
require 'yaml'
class BigDecimal
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index e51ab9ddbc..6fa9967a28 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -44,7 +44,8 @@ class Class
# Base.setting # => []
# Subclass.setting # => [:foo]
#
- # For convenience, a query method is defined as well:
+ # For convenience, an instance predicate method is defined as well.
+ # To skip it, pass <tt>instance_predicate: false</tt>.
#
# Subclass.setting? # => false
#
@@ -70,12 +71,13 @@ class Class
def class_attribute(*attrs)
options = attrs.extract_options!
# double assignment is used to avoid "assigned but unused variable" warning
- instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
+ instance_predicate = options.fetch(:instance_predicate, true)
attrs.each do |name|
define_singleton_method(name) { nil }
- define_singleton_method("#{name}?") { !!public_send(name) }
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
ivar = "@#{name}"
@@ -109,7 +111,7 @@ class Class
self.class.public_send name
end
end
- define_method("#{name}?") { !!public_send(name) }
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
end
attr_writer name if instance_writer
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 fa1dbfdf06..34859617c9 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -32,7 +32,7 @@ class Class
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 class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
unless defined? @@#{sym}
@@#{sym} = nil
@@ -93,7 +93,7 @@ class Class
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 class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
unless defined? @@#{sym}
@@#{sym} = nil
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 5f13f5f70f..465fedda80 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -2,5 +2,4 @@ require 'active_support/core_ext/date/acts_like'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
-require 'active_support/core_ext/date/infinite_comparable'
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 106a65610c..06e4847e82 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -119,4 +119,15 @@ class Date
options.fetch(:day, day)
)
end
+
+ # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.
+ def compare_with_coercion(other)
+ if other.is_a?(Time)
+ self.to_datetime <=> other
+ else
+ compare_without_coercion(other)
+ end
+ end
+ alias_method :compare_without_coercion, :<=>
+ alias_method :<=>, :compare_with_coercion
end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index cdf606f28c..0637fe4929 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,7 +1,6 @@
require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
-require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
diff --git a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb
deleted file mode 100644
index ca5d793942..0000000000
--- a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'active_support/core_ext/infinite_comparable'
-
-class Date
- include InfiniteComparable
-end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 1f78b9eb5a..0d14cba7cc 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -93,7 +93,7 @@ module DateAndTime
# Returns a new date/time at the end of the quarter.
# Example: 31st March, 30th June, 30th September.
- # DateTIme objects will have a time set to 23:59:59.
+ # DateTime objects will have a time set to 23:59:59.
def end_of_quarter
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
beginning_of_month.change(:month => last_quarter_month).end_of_month
@@ -109,11 +109,11 @@ module DateAndTime
alias :at_beginning_of_year :beginning_of_year
# Returns a new date/time representing the given day in the next week.
- # Week is assumed to start on +start_day+, default is
- # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
- # DateTime objects have their time set to 0:00.
- def next_week(start_day = Date.beginning_of_week)
- first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(start_day)) }
+ # The +given_day_in_next_week+ defaults to the beginning of the week
+ # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
+ # when set. +DateTime+ objects have their time set to 0:00.
+ def next_week(given_day_in_next_week = Date.beginning_of_week)
+ first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)) }
end
# Short-hand for months_since(1).
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index 024af91738..e8a27b9f38 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -2,4 +2,3 @@ require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date_time/zones'
-require 'active_support/core_ext/date_time/infinite_comparable'
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 9f0864d9bb..937567440b 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -1,3 +1,4 @@
+require 'date'
require 'active_support/deprecation'
class DateTime
@@ -154,4 +155,11 @@ class DateTime
def utc_offset
(offset * 86400).to_i
end
+
+ # Layers additional behavior on DateTime#<=> so that Time and
+ # ActiveSupport::TimeWithZone instances can be compared with a 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 b7d8414a9d..c44626aed9 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -1,3 +1,4 @@
+require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/date_time/calculations'
@@ -79,6 +80,16 @@ class DateTime
seconds_since_unix_epoch.to_i
end
+ # Returns the fraction of a second as microseconds
+ def usec
+ (sec_fraction * 1_000_000).to_i
+ end
+
+ # Returns the fraction of a second as nanoseconds
+ def nsec
+ (sec_fraction * 1_000_000_000).to_i
+ end
+
private
def offset_in_seconds
diff --git a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb
deleted file mode 100644
index 8a282b19f2..0000000000
--- a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'active_support/core_ext/infinite_comparable'
-
-class DateTime
- include InfiniteComparable
-end
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 6457ffbaf6..01a627f8af 100644
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb
@@ -1,3 +1,4 @@
+require 'date'
require 'active_support/core_ext/time/zones'
class DateTime
diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb
index 5f3868b5b0..4359213380 100644
--- a/activesupport/lib/active_support/core_ext/hash/diff.rb
+++ b/activesupport/lib/active_support/core_ext/hash/diff.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation'
+
class Hash
# Returns a hash that represents the difference between two hashes.
#
diff --git a/activesupport/lib/active_support/core_ext/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/infinite_comparable.rb
deleted file mode 100644
index b78b2deaad..0000000000
--- a/activesupport/lib/active_support/core_ext/infinite_comparable.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'active_support/concern'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/core_ext/object/try'
-
-module InfiniteComparable
- extend ActiveSupport::Concern
-
- included do
- alias_method_chain :<=>, :infinity
- end
-
- define_method :'<=>_with_infinity' do |other|
- if other.class == self.class
- public_send :'<=>_without_infinity', other
- else
- infinite = try(:infinite?)
- other_infinite = other.try(:infinite?)
-
- # inf <=> inf
- if infinite && other_infinite
- infinite <=> other_infinite
- # not_inf <=> inf
- elsif other_infinite
- -other_infinite
- # inf <=> not_inf
- elsif infinite
- infinite
- else
- conversion = "to_#{self.class.name.downcase}"
- other = other.public_send(conversion) if other.respond_to?(conversion)
- public_send :'<=>_without_infinity', other
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index c7a8348b1d..56c79c04bd 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/aliasing'
+
module Marshal
class << self
def load_with_autoloading(source)
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index e608eeaf42..3dde87ac2e 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,8 +1,10 @@
class Module
- # 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.
+ # Provides a +delegate+ class method to easily expose contained objects'
+ # public methods as your own.
+ #
+ # The macro receives one or more method names (specified as symbols or
+ # strings) and the name of the target object via the <tt>:to</tt> option
+ # (also a symbol or string).
#
# Delegation is particularly useful with Active Record associations:
#
@@ -89,29 +91,44 @@ class Module
# 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
- # +nil+ instead with the +:allow_nil+ option.
+ # If the target is +nil+ and does not respond to the delegated method a
+ # +NoMethodError+ is raised, as with any other value. Sometimes, however, it
+ # makes sense to be robust to that situation and that is the purpose of the
+ # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
+ # responds to the method, everything works as usual. But if it is +nil+ and
+ # does not respond to the delegated method, +nil+ is returned.
#
- # class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
- # @bar = bar
- # end
- # delegate :zoo, to: :bar
+ # class User < ActiveRecord::Base
+ # has_one :profile
+ # delegate :age, to: :profile
# end
#
- # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
+ # User.new.age # raises NoMethodError: undefined method `age'
+ #
+ # But if not having a profile yet is fine and should not be an error
+ # condition:
+ #
+ # class User < ActiveRecord::Base
+ # has_one :profile
+ # delegate :age, to: :profile, allow_nil: true
+ # end
+ #
+ # User.new.age # nil
+ #
+ # Note that if the target is not +nil+ then the call is attempted regardless of the
+ # <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
+ # does not respond to the method:
#
# class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
+ # def initialize(bar)
# @bar = bar
# end
- # delegate :zoo, to: :bar, allow_nil: true
+ #
+ # delegate :name, to: :@bar, allow_nil: true
# end
#
- # Foo.new.zoo # returns nil
+ # Foo.new("Bar").name # raises NoMethodError: undefined method `name'
+ #
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
@@ -142,22 +159,31 @@ class Module
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
+ # The following generated methods call the target exactly once, storing
+ # the returned value in a dummy variable.
+ #
+ # Reason is twofold: On one hand doing less calls is in general better.
+ # On the other hand it could be that the target has side-effects,
+ # whereas conceptually, from the user point of view, the delegator should
+ # be doing one call.
if allow_nil
- module_eval(<<-EOS, file, line - 2)
+ module_eval(<<-EOS, file, line - 3)
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
- if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
- #{to}.#{method}(#{definition}) # client.name(*args, &block)
+ _ = #{to} # _ = client
+ if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name)
+ _.#{method}(#{definition}) # _.name(*args, &block)
end # end
end # end
EOS
else
exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- module_eval(<<-EOS, file, line - 1)
+ module_eval(<<-EOS, file, line - 2)
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
- #{to}.#{method}(#{definition}) # client.name(*args, &block)
+ _ = #{to} # _ = client
+ _.#{method}(#{definition}) # _.name(*args, &block)
rescue NoMethodError # rescue NoMethodError
- if #{to}.nil? # if client.nil?
+ if _.nil? # if _.nil?
#{exception} # # add helpful message to the exception
else # else
raise # raise
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index cc45cee5b8..d873de197f 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -14,8 +14,8 @@ class Module
# method where you can implement your custom warning behavior.
#
# class MyLib::Deprecator
- # def deprecation_warning(deprecated_method_name, message, caller_backtrace)
- # message = "#{method_name} is deprecated and will be removed from MyLibrary | #{message}"
+ # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
# Kernel.warn message
# end
# end
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index d5cfc2ece4..a6bc0624be 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,4 +1,3 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/numeric/conversions'
-require 'active_support/core_ext/numeric/infinite_comparable'
diff --git a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb
deleted file mode 100644
index b5f1b0487b..0000000000
--- a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'active_support/core_ext/infinite_comparable'
-
-class Float
- include InfiniteComparable
-end
-
-class BigDecimal
- include InfiniteComparable
-end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index 3fec465ec0..b5671f66d0 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -1,25 +1,15 @@
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:
+ # Returns true if this object is included in the argument. Argument must be
+ # any object which responds to +#include?+. Usage:
#
- # characters = ['Konata', 'Kagami', 'Tsukasa']
- # 'Konata'.in?(characters) # => true
+ # 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
+ # This will throw an ArgumentError if the argument doesn't respond
# to +#include?+.
- def in?(*args)
- if args.length > 1
- args.include? self
- else
- another_object = args.first
- if another_object.respond_to? :include?
- another_object.include? self
- else
- raise ArgumentError.new 'The single parameter passed to #in? must respond to #include?'
- end
- end
+ def in?(another_object)
+ another_object.include?(self)
+ rescue NoMethodError
+ raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 0d5f3501e5..3b137ce6ae 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -53,6 +53,6 @@ class Hash
def to_param(namespace = nil)
collect do |key, value|
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
- end.sort * '&'
+ end.sort! * '&'
end
end
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 3af66aaf2f..3a07401c8a 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/aliasing'
+
class Range
# Extends the default Range#include? to support range comparisons.
# (1..5).include?(1..5) # => true
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 5d7cb81e38..c656db2c6c 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -4,7 +4,6 @@ require 'active_support/core_ext/string/multibyte'
require 'active_support/core_ext/string/starts_ends_with'
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/output_safety'
require 'active_support/core_ext/string/exclude'
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 428fa1f826..6691fc0995 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -15,24 +15,23 @@ class String
# "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC
+ # "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
return if parts.empty?
now = Time.now
- offset = parts[:offset]
- utc_offset = form == :utc ? 0 : now.utc_offset
- adjustment = offset ? offset - utc_offset : 0
-
- Time.send(
- form,
+ time = Time.new(
parts.fetch(:year, now.year),
parts.fetch(:mon, now.month),
parts.fetch(:mday, now.day),
parts.fetch(:hour, 0),
parts.fetch(:min, 0),
- parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0)
- ) - adjustment
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, form == :utc ? 0 : nil)
+ )
+
+ form == :utc ? time.utc : time.getlocal
end
# Converts a string to a Date value.
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index a1b3f79748..c62bb41416 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -50,6 +50,6 @@ class String
length_with_room_for_omission
end
- self[0...stop] + options[:omission]
+ "#{self[0...stop]}#{options[:omission]}"
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/indent.rb b/activesupport/lib/active_support/core_ext/string/indent.rb
index afc3032272..ce3a69cf5f 100644
--- a/activesupport/lib/active_support/core_ext/string/indent.rb
+++ b/activesupport/lib/active_support/core_ext/string/indent.rb
@@ -29,7 +29,7 @@ class String
# "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
# "foo".indent(2, "\t") # => "\t\tfoo"
#
- # While +indent_string+ is tipically one space or tab, it may be any string.
+ # While +indent_string+ is typically one space or tab, it may be any string.
#
# The third argument, +indent_empty_lines+, is a flag that says whether
# empty lines should be indented. Default is false.
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 6522145572..56e8a5f98d 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -41,7 +41,7 @@ class String
#
# If the optional parameter +locale+ is specified,
# the word will be singularized as a word of that language.
- # By default, this paramter is set to <tt>:en</tt>.
+ # By default, this parameter is set to <tt>:en</tt>.
# You must define your own inflection rules for languages other than English.
#
# 'posts'.singularize # => "post"
@@ -185,7 +185,7 @@ class String
#
# Singular names are not handled correctly.
#
- # 'business'.classify # => "Busines"
+ # 'business'.classify # => "Business"
def classify
ActiveSupport::Inflector.classify(self)
end
diff --git a/activesupport/lib/active_support/core_ext/string/xchar.rb b/activesupport/lib/active_support/core_ext/string/xchar.rb
deleted file mode 100644
index f9a5b4fb64..0000000000
--- a/activesupport/lib/active_support/core_ext/string/xchar.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-begin
- # See http://fast-xs.rubyforge.org/ by Eric Wong.
- # Also included with hpricot.
- require 'fast_xs'
-rescue LoadError
- # fast_xs extension unavailable
-else
- begin
- require 'builder'
- rescue LoadError
- # builder demands the first shot at defining String#to_xs
- end
-
- class String
- alias_method :original_xs, :to_xs if method_defined?(:to_xs)
- alias_method :to_xs, :fast_xs
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index af6b589b71..32cffe237d 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -3,4 +3,3 @@ require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/marshal'
require 'active_support/core_ext/time/zones'
-require 'active_support/core_ext/time/infinite_comparable'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index a3ce7dbe3f..c65f20c2d5 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -65,6 +65,18 @@ class Time
def current
::Time.zone ? ::Time.zone.now : ::Time.now
end
+
+ # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime
+ # instances can be used when called with a single argument
+ def at_with_coercion(*args)
+ if args.size == 1 && args.first.acts_like?(:time)
+ at_without_coercion(args.first.to_i)
+ else
+ at_without_coercion(*args)
+ end
+ end
+ alias_method :at_without_coercion, :at
+ alias_method :at, :at_with_coercion
end
# Seconds since midnight: Time.now.seconds_since_midnight
diff --git a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb
deleted file mode 100644
index 63795885f5..0000000000
--- a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'active_support/core_ext/infinite_comparable'
-
-class Time
- include InfiniteComparable
-end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index fff4c776a9..d38e4b0732 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -213,7 +213,7 @@ module ActiveSupport #:nodoc:
yield
end
rescue Exception => exception # errors from loading file
- exception.blame_file! file
+ exception.blame_file! file if exception.respond_to? :blame_file!
raise
end
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 485dc91063..a03a66b96b 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -25,7 +25,7 @@ module ActiveSupport
end
end
- # This DeprecatedObjectProxy transforms object to depracated object.
+ # This DeprecatedObjectProxy transforms object to deprecated object.
#
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
@@ -52,7 +52,7 @@ module ActiveSupport
end
# This DeprecatedInstanceVariableProxy transforms instance variable to
- # depracated instance variable.
+ # deprecated instance variable.
#
# class Example
# def initialize(deprecator)
@@ -93,7 +93,7 @@ module ActiveSupport
end
end
- # This DeprecatedConstantProxy transforms constant to depracated constant.
+ # This DeprecatedConstantProxy transforms constant to deprecated constant.
#
# OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST')
# OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 20136dd1b0..d6918bede2 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -115,7 +115,7 @@ module ActiveSupport
end
def compile_glob(hash)
- hash.freeze # Freeze so changes aren't accidently pushed
+ hash.freeze # Freeze so changes aren't accidentally pushed
return if hash.empty?
globs = hash.map do |key, value|
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 306d80b2df..0a81a8393d 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -78,7 +78,7 @@ module ActiveSupport
end
def self.[](*args)
- new.merge(Hash[*args])
+ new.merge!(Hash[*args])
end
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
@@ -91,7 +91,7 @@ module ActiveSupport
#
# This value can be later fetched using either +:key+ or +'key'+.
def []=(key, value)
- regular_writer(convert_key(key), convert_value(value))
+ regular_writer(convert_key(key), convert_value(value, for: :assignment))
end
alias_method :store, :[]=
@@ -223,13 +223,17 @@ module ActiveSupport
def deep_stringify_keys; dup end
undef :symbolize_keys!
undef :deep_symbolize_keys!
- def symbolize_keys; to_hash.symbolize_keys end
+ def symbolize_keys; to_hash.symbolize_keys! end
def deep_symbolize_keys; to_hash.deep_symbolize_keys end
def to_options!; self end
# Convert to a regular hash with string keys.
def to_hash
- Hash.new(default).merge!(self)
+ _new_hash= {}
+ each do |key, value|
+ _new_hash[convert_key(key)] = convert_value(value, for: :to_hash)
+ end
+ Hash.new(default).merge!(_new_hash)
end
protected
@@ -237,12 +241,18 @@ module ActiveSupport
key.kind_of?(Symbol) ? key.to_s : key
end
- def convert_value(value)
+ def convert_value(value, options = {})
if value.is_a? Hash
- value.nested_under_indifferent_access
+ if options[:for] == :to_hash
+ value.to_hash
+ else
+ value.nested_under_indifferent_access
+ end
elsif value.is_a?(Array)
- value = value.dup if value.frozen?
- value.map! { |e| convert_value(e) }
+ unless options[:for] == :assignment
+ value = value.dup
+ end
+ value.map! { |e| convert_value(e, options) }
else
value
end
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index 22521a8e93..6cc98191d4 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -1,13 +1,13 @@
+require 'active_support/core_ext/hash/deep_merge'
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
begin
- require 'active_support/core_ext/hash/deep_merge'
- require 'active_support/core_ext/hash/except'
- require 'active_support/core_ext/hash/slice'
require 'i18n'
- require 'active_support/lazy_load_hooks'
rescue LoadError => e
$stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
raise e
end
+require 'active_support/lazy_load_hooks'
ActiveSupport.run_load_hooks(:i18n)
I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index a4a32b2ad0..30833a4cb1 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -1,6 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/delegation'
-require 'multi_json'
+require 'json'
module ActiveSupport
# Look for and parse json strings that look like ISO 8601 times.
@@ -13,8 +13,8 @@ module ActiveSupport
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
- def decode(json, options ={})
- data = MultiJson.load(json, options)
+ def decode(json, proc = nil, options = {})
+ data = ::JSON.load(json, proc, options)
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
@@ -22,23 +22,6 @@ module ActiveSupport
end
end
- def engine
- MultiJson.adapter
- end
- alias :backend :engine
-
- def engine=(name)
- MultiJson.use(name)
- end
- alias :backend= :engine=
-
- def with_backend(name)
- old_backend, self.backend = backend, name
- yield
- ensure
- self.backend = old_backend
- end
-
# Returns the class of the error that will be raised when there is an
# error in decoding JSON. Using this method means you won't directly
# depend on the ActiveSupport's JSON implementation, in case it changes
@@ -50,7 +33,7 @@ module ActiveSupport
# Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}")
# end
def parse_error
- MultiJson::DecodeError
+ ::JSON::ParserError
end
private
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 9bf1ea35b3..77b5d8d227 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,6 +1,7 @@
+#encoding: us-ascii
+
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
-require 'active_support/json/variable'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
@@ -98,13 +99,18 @@ module ActiveSupport
"\010" => '\b',
"\f" => '\f',
"\n" => '\n',
+ "\xe2\x80\xa8" => '\u2028',
+ "\xe2\x80\xa9" => '\u2029',
"\r" => '\r',
"\t" => '\t',
'"' => '\"',
'\\' => '\\\\',
'>' => '\u003E',
'<' => '\u003C',
- '&' => '\u0026' }
+ '&' => '\u0026',
+ "#{0xe2.chr}#{0x80.chr}#{0xa8.chr}" => '\u2028',
+ "#{0xe2.chr}#{0x80.chr}#{0xa9.chr}" => '\u2029',
+ }
class << self
# If true, use ISO 8601 format for dates and times. Otherwise, fall back
@@ -121,9 +127,9 @@ module ActiveSupport
def escape_html_entities_in_json=(value)
self.escape_regex = \
if @escape_html_entities_in_json = value
- /[\x00-\x1F"\\><&]/
+ /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\><&]/
else
- /[\x00-\x1F"\\]/
+ /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\]/
end
end
diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb
deleted file mode 100644
index d69dab6408..0000000000
--- a/activesupport/lib/active_support/json/variable.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'active_support/deprecation'
-
-module ActiveSupport
- module JSON
- # Deprecated: A string that returns itself as its JSON-encoded form.
- class Variable < String
- def initialize(*args)
- message = 'ActiveSupport::JSON::Variable is deprecated and will be removed in Rails 4.1. ' \
- 'For your own custom JSON literals, define #as_json and #encode_json yourself.'
- ActiveSupport::Deprecation.warn message
- super
- end
-
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) self end #:nodoc:
- end
- end
-end
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 71654dbb87..598c46bce5 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -4,7 +4,7 @@ require 'openssl'
module ActiveSupport
# KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2
# It can be used to derive a number of keys for various purposes from a given secret.
- # This lets rails applications have a single secure secret, but avoid reusing that
+ # This lets Rails applications have a single secure secret, but avoid reusing that
# key in multiple incompatible contexts.
class KeyGenerator
def initialize(secret, options = {})
@@ -39,7 +39,7 @@ module ActiveSupport
end
end
- class DummyKeyGenerator # :nodoc:
+ class LegacyKeyGenerator # :nodoc:
SECRET_MIN_LENGTH = 30 # Characters
def initialize(secret)
diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb
index e489512531..e2b8f0f648 100644
--- a/activesupport/lib/active_support/lazy_load_hooks.rb
+++ b/activesupport/lib/active_support/lazy_load_hooks.rb
@@ -1,5 +1,5 @@
module ActiveSupport
- # lazy_load_hooks allows rails to lazily load a lot of components and thus
+ # lazy_load_hooks allows Rails to lazily load a lot of components and thus
# making the app boot faster. Because of this feature now there is no need to
# require <tt>ActiveRecord::Base</tt> at boot time purely to apply
# configuration. Instead a hook is registered that applies configuration once
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 21a04a9152..e95dc5a866 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/class/attribute'
+require 'active_support/subscriber'
module ActiveSupport
# ActiveSupport::LogSubscriber is an object set to consume
@@ -33,7 +34,7 @@ module ActiveSupport
# Log subscriber also has some helpers to deal with logging and automatically
# flushes all logs when the request finishes (via action_dispatch.callback
# notification) in a Rails environment.
- class LogSubscriber
+ class LogSubscriber < Subscriber
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
BOLD = "\e[1m"
@@ -53,26 +54,15 @@ module ActiveSupport
class << self
def logger
- if defined?(Rails) && Rails.respond_to?(:logger)
- @logger ||= Rails.logger
+ @logger ||= if defined?(Rails) && Rails.respond_to?(:logger)
+ Rails.logger
end
- @logger
end
attr_writer :logger
- def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
- log_subscribers << log_subscriber
-
- log_subscriber.public_methods(false).each do |event|
- next if %w{ start finish }.include?(event.to_s)
-
- notifier.subscribe("#{event}.#{namespace}", log_subscriber)
- end
- end
-
def log_subscribers
- @@log_subscribers ||= []
+ subscribers
end
# Flush all log_subscribers' logger.
@@ -81,39 +71,18 @@ module ActiveSupport
end
end
- def initialize
- @queue_key = [self.class.name, object_id].join "-"
- super
- end
-
def logger
LogSubscriber.logger
end
def start(name, id, payload)
- return unless logger
-
- e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
- parent = event_stack.last
- parent << e if parent
-
- event_stack.push e
+ super if logger
end
def finish(name, id, payload)
- return unless logger
-
- finished = Time.now
- event = event_stack.pop
- event.end = finished
- event.payload.merge!(payload)
-
- method = name.split('.').first
- begin
- send(method, event)
- rescue Exception => e
- logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
- end
+ super if logger
+ rescue Exception => e
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
protected
@@ -136,11 +105,5 @@ module ActiveSupport
bold = bold ? BOLD : ""
"#{bold}#{color}#{text}#{CLEAR}"
end
-
- private
-
- def event_stack
- Thread.current[@queue_key] ||= []
- end
end
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index ce40a7d689..bffdfc6201 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -12,10 +12,11 @@ module ActiveSupport
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
# where you don't want users to be able to determine the value of the payload.
#
- # key = OpenSSL::Digest::SHA256.new('password').digest # => "\x89\xE0\x156\xAC..."
- # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
- # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
- # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
+ # salt = SecureRandom.random_bytes(64)
+ # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
class MessageEncryptor
module NullSerializer #:nodoc:
def self.load(value)
@@ -28,7 +29,7 @@ module ActiveSupport
end
class InvalidMessage < StandardError; end
- OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
+ OpenSSLCipherError = OpenSSL::Cipher::CipherError
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
# the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
@@ -66,12 +67,11 @@ module ActiveSupport
def _encrypt(value)
cipher = new_cipher
- # Rely on OpenSSL for the initialization vector
- iv = cipher.random_iv
-
cipher.encrypt
cipher.key = @secret
- cipher.iv = iv
+
+ # Rely on OpenSSL for the initialization vector
+ iv = cipher.random_iv
encrypted_data = cipher.update(@serializer.dump(value))
encrypted_data << cipher.final
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index cbc1608349..04e6b71580 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -145,7 +145,7 @@ module ActiveSupport
ncp << (HANGUL_TBASE + tindex) unless tindex == 0
decomposed.concat ncp
# if the codepoint is decomposable in with the current decomposition type
- elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatability)
+ elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatibility)
decomposed.concat decompose(type, ncp.dup)
else
decomposed << cp
@@ -218,51 +218,31 @@ module ActiveSupport
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
# encoding is entirely CP1252 or ISO-8859-1.
def tidy_bytes(string, force = false)
+ return string if string.empty?
+
if force
- return string.unpack("C*").map do |b|
- tidy_byte(b)
- end.flatten.compact.pack("C*").unpack("U*").pack("U*")
+ return string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
end
- bytes = string.unpack("C*")
- conts_expected = 0
- last_lead = 0
-
- bytes.each_index do |i|
+ # We can't transcode to the same format, so we choose a nearly-identical encoding.
+ # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
+ # CP1252 when we get errors. The final string will be 'converted' back to UTF-8
+ # before returning.
+ reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
- byte = bytes[i]
- is_cont = byte > 127 && byte < 192
- is_lead = byte > 191 && byte < 245
- is_unused = byte > 240
- is_restricted = byte > 244
+ source = string.dup
+ out = ''.force_encoding(Encoding::UTF_8_MAC)
- # Impossible or highly unlikely byte? Clean it.
- if is_unused || is_restricted
- bytes[i] = tidy_byte(byte)
- elsif is_cont
- # Not expecting continuation byte? Clean up. Otherwise, now expect one less.
- conts_expected == 0 ? bytes[i] = tidy_byte(byte) : conts_expected -= 1
- else
- if conts_expected > 0
- # Expected continuation, but got ASCII or leading? Clean backwards up to
- # the leading byte.
- (1..(i - last_lead)).each {|j| bytes[i - j] = tidy_byte(bytes[i - j])}
- conts_expected = 0
- end
- if is_lead
- # Final byte is leading? Clean it.
- if i == bytes.length - 1
- bytes[i] = tidy_byte(bytes.last)
- else
- # Valid leading byte? Expect continuations determined by position of
- # first zero bit, with max of 3.
- conts_expected = byte < 224 ? 1 : byte < 240 ? 2 : 3
- last_lead = i
- end
- end
- end
+ loop do
+ reader.primitive_convert(source, out)
+ _, _, _, error_bytes, _ = reader.primitive_errinfo
+ break if error_bytes.nil?
+ out << error_bytes.encode(Encoding::UTF_8_MAC, Encoding::Windows_1252, invalid: :replace, undef: :replace)
end
- bytes.empty? ? "" : bytes.flatten.compact.pack("C*").unpack("U*").pack("U*")
+
+ reader.finish
+
+ out.encode!(Encoding::UTF_8)
end
# Returns the KC normalization of the string by default. NFKC is
@@ -283,9 +263,9 @@ module ActiveSupport
when :c
compose(reorder_characters(decompose(:canonical, codepoints)))
when :kd
- reorder_characters(decompose(:compatability, codepoints))
+ reorder_characters(decompose(:compatibility, codepoints))
when :kc
- compose(reorder_characters(decompose(:compatability, codepoints)))
+ compose(reorder_characters(decompose(:compatibility, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
end.pack('U*')
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 705a4693b7..c45358bba9 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,5 +1,6 @@
require 'active_support/notifications/instrumenter'
require 'active_support/notifications/fanout'
+require 'active_support/per_thread_registry'
module ActiveSupport
# = Notifications
@@ -177,7 +178,27 @@ module ActiveSupport
end
def instrumenter
- Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
+ InstrumentationRegistry.instrumenter_for(notifier)
+ end
+ end
+
+ # This class is a registry which holds all of the +Instrumenter+ objects
+ # in a particular thread local. To access the +Instrumenter+ object for a
+ # particular +notifier+, you can call the following method:
+ #
+ # InstrumentationRegistry.instrumenter_for(notifier)
+ #
+ # The instrumenters for multiple notifiers are held in a single instance of
+ # this class.
+ class InstrumentationRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ def initialize
+ @registry = {}
+ end
+
+ def instrumenter_for(notifier)
+ @registry[notifier] ||= Instrumenter.new(notifier)
end
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 7588fdb67c..99fe03e6d0 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -79,6 +79,13 @@ module ActiveSupport
def initialize(pattern, delegate)
@pattern = pattern
@delegate = delegate
+ @can_publish = delegate.respond_to?(:publish)
+ end
+
+ def publish(name, *args)
+ if @can_publish
+ @delegate.publish name, *args
+ end
end
def start(name, id, payload)
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index e0ae60e61f..0c9a729ce5 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -2,7 +2,7 @@ require 'securerandom'
module ActiveSupport
module Notifications
- # Instrumentors are stored in a thread local.
+ # Instrumenters are stored in a thread local.
class Instrumenter
attr_reader :id
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index cc935e6cb9..414960d2b1 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -295,7 +295,7 @@ module ActiveSupport
options = format_options(options[:locale]).merge!(options)
- parts = number.to_s.to_str.split('.')
+ parts = number.to_s.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
parts.join(options[:separator])
end
@@ -356,7 +356,8 @@ module ActiveSupport
digits, rounded_number = 1, 0
else
digits = (Math.log10(number.abs) + 1).floor
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
+ multiplier = 10 ** (digits - precision)
+ rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier
digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
end
precision -= digits
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index c9518bda79..e03bb4ca0f 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -40,6 +40,14 @@ module ActiveSupport
end
end
+ # +InheritableOptions+ provides a constructor to build an +OrderedOptions+
+ # hash inherited from the another hash.
+ #
+ # Use this if you already have some hash and you want to create a new one based on it.
+ #
+ # h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' })
+ # h.girl # => 'Mary'
+ # h.boy # => 'John'
class InheritableOptions < OrderedOptions
def initialize(parent = nil)
if parent.kind_of?(OrderedOptions)
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
new file mode 100644
index 0000000000..aa682fb36c
--- /dev/null
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -0,0 +1,52 @@
+module ActiveSupport
+ # This module is used to encapsulate access to thread local variables.
+ #
+ # Instead of polluting the thread locals namespace:
+ #
+ # Thread.current[:connection_handler]
+ #
+ # you define a class that extends this module:
+ #
+ # module ActiveRecord
+ # class RuntimeRegistry
+ # extend ActiveSupport::PerThreadRegistry
+ #
+ # attr_accessor :connection_handler
+ # end
+ # end
+ #
+ # and invoke the declared instance accessors as class methods. So
+ #
+ # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler
+ #
+ # sets a connection handler local to the current thread, and
+ #
+ # ActiveRecord::RuntimeRegistry.connection_handler
+ #
+ # returns a connection handler local to the current thread.
+ #
+ # This feature is accomplished by instantiating the class and storing the
+ # instance as a thread local keyed by the class name. In the example above
+ # a key "ActiveRecord::RuntimeRegistry" is stored in <tt>Thread.current</tt>.
+ # The class methods proxy to said thread local instance.
+ #
+ # If the class has an initializer, it must accept no arguments.
+ module PerThreadRegistry
+ protected
+
+ def method_missing(name, *args, &block) # :nodoc:
+ # Caches the method definition as a singleton method of the receiver.
+ define_singleton_method(name) do |*a, &b|
+ per_thread_registry_instance.public_send(name, *a, &b)
+ end
+
+ send(name, *args, &block)
+ end
+
+ private
+
+ def per_thread_registry_instance
+ Thread.current[name] ||= new
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
new file mode 100644
index 0000000000..34c6f900c1
--- /dev/null
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -0,0 +1,93 @@
+require 'active_support/per_thread_registry'
+
+module ActiveSupport
+ # ActiveSupport::Subscriber is an object set to consume
+ # ActiveSupport::Notifications. The subscriber dispatches notifications to
+ # a registered object based on its given namespace.
+ #
+ # An example would be Active Record subscriber responsible for collecting
+ # statistics about queries:
+ #
+ # module ActiveRecord
+ # class StatsSubscriber < ActiveSupport::Subscriber
+ # def sql(event)
+ # Statsd.timing("sql.#{event.payload[:name]}", event.duration)
+ # end
+ # end
+ # end
+ #
+ # And it's finally registered as:
+ #
+ # ActiveRecord::StatsSubscriber.attach_to :active_record
+ #
+ # Since we need to know all instance methods before attaching the log
+ # subscriber, the line above should be called after your subscriber definition.
+ #
+ # After configured, whenever a "sql.active_record" notification is published,
+ # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
+ # the +sql+ method.
+ class Subscriber
+ class << self
+
+ # Attach the subscriber to a namespace.
+ def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
+ subscribers << subscriber
+
+ subscriber.public_methods(false).each do |event|
+ next if %w{ start finish }.include?(event.to_s)
+
+ notifier.subscribe("#{event}.#{namespace}", subscriber)
+ end
+ end
+
+ def subscribers
+ @@subscribers ||= []
+ end
+ end
+
+ def initialize
+ @queue_key = [self.class.name, object_id].join "-"
+ super
+ end
+
+ def start(name, id, payload)
+ e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
+ parent = event_stack.last
+ parent << e if parent
+
+ event_stack.push e
+ end
+
+ def finish(name, id, payload)
+ finished = Time.now
+ event = event_stack.pop
+ event.end = finished
+ event.payload.merge!(payload)
+
+ method = name.split('.').first
+ send(method, event)
+ end
+
+ private
+
+ def event_stack
+ SubscriberQueueRegistry.get_queue(@queue_key)
+ end
+ end
+
+ # This is a registry for all the event stacks kept for subscribers.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class SubscriberQueueRegistry # :nodoc:
+ extend PerThreadRegistry
+
+ def initialize
+ @registry = {}
+ end
+
+ def get_queue(queue_key)
+ @registry[queue_key] ||= []
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 8b392c36d0..2f27aff2bd 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,10 +1,9 @@
gem 'minitest' # make sure we get the gem, not stdlib
-require 'minitest/unit'
+require 'minitest'
require 'active_support/testing/tagged_logging'
require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
-require 'active_support/testing/pending'
require 'active_support/testing/declarative'
require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
@@ -16,10 +15,28 @@ begin
rescue LoadError
end
+module Minitest # :nodoc:
+ class << self
+ remove_method :__run
+ end
+
+ def self.__run reporter, options # :nodoc:
+ # FIXME: MT5's runnables is not ordered. This is needed because
+ # we have have tests have cross-class order-dependent bugs.
+ suites = Runnable.runnables.sort_by { |ts| ts.name.to_s }
+
+ parallel, serial = suites.partition { |s| s.test_order == :parallel }
+
+ ParallelEach.new(parallel).map { |suite| suite.run reporter, options } +
+ serial.map { |suite| suite.run reporter, options }
+ end
+end
+
module ActiveSupport
- class TestCase < ::MiniTest::Unit::TestCase
- Assertion = MiniTest::Assertion
- alias_method :method_name, :__name__
+ class TestCase < ::Minitest::Test
+ Assertion = Minitest::Assertion
+
+ alias_method :method_name, :name
$tags = {}
def self.for_tag(tag)
@@ -36,7 +53,6 @@ module ActiveSupport
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
- include ActiveSupport::Testing::Pending
extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index c446adc16d..5aa5f46310 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -1,5 +1,5 @@
gem 'minitest'
-require 'minitest/unit'
+require 'minitest'
-MiniTest::Unit.autorun
+Minitest.autorun
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index d70d971538..9c52ae7768 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -40,6 +40,10 @@ module ActiveSupport
def method_missing(name, *args)
@calls << [name, args]
end
+
+ def info_signal
+ Signal.list['INFO']
+ end
end
module Isolation
@@ -68,16 +72,12 @@ module ActiveSupport
end
end
- def run(runner)
- _run_class_setup
-
- serialized = run_in_isolation do |isolated_runner|
- super(isolated_runner)
+ def run
+ serialized = run_in_isolation do
+ super
end
- retval, proxy = Marshal.load(serialized)
- proxy.__replay__(runner)
- retval
+ Marshal.load(serialized)
end
module Forking
@@ -86,9 +86,8 @@ module ActiveSupport
pid = fork do
read.close
- proxy = ProxyTestResult.new
- retval = yield proxy
- write.puts [Marshal.dump([retval, proxy])].pack("m")
+ yield
+ write.puts [Marshal.dump(self.dup)].pack("m")
exit!
end
diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb
deleted file mode 100644
index b04bbbbaea..0000000000
--- a/activesupport/lib/active_support/testing/pending.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'active_support/deprecation'
-
-module ActiveSupport
- module Testing
- module Pending # :nodoc:
- unless defined?(Spec)
- def pending(description = "", &block)
- ActiveSupport::Deprecation.warn("#pending is deprecated and will be removed in Rails 4.1, please use #skip instead.")
- skip(description.blank? ? nil : description)
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb
index 9d43eb179f..f4cee64091 100644
--- a/activesupport/lib/active_support/testing/tagged_logging.rb
+++ b/activesupport/lib/active_support/testing/tagged_logging.rb
@@ -7,7 +7,7 @@ module ActiveSupport
def before_setup
if tagged_logger
- heading = "#{self.class}: #{__name__}"
+ heading = "#{self.class}: #{name}"
divider = '-' * heading.size
tagged_logger.info divider
tagged_logger.info heading
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 4a032b0ad0..95b9b8e5ae 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -292,7 +292,7 @@ module ActiveSupport
end
end
- %w(year mon month day mday wday yday hour min sec to_date).each do |method_name|
+ %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method_name} # def month
time.#{method_name} # time.month
@@ -300,10 +300,6 @@ module ActiveSupport
EOV
end
- def usec
- time.respond_to?(:usec) ? time.usec : 0
- end
-
def to_a
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 21a0620c22..3cf82a24b9 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -5,7 +5,7 @@ module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
# It allows us to do the following:
#
- # * Limit the set of zones provided by TZInfo to a meaningful subset of 142
+ # * Limit the set of zones provided by TZInfo to a meaningful subset of 146
# zones.
# * Retrieve and display zones with a friendlier name
# (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
@@ -177,6 +177,7 @@ module ActiveSupport
"Wellington" => "Pacific/Auckland",
"Nuku'alofa" => "Pacific/Tongatapu",
"Tokelau Is." => "Pacific/Fakaofo",
+ "Chatham Is." => "Pacific/Chatham",
"Samoa" => "Pacific/Apia"
}
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index ca23057189..8762330a6e 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,7 +1,7 @@
module ActiveSupport
# Returns the version of the currently loaded ActiveSupport as a Gem::Version
def self.version
- Gem::Version.new "4.0.0.beta1"
+ Gem::Version.new "4.1.0.beta"
end
module VERSION #:nodoc:
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index acc018fd2d..70a95299ec 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -32,7 +32,7 @@ module ActiveSupport
end
def on_start_element(name, attrs = {})
- new_hash = { CONTENT_KEY => '' }.merge(attrs)
+ new_hash = { CONTENT_KEY => '' }.merge!(attrs)
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index 30b94aac47..be2d6a4cb1 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -38,7 +38,7 @@ module ActiveSupport
end
def start_element(name, attrs = [])
- new_hash = { CONTENT_KEY => '' }.merge(Hash[attrs])
+ new_hash = { CONTENT_KEY => '' }.merge!(Hash[attrs])
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index acd320dbe0..ae6eaa4b60 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -257,6 +257,26 @@ module CacheStoreBehavior
assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
end
+ def test_fetch_multi
+ @cache.write('foo', 'bar')
+ @cache.write('fud', 'biz')
+
+ values = @cache.fetch_multi('foo', 'fu', 'fud') {|value| value * 2 }
+
+ assert_equal(["bar", "fufu", "biz"], values)
+ assert_equal("fufu", @cache.read('fu'))
+ end
+
+ def test_multi_with_objects
+ foo = stub(:title => "FOO!", :cache_key => "foo")
+ bar = stub(:cache_key => "bar")
+
+ @cache.write('bar', "BAM!")
+
+ values = @cache.fetch_multi(foo, bar) {|object| object.title }
+ assert_equal(["FOO!", "BAM!"], values)
+ end
+
def test_read_and_write_compressed_small_data
@cache.write('foo', 'bar', :compress => true)
assert_equal 'bar', @cache.read('foo')
@@ -405,7 +425,7 @@ module CacheStoreBehavior
end
# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
-# The error is caused by charcter encodings that can't be compared with ASCII-8BIT regular expressions and by special
+# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
# characters like the umlaut in UTF-8.
module EncodedKeyCacheBehavior
Encoding.list.each do |encoding|
@@ -943,29 +963,28 @@ class CacheEntryTest < ActiveSupport::TestCase
assert_equal value.bytesize, entry.size
end
- def test_restoring_version_3_entries
- version_3_entry = ActiveSupport::Cache::Entry.allocate
- version_3_entry.instance_variable_set(:@value, "hello")
- version_3_entry.instance_variable_set(:@created_at, Time.now - 60)
- entry = Marshal.load(Marshal.dump(version_3_entry))
+ def test_restoring_version_4beta1_entries
+ version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
+ version_4beta1_entry.instance_variable_set(:@v, "hello")
+ version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60)
+ entry = Marshal.load(Marshal.dump(version_4beta1_entry))
assert_equal "hello", entry.value
assert_equal false, entry.expired?
end
- def test_restoring_compressed_version_3_entries
- version_3_entry = ActiveSupport::Cache::Entry.allocate
- version_3_entry.instance_variable_set(:@value, Zlib::Deflate.deflate(Marshal.dump("hello")))
- version_3_entry.instance_variable_set(:@compressed, true)
- entry = Marshal.load(Marshal.dump(version_3_entry))
+ def test_restoring_compressed_version_4beta1_entries
+ version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
+ version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello")))
+ version_4beta1_entry.instance_variable_set(:@c, true)
+ entry = Marshal.load(Marshal.dump(version_4beta1_entry))
assert_equal "hello", entry.value
end
- def test_restoring_expired_version_3_entries
- version_3_entry = ActiveSupport::Cache::Entry.allocate
- version_3_entry.instance_variable_set(:@value, "hello")
- version_3_entry.instance_variable_set(:@created_at, Time.now - 60)
- version_3_entry.instance_variable_set(:@expires_in, 58.9)
- entry = Marshal.load(Marshal.dump(version_3_entry))
+ def test_restoring_expired_version_4beta1_entries
+ version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
+ version_4beta1_entry.instance_variable_set(:@v, "hello")
+ version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1)
+ entry = Marshal.load(Marshal.dump(version_4beta1_entry))
assert_equal "hello", entry.value
assert_equal true, entry.expired?
end
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index 6be8ea8b84..1adfe4edf4 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -109,7 +109,6 @@ class BasicCallbacksTest < ActiveSupport::TestCase
@index = GrandParent.new("index").dispatch
@update = GrandParent.new("update").dispatch
@delete = GrandParent.new("delete").dispatch
- @unknown = GrandParent.new("unknown").dispatch
end
def test_basic_conditional_callback1
@@ -130,7 +129,6 @@ class InheritedCallbacksTest < ActiveSupport::TestCase
@index = Parent.new("index").dispatch
@update = Parent.new("update").dispatch
@delete = Parent.new("delete").dispatch
- @unknown = Parent.new("unknown").dispatch
end
def test_inherited_excluded
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 13f2e3cdaf..f8e2ce22fa 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -66,6 +66,16 @@ module CallbacksTest
end
end
+ class CallbackClass
+ def self.before(model)
+ model.history << [:before_save, :class]
+ end
+
+ def self.after(model)
+ model.history << [:after_save, :class]
+ end
+ end
+
class Person < Record
[:before_save, :after_save].each do |callback_method|
callback_method_sym = callback_method.to_sym
@@ -73,6 +83,7 @@ module CallbacksTest
send(callback_method, callback_string(callback_method_sym))
send(callback_method, callback_proc(callback_method_sym))
send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, '')))
+ send(callback_method, CallbackClass)
send(callback_method) { |model| model.history << [callback_method_sym, :block] }
end
@@ -86,10 +97,14 @@ module CallbacksTest
skip_callback :save, :after, :before_save_method, :unless => :yes
skip_callback :save, :after, :before_save_method, :if => :no
skip_callback :save, :before, :before_save_method, :unless => :no
+ skip_callback :save, :before, CallbackClass , :if => :yes
def yes; true; end
def no; false; end
end
+ class PersonForProgrammaticSkipping < Person
+ end
+
class ParentController
include ActiveSupport::Callbacks
@@ -430,6 +445,26 @@ module CallbacksTest
[:before_save, :object],
[:before_save, :block],
[:after_save, :block],
+ [:after_save, :class],
+ [:after_save, :object],
+ [:after_save, :proc],
+ [:after_save, :string],
+ [:after_save, :symbol]
+ ], person.history
+ end
+
+ def test_skip_person_programmatically
+ PersonForProgrammaticSkipping._save_callbacks.each do |save_callback|
+ if "before" == save_callback.kind.to_s
+ PersonForProgrammaticSkipping.skip_callback("save", save_callback.kind, save_callback.filter)
+ end
+ end
+ person = PersonForProgrammaticSkipping.new
+ assert_equal [], person.history
+ person.save
+ assert_equal [
+ [:after_save, :block],
+ [:after_save, :class],
[:after_save, :object],
[:after_save, :proc],
[:after_save, :string],
@@ -449,8 +484,10 @@ module CallbacksTest
[:before_save, :string],
[:before_save, :proc],
[:before_save, :object],
+ [:before_save, :class],
[:before_save, :block],
[:after_save, :block],
+ [:after_save, :class],
[:after_save, :object],
[:after_save, :proc],
[:after_save, :string],
@@ -488,7 +525,7 @@ module CallbacksTest
class CallbackTerminator
include ActiveSupport::Callbacks
- define_callbacks :save, :terminator => "result == :halt"
+ define_callbacks :save, :terminator => ->(_,result) { result == :halt }
set_callback :save, :before, :first
set_callback :save, :before, :second
@@ -681,7 +718,7 @@ module CallbacksTest
def test_termination_invokes_hook
terminator = CallbackTerminator.new
terminator.save
- assert_equal ":second", terminator.halted
+ assert_equal :second, terminator.halted
end
def test_block_never_called_if_terminated
@@ -715,8 +752,10 @@ module CallbacksTest
[:before_save, :string],
[:before_save, :proc],
[:before_save, :object],
+ [:before_save, :class],
[:before_save, :block],
[:after_save, :block],
+ [:after_save, :class],
[:after_save, :object],
[:after_save, :proc],
[:after_save, :string],
@@ -733,22 +772,6 @@ module CallbacksTest
end
end
- class PerKeyOptionDeprecationTest < ActiveSupport::TestCase
-
- def test_per_key_option_deprecaton
- assert_raise NotImplementedError do
- Phone.class_eval do
- set_callback :save, :before, :before_save1, :per_key => {:if => "true"}
- end
- end
- assert_raise NotImplementedError do
- Phone.class_eval do
- skip_callback :save, :before, :before_save1, :per_key => {:if => "true"}
- end
- end
- end
- end
-
class ExcludingDuplicatesCallbackTest < ActiveSupport::TestCase
def test_excludes_duplicates_in_separate_calls
model = DuplicatingCallbacks.new
@@ -762,4 +785,240 @@ module CallbacksTest
assert_equal ["two", "one", "three", "yielded"], model.record
end
end
+
+ class CallbackProcTest < ActiveSupport::TestCase
+ def build_class(callback)
+ Class.new {
+ include ActiveSupport::Callbacks
+ define_callbacks :foo
+ set_callback :foo, :before, callback
+ def run; run_callbacks :foo; end
+ }
+ end
+
+ def test_proc_arity_0
+ calls = []
+ klass = build_class(->() { calls << :foo })
+ klass.new.run
+ assert_equal [:foo], calls
+ end
+
+ def test_proc_arity_1
+ calls = []
+ klass = build_class(->(o) { calls << o })
+ instance = klass.new
+ instance.run
+ assert_equal [instance], calls
+ end
+
+ def test_proc_arity_2
+ assert_raises(ArgumentError) do
+ klass = build_class(->(x,y) { })
+ klass.new.run
+ end
+ end
+
+ def test_proc_negative_called_with_empty_list
+ calls = []
+ klass = build_class(->(*args) { calls << args })
+ klass.new.run
+ assert_equal [[]], calls
+ end
+ end
+
+ class ConditionalTests < ActiveSupport::TestCase
+ def build_class(callback)
+ Class.new {
+ include ActiveSupport::Callbacks
+ define_callbacks :foo
+ set_callback :foo, :before, :foo, :if => callback
+ def foo; end
+ def run; run_callbacks :foo; end
+ }
+ end
+
+ # FIXME: do we really want to support classes as conditionals? There were
+ # no tests for it previous to this.
+ def test_class_conditional_with_scope
+ z = []
+ callback = Class.new {
+ define_singleton_method(:foo) { |o| z << o }
+ }
+ klass = Class.new {
+ include ActiveSupport::Callbacks
+ define_callbacks :foo, :scope => [:name]
+ set_callback :foo, :before, :foo, :if => callback
+ def run; run_callbacks :foo; end
+ private
+ def foo; end
+ }
+ object = klass.new
+ object.run
+ assert_equal [object], z
+ end
+
+ # FIXME: do we really want to support classes as conditionals? There were
+ # no tests for it previous to this.
+ def test_class
+ z = []
+ klass = build_class Class.new {
+ define_singleton_method(:before) { |o| z << o }
+ }
+ object = klass.new
+ object.run
+ assert_equal [object], z
+ end
+
+ def test_proc_negative_arity # passes an empty list if *args
+ z = []
+ object = build_class(->(*args) { z << args }).new
+ object.run
+ assert_equal [], z.flatten
+ end
+
+ def test_proc_arity0
+ z = []
+ object = build_class(->() { z << 0 }).new
+ object.run
+ assert_equal [0], z
+ end
+
+ def test_proc_arity1
+ z = []
+ object = build_class(->(x) { z << x }).new
+ object.run
+ assert_equal [object], z
+ end
+
+ def test_proc_arity2
+ assert_raises(ArgumentError) do
+ object = build_class(->(a,b) { }).new
+ object.run
+ end
+ end
+ end
+
+ class ResetCallbackTest < ActiveSupport::TestCase
+ def build_class(memo)
+ klass = Class.new {
+ include ActiveSupport::Callbacks
+ define_callbacks :foo
+ set_callback :foo, :before, :hello
+ def run; run_callbacks :foo; end
+ }
+ klass.class_eval {
+ define_method(:hello) { memo << :hi }
+ }
+ klass
+ end
+
+ def test_reset_callbacks
+ events = []
+ klass = build_class events
+ klass.new.run
+ assert_equal 1, events.length
+
+ klass.reset_callbacks :foo
+ klass.new.run
+ assert_equal 1, events.length
+ end
+
+ def test_reset_impacts_subclasses
+ events = []
+ klass = build_class events
+ subclass = Class.new(klass) { set_callback :foo, :before, :world }
+ subclass.class_eval { define_method(:world) { events << :world } }
+
+ subclass.new.run
+ assert_equal 2, events.length
+
+ klass.reset_callbacks :foo
+ subclass.new.run
+ assert_equal 3, events.length
+ end
+ end
+
+ class CallbackTypeTest < ActiveSupport::TestCase
+ def build_class(callback, n = 10)
+ Class.new {
+ include ActiveSupport::Callbacks
+ define_callbacks :foo
+ n.times { set_callback :foo, :before, callback }
+ def run; run_callbacks :foo; end
+ def self.skip(thing); skip_callback :foo, :before, thing; end
+ }
+ end
+
+ def test_add_class
+ calls = []
+ callback = Class.new {
+ define_singleton_method(:before) { |o| calls << o }
+ }
+ build_class(callback).new.run
+ assert_equal 10, calls.length
+ end
+
+ def test_add_lambda
+ calls = []
+ build_class(->(o) { calls << o }).new.run
+ assert_equal 10, calls.length
+ end
+
+ def test_add_symbol
+ calls = []
+ klass = build_class(:bar)
+ klass.class_eval { define_method(:bar) { calls << klass } }
+ klass.new.run
+ assert_equal 1, calls.length
+ end
+
+ def test_add_eval
+ calls = []
+ klass = build_class("bar")
+ klass.class_eval { define_method(:bar) { calls << klass } }
+ klass.new.run
+ assert_equal 1, calls.length
+ end
+
+ def test_skip_class # removes one at a time
+ calls = []
+ callback = Class.new {
+ define_singleton_method(:before) { |o| calls << o }
+ }
+ klass = build_class(callback)
+ 9.downto(0) { |i|
+ klass.skip callback
+ klass.new.run
+ assert_equal i, calls.length
+ calls.clear
+ }
+ end
+
+ def test_skip_lambda # removes nothing
+ calls = []
+ callback = ->(o) { calls << o }
+ klass = build_class(callback)
+ 10.times { klass.skip callback }
+ klass.new.run
+ assert_equal 10, calls.length
+ end
+
+ def test_skip_symbol # removes all
+ calls = []
+ klass = build_class(:bar)
+ klass.class_eval { define_method(:bar) { calls << klass } }
+ klass.skip :bar
+ klass.new.run
+ assert_equal 0, calls.length
+ end
+
+ def test_skip_eval # removes nothing
+ calls = []
+ klass = build_class("bar")
+ klass.class_eval { define_method(:bar) { calls << klass } }
+ klass.skip "bar"
+ klass.new.run
+ assert_equal 1, calls.length
+ end
+ end
end
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 912ce30c29..8e2c298fc6 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -91,4 +91,18 @@ class ConcernTest < ActiveSupport::TestCase
@klass.send(:include, Foo)
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
end
+
+ def test_raise_on_multiple_included_calls
+ assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ included do
+ end
+
+ included do
+ end
+ end
+ end
+ end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index efa7582ab0..384064d81f 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -96,6 +96,10 @@ class ArrayExtToSentenceTests < ActiveSupport::TestCase
assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options)
assert_equal({ words_connector: ' ' }, options)
end
+
+ def test_with_blank_elements
+ assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence
+ end
end
class ArrayExtToSTests < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb
index 8d827f054e..0d5f39a72b 100644
--- a/activesupport/test/core_ext/class/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb
@@ -44,16 +44,18 @@ class ClassAttributeAccessorTest < ActiveSupport::TestCase
end
def test_should_raise_name_error_if_attribute_name_is_invalid
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- cattr_reader "invalid attribute name"
+ cattr_reader "1nvalid"
end
end
+ assert_equal "invalid class attribute name: 1nvalid", exception.message
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- cattr_writer "invalid attribute name"
+ cattr_writer "1nvalid"
end
end
+ assert_equal "invalid class attribute name: 1nvalid", exception.message
end
end
diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb
index 1c3ba8a7a0..e7a1334db3 100644
--- a/activesupport/test/core_ext/class/attribute_test.rb
+++ b/activesupport/test/core_ext/class/attribute_test.rb
@@ -27,7 +27,7 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_equal 1, Class.new(@sub).setting
end
- test 'query method' do
+ test 'predicate method' do
assert_equal false, @klass.setting?
@klass.setting = 1
assert_equal true, @klass.setting?
@@ -48,7 +48,7 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_equal 1, object.setting
end
- test 'instance query' do
+ test 'instance predicate' do
object = @klass.new
assert_equal false, object.setting?
object.setting = 1
@@ -73,6 +73,11 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { object.setting = 'boom' }
end
+ test 'disabling instance predicate' do
+ object = Class.new { class_attribute :setting, instance_predicate: false }.new
+ assert_raise(NoMethodError) { object.setting? }
+ end
+
test 'works well with singleton classes' do
object = @klass.new
object.singleton_class.setting = 'foo'
diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb
index 9927856aa2..b4ef5a0597 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -95,6 +95,11 @@ module DateAndTimeBehavior
end
def test_next_week
+ # M | T | W | T | F | S | S # M | T | W | T | F | S | S #
+ # | 22/2 | | | | | # 28/2 | | | | | | # monday in next week `next_week`
+ # | 22/2 | | | | | # | | | | 4/3 | | # friday in next week `next_week(:friday)`
+ # 23/10 | | | | | | # 30/10 | | | | | | # monday in next week `next_week`
+ # 23/10 | | | | | | # | | 1/11 | | | | # wednesday in next week `next_week(:wednesday)`
assert_equal date_time_init(2005,2,28,0,0,0), date_time_init(2005,2,22,15,15,10).next_week
assert_equal date_time_init(2005,3,4,0,0,0), date_time_init(2005,2,22,15,15,10).next_week(:friday)
assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index f3fa96ec6f..aa5e4461fd 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -48,6 +48,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_compare_to_time
+ assert Date.yesterday < Time.now
+ end
+
def test_to_datetime
assert_equal DateTime.civil(2005, 2, 21), Date.new(2005, 2, 21).to_datetime
assert_equal 0, Date.new(2005, 2, 21).to_datetime.offset # use UTC offset
@@ -357,11 +361,6 @@ class DateExtBehaviorTest < ActiveSupport::TestCase
Date.today.freeze.freeze
end
end
-
- def test_compare_with_infinity
- assert_equal(-1, Date.today <=> Float::INFINITY)
- assert_equal(1, Date.today <=> -Float::INFINITY)
- end
end
class DateExtConversionsTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 7be578599b..571344b728 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -327,6 +327,16 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal 946684800, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_i
end
+ def test_usec
+ assert_equal 0, DateTime.civil(2000).usec
+ assert_equal 500000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).usec
+ end
+
+ def test_nsec
+ assert_equal 0, DateTime.civil(2000).nsec
+ assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
@@ -335,10 +345,3 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
end
-
-class DateTimeExtBehaviorTest < ActiveSupport::TestCase
- def test_compare_with_infinity
- assert_equal(-1, DateTime.now <=> Float::INFINITY)
- assert_equal(1, DateTime.now <=> -Float::INFINITY)
- end
-end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 0a1abac767..6781e3c20e 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -24,10 +24,8 @@ class EnumerableTests < ActiveSupport::TestCase
def test_group_by
names = %w(marcel sam david jeremy)
klass = Struct.new(:name)
- objects = (1..50).inject([]) do |people,|
- p = klass.new
- p.name = names.sort_by { rand }.first
- people << p
+ objects = (1..50).map do
+ klass.new names.sample
end
enum = GenericEnumerable.new(objects)
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 30d95b75bc..39bd0a2dd4 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -490,6 +490,10 @@ class HashExtTest < ActiveSupport::TestCase
roundtrip = mixed_with_default.with_indifferent_access.to_hash
assert_equal @strings, roundtrip
assert_equal '1234', roundtrip.default
+ new_to_hash = @nested_mixed.with_indifferent_access.to_hash
+ assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
end
def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
@@ -499,9 +503,21 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal [1], hash[:a]
end
+ def test_with_indifferent_access_has_no_side_effects_on_existing_hash
+ hash = {content: [{:foo => :bar, 'bar' => 'baz'}]}
+ hash.with_indifferent_access
+
+ assert_equal [:foo, "bar"], hash[:content].first.keys
+ end
+
def test_indifferent_hash_with_array_of_hashes
hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
assert_equal "1", hash[:urls][:url].first[:address]
+
+ hash = hash.to_hash
+ assert_not hash.instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
end
def test_should_preserve_array_subclass_when_value_is_array
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 82249ddd1b..8872611fb1 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -82,6 +82,21 @@ class Name
end
end
+class SideEffect
+ attr_reader :ints
+
+ delegate :to_i, :to => :shift, :allow_nil => true
+ delegate :to_s, :to => :shift
+
+ def initialize
+ @ints = [1, 2, 3]
+ end
+
+ def shift
+ @ints.shift
+ end
+end
+
class ModuleTest < ActiveSupport::TestCase
def setup
@david = Someone.new("David", Somewhere.new("Paulina", "Chicago"))
@@ -171,6 +186,17 @@ class ModuleTest < ActiveSupport::TestCase
assert_nil rails.name
end
+ # Ensures with check for nil, not for a falseish target.
+ def test_delegation_with_allow_nil_and_false_value
+ project = Project.new(false, false)
+ assert_raise(NoMethodError) { project.name }
+ end
+
+ def test_delegation_with_allow_nil_and_invalid_value
+ rails = Project.new("Rails", "David")
+ assert_raise(NoMethodError) { rails.name }
+ end
+
def test_delegation_with_allow_nil_and_nil_value_and_prefix
Project.class_eval do
delegate :name, :to => :person, :allow_nil => true, :prefix => true
@@ -228,6 +254,16 @@ class ModuleTest < ActiveSupport::TestCase
"[#{e.backtrace.inspect}] did not include [#{file_and_line}]"
end
+ def test_delegation_invokes_the_target_exactly_once
+ se = SideEffect.new
+
+ assert_equal 1, se.to_i
+ assert_equal [2, 3], se.ints
+
+ assert_equal '2', se.to_s
+ assert_equal [3], se.ints
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 3744d50864..1da72eb17d 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -77,7 +77,7 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
assert_equal @dtnow.advance(:days => 1).advance(:months => 2), @dtnow + 1.day + 2.months
end
- def test_duration_after_convertion_is_no_longer_accurate
+ def test_duration_after_conversion_is_no_longer_accurate
assert_equal 30.days.to_i.since(@now), 1.month.to_i.since(@now)
assert_equal 365.25.days.to_f.since(@now), 1.year.to_f.since(@now)
assert_equal 30.days.to_i.since(@dtnow), 1.month.to_i.since(@dtnow)
@@ -441,57 +441,3 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
end
-
-class NumericExtBehaviorTest < ActiveSupport::TestCase
- def setup
- @inf = BigDecimal.new('Infinity')
- end
-
- def test_compare_infinity_with_date
- assert_equal(-1, -Float::INFINITY <=> Date.today)
- assert_equal(1, Float::INFINITY <=> Date.today)
- assert_equal(-1, -@inf <=> Date.today)
- assert_equal(1, @inf <=> Date.today)
- end
-
- def test_compare_infinty_with_infinty
- assert_equal(-1, -Float::INFINITY <=> Float::INFINITY)
- assert_equal(1, Float::INFINITY <=> -Float::INFINITY)
- assert_equal(0, Float::INFINITY <=> Float::INFINITY)
- assert_equal(0, -Float::INFINITY <=> -Float::INFINITY)
-
- assert_equal(-1, -Float::INFINITY <=> BigDecimal::INFINITY)
- assert_equal(1, Float::INFINITY <=> -BigDecimal::INFINITY)
- assert_equal(0, Float::INFINITY <=> BigDecimal::INFINITY)
- assert_equal(0, -Float::INFINITY <=> -BigDecimal::INFINITY)
-
- assert_equal(-1, -BigDecimal::INFINITY <=> Float::INFINITY)
- assert_equal(1, BigDecimal::INFINITY <=> -Float::INFINITY)
- assert_equal(0, BigDecimal::INFINITY <=> Float::INFINITY)
- assert_equal(0, -BigDecimal::INFINITY <=> -Float::INFINITY)
- end
-
- def test_compare_infinity_with_time
- assert_equal(-1, -Float::INFINITY <=> Time.now)
- assert_equal(1, Float::INFINITY <=> Time.now)
- assert_equal(-1, -@inf <=> Time.now)
- assert_equal(1, @inf <=> Time.now)
- end
-
- def test_compare_infinity_with_datetime
- assert_equal(-1, -Float::INFINITY <=> DateTime.now)
- assert_equal(1, Float::INFINITY <=> DateTime.now)
- assert_equal(-1, -@inf <=> DateTime.now)
- assert_equal(1, @inf <=> DateTime.now)
- end
-
- def test_compare_infinity_with_twz
- time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone)
-
- assert_equal(-1, -Float::INFINITY <=> twz)
- assert_equal(1, Float::INFINITY <=> twz)
- assert_equal(-1, -@inf <=> twz)
- assert_equal(1, @inf <=> twz)
- end
-end
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index 22888333f5..478706eeae 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -2,16 +2,6 @@ require 'abstract_unit'
require 'active_support/core_ext/object/inclusion'
class InTest < ActiveSupport::TestCase
- def test_in_multiple_args
- assert :b.in?(:a,:b)
- assert !:c.in?(:a,:b)
- end
-
- def test_in_multiple_arrays
- assert [1,2].in?([1,2],[2,3])
- assert ![1,2].in?([1,3],[2,1])
- end
-
def test_in_array
assert 1.in?([1,2])
assert !3.in?([1,2])
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 6e94d5e10d..2f8723a074 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -1,7 +1,6 @@
require 'abstract_unit'
require 'active_support/time'
require 'active_support/core_ext/range'
-require 'active_support/core_ext/numeric'
class RangeTest < ActiveSupport::TestCase
def test_to_s_from_dates
@@ -17,7 +16,6 @@ class RangeTest < ActiveSupport::TestCase
def test_date_range
assert_instance_of Range, DateTime.new..DateTime.new
assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new
- assert_instance_of Range, DateTime.new..DateTime::Infinity.new
end
def test_overlaps_last_inclusive
@@ -56,7 +54,7 @@ class RangeTest < ActiveSupport::TestCase
assert((1...10) === (1...10))
end
- def test_should_compare_other_with_exlusive_end
+ def test_should_compare_other_with_exclusive_end
assert((1..10) === (1...10))
end
@@ -92,28 +90,4 @@ class RangeTest < ActiveSupport::TestCase
time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00)
assert !time_range_1.overlaps?(time_range_2)
end
-
- def test_infinite_bounds
- time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
-
- time = Time.now
- date = Date.today
- datetime = DateTime.now
- twz = ActiveSupport::TimeWithZone.new(time, time_zone)
-
- infinity1 = Float::INFINITY
- infinity2 = BigDecimal.new('Infinity')
-
- [infinity1, infinity2].each do |infinity|
- [time, date, datetime, twz].each do |bound|
- [time, date, datetime, twz].each do |value|
- assert Range.new(bound, infinity).include?(value + 10.years)
- assert Range.new(-infinity, bound).include?(value - 10.years)
-
- assert !Range.new(bound, infinity).include?(value - 10.years)
- assert !Range.new(-infinity, bound).include?(value + 10.years)
- end
- end
- end
- end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 62c5741ffb..8f0ebc13ea 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -300,9 +300,9 @@ class StringConversionsTest < ActiveSupport::TestCase
assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time
assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:utc)
assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time
- assert_equal Time.local(2011, 2, 27, 18, 50), "2011-02-27 13:50 -0100".to_time
+ assert_equal Time.local(2011, 2, 27, 17, 50), "2011-02-27 13:50 -0100".to_time
assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc)
- assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 14:50 -0500".to_time
+ assert_equal Time.local(2005, 2, 27, 22, 50), "2005-02-27 14:50 -0500".to_time
assert_nil "".to_time
end
end
@@ -326,6 +326,122 @@ class StringConversionsTest < ActiveSupport::TestCase
end
end
+ def test_standard_time_string_to_time_when_current_time_is_standard_time
+ with_env_tz "US/Eastern" do
+ Time.stubs(:now).returns(Time.local(2012, 1, 1))
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ end
+ end
+
+ def test_standard_time_string_to_time_when_current_time_is_daylight_savings
+ with_env_tz "US/Eastern" do
+ Time.stubs(:now).returns(Time.local(2012, 7, 1))
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ end
+ end
+
+ def test_daylight_savings_string_to_time_when_current_time_is_standard_time
+ with_env_tz "US/Eastern" do
+ Time.stubs(:now).returns(Time.local(2012, 1, 1))
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ end
+ end
+
+ def test_daylight_savings_string_to_time_when_current_time_is_daylight_savings
+ with_env_tz "US/Eastern" do
+ Time.stubs(:now).returns(Time.local(2012, 7, 1))
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ end
+ end
+
+ def test_partial_string_to_time_when_current_time_is_standard_time
+ with_env_tz "US/Eastern" do
+ Time.stubs(:now).returns(Time.local(2012, 1, 1))
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time
+ assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time
+ assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time
+ assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ end
+ end
+
+ def test_partial_string_to_time_when_current_time_is_daylight_savings
+ with_env_tz "US/Eastern" do
+ Time.stubs(:now).returns(Time.local(2012, 7, 1))
+ assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time
+ assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time
+ assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time
+ assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time
+ assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ end
+ end
+
def test_string_to_datetime
assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_datetime
assert_equal 0, "2039-02-27 23:50".to_datetime.offset # use UTC offset
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 2864d7a57f..eefcdbb1b3 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -741,6 +741,28 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] ))
end
+ def test_at_with_datetime
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0))
+
+ # Only test this if the underlying Time.at raises a TypeError
+ begin
+ Time.at_without_coercion(Time.now, 0)
+ rescue TypeError
+ assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0), 0)) }
+ end
+ end
+
+ def test_at_with_time_with_zone
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']))
+
+ # Only test this if the underlying Time.at raises a TypeError
+ begin
+ Time.at_without_coercion(Time.now, 0)
+ rescue TypeError
+ assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']), 0)) }
+ end
+ end
+
def test_eql?
assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) )
assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
@@ -855,10 +877,3 @@ class TimeExtMarshalingTest < ActiveSupport::TestCase
assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter
end
end
-
-class TimeExtBehaviorTest < ActiveSupport::TestCase
- def test_compare_with_infinity
- assert_equal(-1, Time.now <=> Float::INFINITY)
- assert_equal(1, Time.now <=> -Float::INFINITY)
- end
-end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 98a87ab9e6..ddcfcc491f 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -445,6 +445,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal 0, twz.usec
end
+ def test_usec_returns_sec_fraction_when_datetime_is_wrapped
+ twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone)
+ assert_equal 500000, twz.usec
+ end
+
+ def test_nsec_returns_sec_fraction_when_datetime_is_wrapped
+ twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone)
+ assert_equal 500000000, twz.nsec
+ end
+
def test_utc_to_local_conversion_saves_period_in_instance_variable
assert_nil @twz.instance_variable_get('@period')
@twz.time
@@ -1118,13 +1128,3 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase
Time.zone = old_tz
end
end
-
-class TimeWithZoneExtBehaviorTest < ActiveSupport::TestCase
- def test_compare_with_infinity
- time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone)
-
- assert_equal(-1, twz <=> Float::INFINITY)
- assert_equal(1, twz <=> -Float::INFINITY)
- end
-end
diff --git a/activesupport/test/dependencies/raises_exception_without_blame_file.rb b/activesupport/test/dependencies/raises_exception_without_blame_file.rb
new file mode 100644
index 0000000000..4b2da6ff30
--- /dev/null
+++ b/activesupport/test/dependencies/raises_exception_without_blame_file.rb
@@ -0,0 +1,5 @@
+exception = Exception.new('I am not blamable!')
+class << exception
+ undef_method(:blame_file!)
+end
+raise exception
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 115a4e894d..68b6cc6e8c 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -76,6 +76,14 @@ class DependenciesTest < ActiveSupport::TestCase
end
end
+ def test_dependency_which_raises_doesnt_blindly_call_blame_file!
+ with_loading do
+ filename = 'dependencies/raises_exception_without_blame_file'
+
+ assert_raises(Exception) { require_dependency filename }
+ end
+ end
+
def test_warnings_should_be_enabled_on_first_load
with_loading 'dependencies' do
old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true
@@ -526,7 +534,6 @@ class DependenciesTest < ActiveSupport::TestCase
m = Module.new
m.module_eval "def a() CountingLoader; end"
extend m
- kls = nil
with_autoloading_fixtures do
kls = nil
assert_nothing_raised { kls = a }
diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb
index ab1be296f8..a2ae066a21 100644
--- a/activesupport/test/descendants_tracker_with_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -6,7 +6,7 @@ require 'descendants_tracker_test_cases'
class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
include DescendantsTrackerTestCases
- def test_clear_with_autoloaded_parent_children_and_granchildren
+ def test_clear_with_autoloaded_parent_children_and_grandchildren
mark_as_autoloaded(*ALL) do
ActiveSupport::DescendantsTracker.clear
ALL.each do |k|
@@ -15,7 +15,7 @@ class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
end
end
- def test_clear_with_autoloaded_children_and_granchildren
+ def test_clear_with_autoloaded_children_and_grandchildren
mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
ActiveSupport::DescendantsTracker.clear
assert_equal_sets [Child2], Parent.descendants
@@ -23,7 +23,7 @@ class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
end
end
- def test_clear_with_autoloaded_granchildren
+ def test_clear_with_autoloaded_grandchildren
mark_as_autoloaded Grandchild1, Grandchild2 do
ActiveSupport::DescendantsTracker.clear
assert_equal_sets [Child1, Child2], Parent.descendants
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index d1454902e5..34ed866848 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -55,29 +55,23 @@ class TestJSONDecoding < ActiveSupport::TestCase
%q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}
}
- backends = [:ok_json]
- backends << :json_gem if defined?(::JSON)
- backends << :yajl if defined?(::Yajl)
-
- backends.each do |backend|
- TESTS.each do |json, expected|
- test "json decodes #{json} with the #{backend} backend" do
- ActiveSupport.parse_json_times = true
- silence_warnings do
- ActiveSupport::JSON.with_backend backend do
- assert_equal expected, ActiveSupport::JSON.decode(json)
- end
- end
+ TESTS.each do |json, expected|
+ test "json decodes #{json}" do
+ prev = ActiveSupport.parse_json_times
+ ActiveSupport.parse_json_times = true
+ silence_warnings do
+ assert_equal expected, ActiveSupport::JSON.decode(json)
end
+ ActiveSupport.parse_json_times = prev
end
+ end
- test "json decodes time json with time parsing disabled with the #{backend} backend" do
- ActiveSupport.parse_json_times = false
- expected = {"a" => "2007-01-01 01:12:34 Z"}
- ActiveSupport::JSON.with_backend backend do
- assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
- end
- end
+ test "json decodes time json with time parsing disabled" do
+ prev = ActiveSupport.parse_json_times
+ ActiveSupport.parse_json_times = false
+ expected = {"a" => "2007-01-01 01:12:34 Z"}
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
+ ActiveSupport.parse_json_times = prev
end
def test_failed_json_decoding
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 12ce250eb3..ed1326705c 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -45,8 +45,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
- [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
- %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F") ]]
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\342\200\250\342\200\251",
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F\\u2028\\u2029") ]]
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
@@ -82,6 +82,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
constants.grep(/Tests$/).each do |class_tests|
define_method("test_#{class_tests[0..-6].underscore}") do
begin
+ prev = ActiveSupport.use_standard_json_time_format
+
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
self.class.const_get(class_tests).each do |pair|
@@ -89,18 +91,11 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
ensure
ActiveSupport.escape_html_entities_in_json = false
- ActiveSupport.use_standard_json_time_format = false
+ ActiveSupport.use_standard_json_time_format = prev
end
end
end
- def test_json_variable
- assert_deprecated do
- assert_equal ActiveSupport::JSON::Variable.new('foo'), 'foo'
- assert_equal ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")'
- end
- end
-
def test_hash_encoding
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
@@ -172,23 +167,28 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_time_to_json_includes_local_offset
+ prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.use_standard_json_time_format = true
with_env_tz 'US/Eastern' do
assert_equal %("2005-02-01T15:15:10-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
end
ensure
- ActiveSupport.use_standard_json_time_format = false
+ ActiveSupport.use_standard_json_time_format = prev
end
def test_hash_with_time_to_json
+ prev = ActiveSupport.use_standard_json_time_format
+ ActiveSupport.use_standard_json_time_format = false
assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
+ ensure
+ ActiveSupport.use_standard_json_time_format = prev
end
def test_nested_hash_with_float
assert_nothing_raised do
hash = {
"CHI" => {
- :dislay_name => "chicago",
+ :display_name => "chicago",
:latitude => 123.234
}
}
@@ -302,12 +302,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal({"name" => "David",
"sub" => {
"name" => "David",
- "date" => "2010/01/01" }}, JSON.parse(json_custom))
+ "date" => "2010-01-01" }}, JSON.parse(json_custom))
assert_equal({"name" => "David", "email" => "sample@example.com"},
JSON.parse(json_strings))
- assert_equal({"name" => "David", "date" => "2010/01/01"},
+ assert_equal({"name" => "David", "date" => "2010-01-01"},
JSON.parse(json_string_and_date))
end
diff --git a/activesupport/test/load_paths_test.rb b/activesupport/test/load_paths_test.rb
index 979e25bdf3..ac617a9fd8 100644
--- a/activesupport/test/load_paths_test.rb
+++ b/activesupport/test/load_paths_test.rb
@@ -10,7 +10,7 @@ class LoadPathsTest < ActiveSupport::TestCase
}
load_paths_count[File.expand_path('../../lib', __FILE__)] -= 1
- filtered = load_paths_count.select { |k, v| v > 1 }
- assert filtered.empty?, filtered.inspect
+ load_paths_count.select! { |k, v| v > 1 }
+ assert load_paths_count.empty?, load_paths_count.inspect
end
end
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 06c7e8a1a7..509c453b5c 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -56,9 +56,14 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
def test_alternative_serialization_method
+ prev = ActiveSupport.use_standard_json_time_format
+ ActiveSupport.use_standard_json_time_format = true
encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new)
message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) })
- assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
+ exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
+ assert_equal exp, encryptor.decrypt_and_verify(message)
+ ensure
+ ActiveSupport.use_standard_json_time_format = prev
end
private
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index 5adff41653..a8633f7299 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -45,9 +45,14 @@ class MessageVerifierTest < ActiveSupport::TestCase
end
def test_alternative_serialization_method
+ prev = ActiveSupport.use_standard_json_time_format
+ ActiveSupport.use_standard_json_time_format = true
verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new)
message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) })
- assert_equal verifier.verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
+ exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
+ assert_equal exp, verifier.verify(message)
+ ensure
+ ActiveSupport.use_standard_json_time_format = prev
end
def assert_not_verified(message)
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index d63c59883a..f729f0a95b 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -81,6 +81,20 @@ module Notifications
end
end
+ class TestSubscriber
+ attr_reader :starts, :finishes, :publishes
+
+ def initialize
+ @starts = []
+ @finishes = []
+ @publishes = []
+ end
+
+ def start(*args); @starts << args; end
+ def finish(*args); @finishes << args; end
+ def publish(*args); @publishes << args; end
+ end
+
class SyncPubSubTest < TestCase
def test_events_are_published_to_a_listener
@notifier.publish :foo
@@ -99,7 +113,7 @@ module Notifications
@notifier.publish :foo
@notifier.publish :foo
- @notifier.subscribe("not_existant") do |*args|
+ @notifier.subscribe("not_existent") do |*args|
@events << ActiveSupport::Notifications::Event.new(*args)
end
@@ -144,6 +158,14 @@ module Notifications
assert_equal [[:foo]], @another
end
+ def test_publish_with_subscriber
+ subscriber = TestSubscriber.new
+ @notifier.subscribe nil, subscriber
+ @notifier.publish :foo
+
+ assert_equal [[:foo]], subscriber.publishes
+ end
+
private
def event(*args)
args
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 6aea9d56f1..c3fe89de4b 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -244,11 +244,7 @@ class OrderedHashTest < ActiveSupport::TestCase
end
def test_each_after_yaml_serialization
- values = []
- @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash))
-
- @deserialized_ordered_hash.each {|key, value| values << value}
- assert_equal @values, values
+ assert_equal @values, YAML.load(YAML.dump(@ordered_hash)).values
end
def test_each_when_yielding_to_block_with_splat
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index f60f9a58e3..fdc745b23b 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -7,13 +7,13 @@ class OrderedOptionsTest < ActiveSupport::TestCase
assert_nil a[:not_set]
- a[:allow_concurreny] = true
+ a[:allow_concurrency] = true
assert_equal 1, a.size
- assert a[:allow_concurreny]
+ assert a[:allow_concurrency]
- a[:allow_concurreny] = false
+ a[:allow_concurrency] = false
assert_equal 1, a.size
- assert !a[:allow_concurreny]
+ assert !a[:allow_concurrency]
a["else_where"] = 56
assert_equal 2, a.size
@@ -23,10 +23,10 @@ class OrderedOptionsTest < ActiveSupport::TestCase
def test_looping
a = ActiveSupport::OrderedOptions.new
- a[:allow_concurreny] = true
+ a[:allow_concurrency] = true
a["else_where"] = 56
- test = [[:allow_concurreny, true], [:else_where, 56]]
+ test = [[:allow_concurrency, true], [:else_where, 56]]
a.each_with_index do |(key, value), index|
assert_equal test[index].first, key
@@ -39,13 +39,13 @@ class OrderedOptionsTest < ActiveSupport::TestCase
assert_nil a.not_set
- a.allow_concurreny = true
+ a.allow_concurrency = true
assert_equal 1, a.size
- assert a.allow_concurreny
+ assert a.allow_concurrency
- a.allow_concurreny = false
+ a.allow_concurrency = false
assert_equal 1, a.size
- assert !a.allow_concurreny
+ assert !a.allow_concurrency
a.else_where = 56
assert_equal 2, a.size
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index e099e47e0e..ec9d231125 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -97,7 +97,7 @@ class RescuableTest < ActiveSupport::TestCase
assert_equal expected, result
end
- def test_children_should_inherit_rescue_defintions_from_parents_and_child_rescue_should_be_appended
+ def test_children_should_inherit_rescue_definitions_from_parents_and_child_rescue_should_be_appended
expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"]
result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first}
assert_equal expected, result
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
deleted file mode 100644
index dfe9f3c11c..0000000000
--- a/activesupport/test/test_case_test.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-require 'abstract_unit'
-
-module ActiveSupport
- class TestCaseTest < ActiveSupport::TestCase
- class FakeRunner
- attr_reader :puked
-
- def initialize
- @puked = []
- end
-
- def puke(klass, name, e)
- @puked << [klass, name, e]
- end
-
- def options
- nil
- end
-
- def record(*args)
- end
- end
-
- def test_standard_error_raised_within_setup_callback_is_puked
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- setup :bad_callback
- def bad_callback; raise 'oh noes' end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- test.run fr
- klass, name, exception = *fr.puked.first
-
- assert_equal tc, klass
- assert_equal test_name, name
- assert_equal 'oh noes', exception.message
- end
-
- def test_standard_error_raised_within_teardown_callback_is_puked
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- teardown :bad_callback
- def bad_callback; raise 'oh noes' end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- test.run fr
- klass, name, exception = *fr.puked.first
-
- assert_equal tc, klass
- assert_equal test_name, name
- assert_equal 'oh noes', exception.message
- end
-
- def test_passthrough_exception_raised_within_test_method_is_not_rescued
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- def test_which_raises_interrupt; raise Interrupt; end
- end
-
- test_name = 'test_which_raises_interrupt'
- fr = FakeRunner.new
-
- test = tc.new test_name
- assert_raises(Interrupt) { test.run fr }
- end
-
- def test_passthrough_exception_raised_within_setup_callback_is_not_rescued
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- setup :callback_which_raises_interrupt
- def callback_which_raises_interrupt; raise Interrupt; end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- assert_raises(Interrupt) { test.run fr }
- end
-
- def test_passthrough_exception_raised_within_teardown_callback_is_not_rescued
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- teardown :callback_which_raises_interrupt
- def callback_which_raises_interrupt; raise Interrupt; end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- assert_raises(Interrupt) { test.run fr }
- end
-
- def test_pending_deprecation
- assert_deprecated do
- pending "should use #skip instead"
- end
- end
- end
-end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 3e6ac811a4..68f9ec6c00 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -201,6 +201,6 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
end
def test_logs_tagged_with_current_test_case
- assert_match "#{self.class}: #{__name__}\n", @out.string
+ assert_match "#{self.class}: #{name}\n", @out.string
end
end
diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb
deleted file mode 100644
index 294d6595f7..0000000000
--- a/activesupport/test/ts_isolated.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'active_support/testing/autorun'
-require 'active_support/test_case'
-require 'rbconfig'
-require 'active_support/core_ext/kernel/reporting'
-
-class TestIsolated < ActiveSupport::TestCase
- ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
-
- Dir["#{File.dirname(__FILE__)}/**/*_test.rb"].each do |file|
- define_method("test #{file}") do
- command = "#{ruby} -Ilib:test #{file}"
- result = silence_stderr { `#{command}` }
- assert $?.to_i.zero?, "#{command}\n#{result}"
- end
- end
-end
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index 2dfdde5c62..904ef7b208 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -176,10 +176,11 @@ if RUBY_PLATFORM =~ /java/
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
else
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index 36ac4161ea..e7cb350663 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -141,7 +141,7 @@ class LibxmlEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -193,10 +193,11 @@ class LibxmlEngineTest < ActiveSupport::TestCase
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
index 82337961a1..07485911c9 100644
--- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb
+++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
@@ -141,7 +141,7 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -184,10 +184,11 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 71f57e43d2..937517786e 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -155,7 +155,7 @@ class NokogiriEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -206,10 +206,11 @@ class NokogiriEngineTest < ActiveSupport::TestCase
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 884494e95e..84a5c44a87 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -56,9 +56,9 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
end
end
- def test_setting_nokogiri_as_backend
- XmlMini.backend = 'Nokogiri'
- assert_equal XmlMini_Nokogiri, XmlMini.backend
+ def test_setting_nokogirisax_as_backend
+ XmlMini.backend = 'NokogiriSAX'
+ assert_equal XmlMini_NokogiriSAX, XmlMini.backend
end
def test_blank_returns_empty_hash
@@ -156,7 +156,7 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -207,10 +207,11 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index c4770405f2..70a3b918fd 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -24,6 +24,13 @@ class REXMLEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
+
+ private
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index b0e52847e1..766f7f6f56 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,12 +1,3 @@
-## Rails 4.0.0 (unreleased) ##
-* Change Service pages(404, etc). *Stanislav Sobolev*
+* No changes.
-## Rails 4.0.0.beta1 (unreleased) ##
-
-* Split Validations and Callbacks guide into two. *Steve Klabnik*
-
-* New guide _Working with JavaScript in Rails_. *Steve Klabnik*
-
-* Guides updated to reflect new test locations. *Mike Moore*
-
-* Guides have a responsive design. *Joe Fiorini*
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/assets/images/challenge.png b/guides/assets/images/getting_started/challenge.png
index 30be3d7028..30be3d7028 100644
--- a/guides/assets/images/challenge.png
+++ b/guides/assets/images/getting_started/challenge.png
Binary files differ
diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png
new file mode 100644
index 0000000000..569dd846a8
--- /dev/null
+++ b/guides/assets/images/getting_started/rails_welcome.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
index c0de53555d..d60a30465f 100644
--- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png
+++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
Binary files differ
diff --git a/guides/assets/images/icons/README b/guides/assets/images/icons/README
index f12b2a730c..09da77fc86 100644
--- a/guides/assets/images/icons/README
+++ b/guides/assets/images/icons/README
@@ -1,5 +1,5 @@
Replaced the plain DocBook XSL admonition icons with Jimmac's DocBook
icons (http://jimmac.musichall.cz/ikony.php3). I dropped transparency
-from the Jimmac icons to get round MS IE and FOP PNG incompatibilies.
+from the Jimmac icons to get round MS IE and FOP PNG incompatibilities.
Stuart Rackham
diff --git a/guides/assets/images/rails_welcome.png b/guides/assets/images/rails_welcome.png
deleted file mode 100644
index 8ad2d351de..0000000000
--- a/guides/assets/images/rails_welcome.png
+++ /dev/null
Binary files differ
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
new file mode 100644
index 0000000000..2e604bec47
--- /dev/null
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -0,0 +1,37 @@
+# Activate the gem you are reporting the issue against.
+gem 'activerecord', '3.2.13'
+require 'active_record'
+require 'minitest/autorun'
+require 'logger'
+
+# This connection will do for database-independent bug reports.
+ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+
+ActiveRecord::Schema.define do
+ create_table :posts do |t|
+ end
+
+ create_table :comments do |t|
+ t.integer :post_id
+ end
+end
+
+class Post < ActiveRecord::Base
+ has_many :comments
+end
+
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+
+class BugTest < MiniTest::Unit::TestCase
+ def test_association_stuff
+ post = Post.create!
+ post.comments << Comment.create!
+
+ assert_equal 1, post.comments.count
+ assert_equal 1, Comment.count
+ assert_equal post.id, Comment.first.post.id
+ end
+end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
new file mode 100644
index 0000000000..68069cdd8d
--- /dev/null
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -0,0 +1,48 @@
+unless File.exists?('Gemfile')
+ File.write('Gemfile', <<-GEMFILE)
+ source 'https://rubygems.org'
+ gem 'rails', github: 'rails/rails'
+ gem 'sqlite3'
+ GEMFILE
+
+ system 'bundle'
+end
+
+require 'bundler'
+Bundler.setup(:default)
+
+require 'active_record'
+require 'minitest/autorun'
+require 'logger'
+
+# This connection will do for database-independent bug reports.
+ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+
+ActiveRecord::Schema.define do
+ create_table :posts do |t|
+ end
+
+ create_table :comments do |t|
+ t.integer :post_id
+ end
+end
+
+class Post < ActiveRecord::Base
+ has_many :comments
+end
+
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+
+class BugTest < MiniTest::Unit::TestCase
+ def test_association_stuff
+ post = Post.create!
+ post.comments << Comment.create!
+
+ assert_equal 1, post.comments.count
+ assert_equal 1, Comment.count
+ assert_equal post.id, Comment.first.post.id
+ end
+end
diff --git a/guides/code/getting_started/.gitignore b/guides/code/getting_started/.gitignore
index 25a742dff0..6a502e997f 100644
--- a/guides/code/getting_started/.gitignore
+++ b/guides/code/getting_started/.gitignore
@@ -1,4 +1,4 @@
-# See http://help.github.com/ignore-files/ for more about ignoring files.
+# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index dca00b43cd..acd2ed5160 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -2,26 +2,31 @@ source 'https://rubygems.org'
gem 'rails', '4.0.0'
+# Use sqlite3 as the database for Active Record
gem 'sqlite3'
-# Gems used only for assets and not required
-# in production environments by default.
-group :assets do
- gem 'sprockets-rails'
- gem 'sass-rails'
- gem 'coffee-rails'
+# Use SCSS for stylesheets
+gem 'sass-rails'
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- # gem 'therubyracer', platforms: :ruby
+# Use CoffeeScript for .js.coffee assets and views
+gem 'coffee-rails'
- gem 'uglifier', '>= 1.0.3'
-end
+# See https://github.com/sstephenson/execjs#readme for more supported runtimes
+# gem 'therubyracer', platforms: :ruby
+
+# Use Uglifier as compressor for JavaScript assets
+gem 'uglifier', '>= 1.0.3'
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
+group :doc do
+ # bundle exec rake doc:rails generates the API under doc/api.
+ gem 'sdoc', require: false
+end
+
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 1.2'
diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock
index 823fac5ff7..888a6b30e2 100644
--- a/guides/code/getting_started/Gemfile.lock
+++ b/guides/code/getting_started/Gemfile.lock
@@ -92,7 +92,7 @@ GEM
multi_json (~> 1.0)
hike (1.2.1)
i18n (0.6.1)
- jbuilder (1.0.2)
+ jbuilder (1.3.0)
activesupport (>= 3.0.0)
jquery-rails (2.2.0)
railties (>= 3.0, < 5.0)
diff --git a/guides/code/getting_started/app/assets/images/rails.png b/guides/code/getting_started/app/assets/images/rails.png
deleted file mode 100644
index d5edc04e65..0000000000
--- a/guides/code/getting_started/app/assets/images/rails.png
+++ /dev/null
Binary files differ
diff --git a/guides/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js
index 9e83eb5e7e..5a4fbaa370 100644
--- a/guides/code/getting_started/app/assets/javascripts/application.js
+++ b/guides/code/getting_started/app/assets/javascripts/application.js
@@ -7,8 +7,7 @@
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
-// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-// GO AFTER THE REQUIRES BELOW.
+// stub path allows dependency to be excluded from the asset bundle.
//
//= require jquery
//= require jquery_ujs
diff --git a/guides/code/getting_started/config/application.rb b/guides/code/getting_started/config/application.rb
index 526a782b5c..3d7604b659 100644
--- a/guides/code/getting_started/config/application.rb
+++ b/guides/code/getting_started/config/application.rb
@@ -2,8 +2,9 @@ require File.expand_path('../boot', __FILE__)
require 'rails/all'
-# Assets should be precompiled for production (so we don't need the gems loaded then)
-Bundler.require(*Rails.groups(assets: %w(development test)))
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env)
module Blog
class Application < Rails::Application
diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb
index 2d65111004..e7e341c960 100644
--- a/guides/code/getting_started/config/environment.rb
+++ b/guides/code/getting_started/config/environment.rb
@@ -1,5 +1,5 @@
-# Load the rails application.
+# Load the Rails application.
require File.expand_path('../application', __FILE__)
-# Initialize the rails application.
+# Initialize the Rails application.
Blog::Application.initialize!
diff --git a/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb
index 2e37d93799..3b2ca93ab9 100644
--- a/guides/code/getting_started/config/initializers/session_store.rb
+++ b/guides/code/getting_started/config/initializers/session_store.rb
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Blog::Application.config.session_store :encrypted_cookie_store, key: '_blog_session'
+Blog::Application.config.session_store :cookie_store, key: '_blog_session'
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index ab890f202c..ce409868ca 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -22,7 +22,7 @@ end
begin
require 'redcarpet'
-rescue Gem::LoadError
+rescue LoadError
# This can happen if doc:guides is executed in an application.
$stderr.puts('Generating guides requires Redcarpet 2.1.1+.')
$stderr.puts(<<ERROR) if bundler?
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index c3fe5b8799..2eb7ca17a3 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -65,7 +65,7 @@ HTML
# if a bulleted list follows the first item is not rendered
# as a list item, but as a paragraph starting with a plain
# asterisk.
- body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do |m|
+ body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do
css_class = case $1
when 'CAUTION', 'IMPORTANT'
'warning'
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index 802455f612..7db4cf07e7 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -200,7 +200,7 @@ Active Record association proxies now respect the scope of methods on the proxie
* More information:
* [Rails 2.2 Change: Private Methods on Association Proxies are Private](http://afreshcup.com/2008/10/24/rails-22-change-private-methods-on-association-proxies-are-private/)
-### Other ActiveRecord Changes
+### Other Active Record Changes
* `rake db:migrate:redo` now accepts an optional VERSION to target that specific migration to redo
* Set `config.active_record.timestamped_migrations = false` to have migrations with numeric prefix instead of UTC timestamp.
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 7aef566e40..3c08f148cf 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -134,7 +134,7 @@ Rails 2.3 will introduce the notion of _default scopes_ similar to named scopes,
### Batch Processing
-You can now process large numbers of records from an ActiveRecord model with less pressure on memory by using `find_in_batches`:
+You can now process large numbers of records from an Active Record model with less pressure on memory by using `find_in_batches`:
```ruby
Customer.find_in_batches(:conditions => {:active => true}) do |customer_group|
@@ -504,7 +504,7 @@ A lot of folks have adopted the notion of using try() to attempt operations on o
### Swappable Parsers for XMLmini
-The support for XML parsing in ActiveSupport has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed:
+The support for XML parsing in Active Support has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed:
```ruby
XmlMini.backend = 'LibXML'
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 388ba3fa30..d398cd680c 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -79,7 +79,7 @@ Creating a Rails 3.0 application
--------------------------------
```bash
-# You should have the 'rails' rubygem installed
+# You should have the 'rails' RubyGem installed
$ rails new myapp
$ cd myapp
```
@@ -342,7 +342,7 @@ Helpers that do something else, like `cache` or `content_for`, are not affected
* Helpers now output HTML 5 by default.
* Form label helper now pulls values from I18n with a single value, so `f.label :name` will pull the `:name` translation.
* I18n select label on should now be :en.helpers.select instead of :en.support.select.
-* You no longer need to place a minus sign at the end of a ruby interpolation inside an ERb template to remove the trailing carriage return in the HTML output.
+* You no longer need to place a minus sign at the end of a Ruby interpolation inside an ERB template to remove the trailing carriage return in the HTML output.
* Added `grouped_collection_select` helper to Action View.
* `content_for?` has been added allowing you to check for the existence of content in a view before rendering.
* passing `:value => nil` to form helpers will set the field's `value` attribute to nil as opposed to using the default value
@@ -475,7 +475,7 @@ As well as the following deprecations:
* `named_scope` in an Active Record class is deprecated and has been renamed to just `scope`.
* In `scope` methods, you should move to using the relation methods, instead of a `:conditions => {}` finder method, for example `scope :since, lambda {|time| where("created_at > ?", time) }`.
* `save(false)` is deprecated, in favor of `save(:validate => false)`.
-* I18n error messages for ActiveRecord should be changed from :en.activerecord.errors.template to `:en.errors.template`.
+* I18n error messages for Active Record should be changed from :en.activerecord.errors.template to `:en.errors.template`.
* `model.errors.on` is deprecated in favor of `model.errors[]`
* validates_presence_of => validates... :presence => true
* `ActiveRecord::Base.colorize_logging` and `config.active_record.colorize_logging` are deprecated in favor of `Rails::LogSubscriber.colorize_logging` or `config.colorize_logging`
@@ -580,7 +580,7 @@ Action Mailer has been given a new API with TMail being replaced out with the ne
* All mailers are now in `app/mailers` by default.
* Can now send email using new API with three methods: `attachments`, `headers` and `mail`.
-* ActionMailer now has native support for inline attachments using the `attachments.inline` method.
+* Action Mailer now has native support for inline attachments using the `attachments.inline` method.
* Action Mailer emailing methods now return `Mail::Message` objects, which can then be sent the `deliver` message to send itself.
* All delivery methods are now abstracted out to the Mail gem.
* The mail delivery method can accept a hash of all valid mail header fields with their value pair.
@@ -611,4 +611,3 @@ Credits
See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails 3. Kudos to all of them.
Rails 3.0 Release Notes were compiled by [Mikel Lindsaar](http://lindsaar.net.)
-
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index d3f8abe0c8..5c99892e39 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -137,7 +137,7 @@ Creating a Rails 3.1 application
--------------------------------
```bash
-# You should have the 'rails' rubygem installed
+# You should have the 'rails' RubyGem installed
$ rails new myapp
$ cd myapp
```
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index 68a47be14f..e036860de2 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -67,7 +67,7 @@ Creating a Rails 3.2 application
--------------------------------
```bash
-# You should have the 'rails' rubygem installed
+# You should have the 'rails' RubyGem installed
$ rails new myapp
$ cd myapp
```
@@ -101,7 +101,7 @@ Rails 3.2 comes with a development mode that's noticeably faster. Inspired by [A
### Automatic Query Explains
-Rails 3.2 comes with a nice feature that explains queries generated by ARel by defining an `explain` method in `ActiveRecord::Relation`. For example, you can run something like `puts Person.active.limit(5).explain` and the query ARel produces is explained. This allows to check for the proper indexes and further optimizations.
+Rails 3.2 comes with a nice feature that explains queries generated by Arel by defining an `explain` method in `ActiveRecord::Relation`. For example, you can run something like `puts Person.active.limit(5).explain` and the query Arel produces is explained. This allows to check for the proper indexes and further optimizations.
Queries that take more than half a second to run are *automatically* explained in the development mode. This threshold, of course, can be changed.
@@ -189,7 +189,7 @@ Action Pack
* form\_for is changed to use "#{action}\_#{as}" as the css class and id if `:as` option is provided. Earlier versions used "#{as}\_#{action}".
-* `ActionController::ParamsWrapper` on ActiveRecord models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`.
+* `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`.
* Log "Filter chain halted as CALLBACKNAME rendered or redirected" every time a before callback halts.
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 37afb25181..b9dbe820c8 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -22,7 +22,7 @@ Creating a Rails 4.0 application
--------------------------------
```
- You should have the 'rails' rubygem installed
+ You should have the 'rails' RubyGem installed
$ rails new myapp
$ cd myapp
```
@@ -59,15 +59,15 @@ Extraction of features to gems
In Rails 4.0, several features have been extracted into gems. You can simply add the extracted gems to your `Gemfile` to bring the functionality back.
-* Hash-based & Dynamic finder methods ([Github](https://github.com/rails/activerecord-deprecated_finders))
-* Mass assignment protection in Active Record models ([Github](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251))
-* ActiveRecord::SessionStore ([Github](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436))
-* Active Record Observers ([Github](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2))
-* Active Resource ([Github](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource))
-* Action Caching ([Github](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
-* Page Caching ([Github](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
-* Sprockets ([Github](https://github.com/rails/sprockets-rails))
-* Performance tests ([Github](https://github.com/rails/rails-perftest), [Pull Request](https://github.com/rails/rails/pull/8876))
+* Hash-based & Dynamic finder methods ([GitHub](https://github.com/rails/activerecord-deprecated_finders))
+* Mass assignment protection in Active Record models ([GitHub](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251))
+* ActiveRecord::SessionStore ([GitHub](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436))
+* Active Record Observers ([GitHub](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2))
+* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource))
+* Action Caching ([GitHub](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
+* Page Caching ([GitHub](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
+* Sprockets ([GitHub](https://github.com/rails/sprockets-rails))
+* Performance tests ([GitHub](https://github.com/rails/rails-perftest), [Pull Request](https://github.com/rails/rails/pull/8876))
Documentation
-------------
@@ -85,7 +85,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt
* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878))
-* Your app's executables now live in the `bin/` dir. Run `rake rails:update:bin` to get `bin/bundle`, `bin/rails`, and `bin/rake`.
+* Your app's executables now live in the `bin/` directory. Run `rake rails:update:bin` to get `bin/bundle`, `bin/rails`, and `bin/rake`.
* Threadsafe on by default
@@ -113,7 +113,8 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ
* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed.
-* Added `ActiveModel::Model`, a mixin to make Ruby objects work with AP out of box.
+* Added `ActiveModel::Model`, a mixin to make Ruby objects work with
+ Action Pack out of box.
### Deprecations
@@ -142,7 +143,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ
* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols).
-* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby stdlib.
+* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library.
* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?`
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index e01d0d57ea..28939f307f 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -27,6 +27,16 @@ A controller can thus be thought of as a middle man between models and views. It
NOTE: For more details on the routing process, see [Rails Routing from the Outside In](routing.html).
+Controller Naming Convention
+----------------------------
+
+The naming convention of controllers in Rails favors pluralization of the last word in the controller's name, although it is not strictly required (e.g. `ApplicationController`). For example, `ClientsController` is preferable to `ClientController`, `SiteAdminsController` is preferable to `SiteAdminController` or `SitesAdminsController`, and so on.
+
+Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and keeps URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details.
+
+NOTE: The controller naming convention differs from the naming convention of models, which expected to be named in singular form.
+
+
Methods and Actions
-------------------
@@ -39,7 +49,7 @@ class ClientsController < ApplicationController
end
```
-As an example, if a user goes to `/clients/new` in your application to add a new client, Rails will create an instance of `ClientsController` and run the `new` method. Note that the empty method from the example above could work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new `Client`:
+As an example, if a user goes to `/clients/new` in your application to add a new client, Rails will create an instance of `ClientsController` and run the `new` method. Note that the empty method from the example above would work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new `Client`:
```ruby
def new
@@ -113,21 +123,21 @@ To send a hash you include the key name inside the brackets:
</form>
```
-When this form is submitted, the value of `params[:client]` will be `{"name" => "Acme", "phone" => "12345", "address" => {"postcode" => "12345", "city" => "Carrot City"}}`. Note the nested hash in `params[:client][:address]`.
+When this form is submitted, the value of `params[:client]` will be `{ "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }`. Note the nested hash in `params[:client][:address]`.
-Note that the `params` hash is actually an instance of `ActiveSupport::HashWithIndifferentAccess`, which acts like a hash that lets you use symbols and strings interchangeably as keys.
+Note that the `params` hash is actually an instance of `ActiveSupport::HashWithIndifferentAccess`, which acts like a hash but lets you use symbols and strings interchangeably as keys.
### JSON parameters
-If you're writing a web service application, you might find yourself more comfortable on accepting parameters in JSON format. Rails will automatically convert your parameters into `params` hash, which you'll be able to access like you would normally do with form data.
+If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. Rails will automatically convert your parameters into the `params` hash, which you can access as you would normally.
-So for example, if you are sending this JSON parameter:
+So for example, if you are sending this JSON content:
```json
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
```
-You'll get `params[:company]` as `{ :name => "acme", "address" => "123 Carrot Street" }`.
+You'll get `params[:company]` as `{ "name" => "acme", "address" => "123 Carrot Street" }`.
Also, if you've turned on `config.wrap_parameters` in your initializer or calling `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. The parameters will be cloned and wrapped in the key according to your controller's name by default. So the above parameter can be written as:
@@ -138,19 +148,19 @@ Also, if you've turned on `config.wrap_parameters` in your initializer or callin
And assume that you're sending the data to `CompaniesController`, it would then be wrapped in `:company` key like this:
```ruby
-{ :name => "acme", :address => "123 Carrot Street", :company => { :name => "acme", :address => "123 Carrot Street" }}
+{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }
```
You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html)
-NOTE: A support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`
+NOTE: Support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`
### Routing Parameters
The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL:
```ruby
-match '/clients/:status' => 'clients#index', foo: "bar"
+match '/clients/:status' => 'clients#index', foo: 'bar'
```
In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar" just like it was passed in the query string. In the same way `params[:action]` will contain "index".
@@ -173,7 +183,7 @@ If you define `default_url_options` in `ApplicationController`, as in the exampl
### Strong Parameters
-With strong parameters Action Controller parameters are forbidden to
+With strong parameters, Action Controller parameters are forbidden to
be used in Active Model mass assignments until they have been
whitelisted. This means you'll have to make a conscious choice about
which attributes to allow for mass updating and thus prevent
@@ -232,15 +242,15 @@ The permitted scalar types are `String`, `Symbol`, `NilClass`,
`StringIO`, `IO`, `ActionDispatch::Http::UploadedFile` and
`Rack::Test::UploadedFile`.
-To declare that the value in `params+ must be an array of permitted
+To declare that the value in `params` must be an array of permitted
scalar values map the key to an empty array:
```ruby
-params.permit(:id => [])
+params.permit(id: [])
```
-To whitelist an entire hash of parameters, the `permit!+ method can be
-used
+To whitelist an entire hash of parameters, the `permit!` method can be
+used:
```ruby
params.require(:log_entry).permit!
@@ -256,9 +266,9 @@ mass-assigned.
You can also use permit on nested parameters, like:
```ruby
-params.permit(:name, {:emails => []},
- :friends => [ :name,
- { :family => [ :name ], :hobbies => [] }])
+params.permit(:name, { emails: [] },
+ friends: [ :name,
+ { family: [ :name ], hobbies: [] }])
```
This declaration whitelists the `name`, `emails` and `friends`
@@ -273,7 +283,7 @@ to having a `name` (any permitted scalar values allowed, too).
You want to also use the permitted attributes in the `new`
action. This raises the problem that you can't use `require` on the
-root-key because normally it does not exist when calling `new`:
+root key because normally it does not exist when calling `new`:
```ruby
# using `fetch` you can supply a default and use
@@ -281,7 +291,7 @@ root-key because normally it does not exist when calling `new`:
params.fetch(:blog, {}).permit(:title, :author)
```
-`accepts_nested_attributes_for` allows you update and destroy the
+`accepts_nested_attributes_for` allows you to update and destroy
associated records. This is based on the `id` and `_destroy`
parameters:
@@ -291,7 +301,7 @@ params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy]
```
Hashes with integer keys are treated differently and you can declare
-the attributes as if they were direct children. You get this kind of
+the attributes as if they were direct children. You get these kinds of
parameters when you use `accepts_nested_attributes_for` in combination
with a `has_many` association:
@@ -311,7 +321,7 @@ in mind. It is not meant as a silver bullet to handle all your
whitelisting problems. However you can easily mix the API with your
own code to adapt to your situation.
-Imagine a situation where you want to whitelist an attribute
+Imagine a scenario where you want to whitelist an attribute
containing a hash with any keys. Using strong parameters you can't
allow a hash with any keys but you can use a simple assignment to get
the job done:
@@ -558,10 +568,10 @@ end
Note that while for session values you set the key to `nil`, to delete a cookie value you should use `cookies.delete(:key)`.
-Rendering xml and json data
+Rendering XML and JSON data
---------------------------
-ActionController makes it extremely easy to render `xml` or `json` data. If you generate a controller using scaffolding then it would look something like this:
+ActionController makes it extremely easy to render `XML` or `JSON` data. If you've generated a controller using scaffolding, it would look something like this:
```ruby
class UsersController < ApplicationController
@@ -576,7 +586,7 @@ class UsersController < ApplicationController
end
```
-Notice that in the above case code is `render xml: @users` and not `render xml: @users.to_xml`. That is because if the input is not string then rails automatically invokes `to_xml` .
+You may notice in the above code that we're using `render xml: @users`, not `render xml: @users.to_xml`. If the object is not a String, then Rails will automatically invoke `to_xml` for us.
Filters
-------
@@ -599,15 +609,6 @@ class ApplicationController < ActionController::Base
redirect_to new_login_url # halts request cycle
end
end
-
- # The logged_in? method simply returns true if the user is logged
- # in and false otherwise. It does this by "booleanizing" the
- # current_user method we created previously using a double ! operator.
- # Note that this is not common in Ruby and is discouraged unless you
- # really mean to convert something into true or false.
- def logged_in?
- !!current_user
- end
end
```
@@ -788,7 +789,7 @@ Rails comes with two built-in HTTP authentication mechanisms:
HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, `http_basic_authenticate_with`.
```ruby
-class AdminController < ApplicationController
+class AdminsController < ApplicationController
http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end
```
@@ -800,7 +801,7 @@ With this in place, you can create namespaced controllers that inherit from `Adm
HTTP digest authentication is superior to the basic authentication as it does not require the client to send an unencrypted password over the network (though HTTP basic authentication is safe over HTTPS). Using digest authentication with Rails is quite easy and only requires using one method, `authenticate_or_request_with_http_digest`.
```ruby
-class AdminController < ApplicationController
+class AdminsController < ApplicationController
USERS = { "lifo" => "world" }
before_action :authenticate
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index a0d962f9c4..c351339117 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -1,7 +1,9 @@
Action Mailer Basics
====================
-This guide should provide you with all you need to get started in sending and receiving emails from and to your application, and many internals of Action Mailer. It also covers how to test your mailers.
+This guide provides you with all you need to get started in sending and
+receiving emails from and to your application, and many internals of Action
+Mailer. It also covers how to test your mailers.
After reading this guide, you will know:
@@ -9,17 +11,19 @@ After reading this guide, you will know:
* How to generate and edit an Action Mailer class and mailer view.
* How to configure Action Mailer for your environment.
* How to test your Action Mailer classes.
+
--------------------------------------------------------------------------------
Introduction
------------
-Action Mailer allows you to send emails from your application using a mailer model and views. So, in Rails, emails are used by creating mailers that inherit from `ActionMailer::Base` and live in `app/mailers`. Those mailers have associated views that appear alongside controller views in `app/views`.
+Action Mailer allows you to send emails from your application using mailer classes and views. Mailers work very similarly to controllers. They inherit from `ActionMailer::Base` and live in `app/mailers`, and they have associated views that appear in `app/views`.
Sending Emails
--------------
-This section will provide a step-by-step guide to creating a mailer and its views.
+This section will provide a step-by-step guide to creating a mailer and its
+views.
### Walkthrough to Generating a Mailer
@@ -34,10 +38,25 @@ invoke test_unit
create test/mailers/user_mailer_test.rb
```
-So we got the mailer, the views, and the tests.
+As you can see, you can generate mailers just like you use other generators with
+Rails. Mailers are conceptually similar to controllers, and so we get a mailer,
+a directory for views, and a test.
+
+If you didn't want to use a generator, you could create your own file inside of
+app/mailers, just make sure that it inherits from `ActionMailer::Base`:
+
+```ruby
+class MyMailer < ActionMailer::Base
+end
+```
#### Edit the Mailer
+Mailers are very similar to Rails controllers. They also have methods called
+"actions" and use views to structure the content. Where a controller generates
+content like HTML to send back to the client, a Mailer creates a message to be
+delivered via email.
+
`app/mailers/user_mailer.rb` contains an empty mailer:
```ruby
@@ -46,7 +65,8 @@ class UserMailer < ActionMailer::Base
end
```
-Let's add a method called `welcome_email`, that will send an email to the user's registered email address:
+Let's add a method called `welcome_email`, that will send an email to the user's
+registered email address:
```ruby
class UserMailer < ActionMailer::Base
@@ -55,21 +75,25 @@ class UserMailer < ActionMailer::Base
def welcome_email(user)
@user = user
@url = 'http://example.com/login'
- mail(to: user.email, subject: 'Welcome to My Awesome Site')
+ mail(to: @user.email, subject: 'Welcome to My Awesome Site')
end
end
```
-Here is a quick explanation of the items presented in the preceding method. For a full list of all available options, please have a look further down at the Complete List of Action Mailer user-settable attributes section.
+Here is a quick explanation of the items presented in the preceding method. For
+a full list of all available options, please have a look further down at the
+Complete List of Action Mailer user-settable attributes section.
-* `default Hash` - This is a hash of default values for any email you send, in this case we are setting the `:from` header to a value for all messages in this class, this can be overridden on a per email basis
+* `default Hash` - This is a hash of default values for any email you send from this mailer. In this case we are setting the `:from` header to a value for all messages in this class. This can be overridden on a per-email basis.
* `mail` - The actual email message, we are passing the `:to` and `:subject` headers in.
-Just like controllers, any instance variables we define in the method become available for use in the views.
+Just like controllers, any instance variables we define in the method become
+available for use in the views.
#### Create a Mailer View
-Create a file called `welcome_email.html.erb` in `app/views/user_mailer/`. This will be the template used for the email, formatted in HTML:
+Create a file called `welcome_email.html.erb` in `app/views/user_mailer/`. This
+will be the template used for the email, formatted in HTML:
```html+erb
<!DOCTYPE html>
@@ -91,7 +115,9 @@ Create a file called `welcome_email.html.erb` in `app/views/user_mailer/`. This
</html>
```
-It is also a good idea to make a text part for this email. To do this, create a file called `welcome_email.text.erb` in `app/views/user_mailer/`:
+Let's also make a text part for this email. Not all clients prefer HTML emails,
+and so sending both is best practice. To do this, create a file called
+`welcome_email.text.erb` in `app/views/user_mailer/`:
```erb
Welcome to example.com, <%= @user.name %>
@@ -105,22 +131,29 @@ To login to the site, just follow this link: <%= @url %>.
Thanks for joining and have a great day!
```
-When you call the `mail` method now, Action Mailer will detect the two templates (text and HTML) and automatically generate a `multipart/alternative` email.
+When you call the `mail` method now, Action Mailer will detect the two templates
+(text and HTML) and automatically generate a `multipart/alternative` email.
-#### Wire It Up So That the System Sends the Email When a User Signs Up
+#### Calling the Mailer
-There are several ways to do this, some people create Rails Observers to fire off emails, others do it inside of the User Model. However, mailers are really just another way to render a view. Instead of rendering a view and sending out the HTTP protocol, they are just sending it out through the Email protocols instead. Due to this, it makes sense to just have your controller tell the mailer to send an email when a user is successfully created.
+Mailers are really just another way to render a view. Instead of rendering a
+view and sending out the HTTP protocol, they are just sending it out through the
+Email protocols instead. Due to this, it makes sense to just have your
+controller tell the Mailer to send an email when a user is successfully created.
Setting this up is painfully simple.
-First off, we need to create a simple `User` scaffold:
+First, let's create a simple `User` scaffold:
```bash
$ rails generate scaffold user name email login
$ rake db:migrate
```
-Now that we have a user model to play with, we will just edit the `app/controllers/users_controller.rb` make it instruct the UserMailer to deliver an email to the newly created user by editing the create action and inserting a call to `UserMailer.welcome_email` right after the user is successfully saved:
+Now that we have a user model to play with, we will just edit the
+`app/controllers/users_controller.rb` make it instruct the UserMailer to deliver
+an email to the newly created user by editing the create action and inserting a
+call to `UserMailer.welcome_email` right after the user is successfully saved:
```ruby
class UsersController < ApplicationController
@@ -145,63 +178,50 @@ class UsersController < ApplicationController
end
```
-This provides a much simpler implementation that does not require the registering of observers and the like.
-
-The method `welcome_email` returns a `Mail::Message` object which can then just be told `deliver` to send itself out.
+The method `welcome_email` returns a `Mail::Message` object which can then just
+be told `deliver` to send itself out.
### Auto encoding header values
-Action Mailer now handles the auto encoding of multibyte characters inside of headers and bodies.
-
-If you are using UTF-8 as your character set, you do not have to do anything special, just go ahead and send in UTF-8 data to the address fields, subject, keywords, filenames or body of the email and Action Mailer will auto encode it into quoted printable for you in the case of a header field or Base64 encode any body parts that are non US-ASCII.
+Action Mailer handles the auto encoding of multibyte characters inside of
+headers and bodies.
-For more complex examples such as defining alternate character sets or self-encoding text first, please refer to the Mail library.
+For more complex examples such as defining alternate character sets or
+self-encoding text first, please refer to the
+[Mail](https://github.com/mikel/mail) library.
### Complete List of Action Mailer Methods
-There are just three methods that you need to send pretty much any email message:
-
-* `headers` - Specifies any header on the email you want. You can pass a hash of header field names and value pairs, or you can call `headers[:field_name] = 'value'`.
-* `attachments` - Allows you to add attachments to your email. For example, `attachments['file-name.jpg'] = File.read('file-name.jpg')`.
-* `mail` - Sends the actual email itself. You can pass in headers as a hash to the mail method as a parameter, mail will then create an email, either plain text, or multipart, depending on what email templates you have defined.
+There are just three methods that you need to send pretty much any email
+message:
-#### Custom Headers
-
-Defining custom headers are simple, you can do it one of three ways:
-
-* Defining a header field as a parameter to the `mail` method:
-
- ```ruby
- mail('X-Spam' => value)
- ```
-
-* Passing in a key value assignment to the `headers` method:
-
- ```ruby
- headers['X-Spam'] = value
- ```
-
-* Passing a hash of key value pairs to the `headers` method:
-
- ```ruby
- headers {'X-Spam' => value, 'X-Special' => another_value}
- ```
-
-TIP: All `X-Value` headers per the RFC2822 can appear more than once. If you want to delete an `X-Value` header, you need to assign it a value of `nil`.
+* `headers` - Specifies any header on the email you want. You can pass a hash of
+ header field names and value pairs, or you can call `headers[:field_name] =
+ 'value'`.
+* `attachments` - Allows you to add attachments to your email. For example,
+ `attachments['file-name.jpg'] = File.read('file-name.jpg')`.
+* `mail` - Sends the actual email itself. You can pass in headers as a hash to
+ the mail method as a parameter, mail will then create an email, either plain
+ text, or multipart, depending on what email templates you have defined.
#### Adding Attachments
-Adding attachments has been simplified in Action Mailer 3.0.
+Action Mailer makes it very easy to add attachments.
-* Pass the file name and content and Action Mailer and the Mail gem will automatically guess the mime_type, set the encoding and create the attachment.
+* Pass the file name and content and Action Mailer and the
+ [Mail gem](https://github.com/mikel/mail) will automatically guess the
+ mime_type, set the encoding and create the attachment.
```ruby
attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
```
-NOTE: Mail will automatically Base64 encode an attachment. If you want something different, pre-encode your content and pass in the encoded content and encoding in a `Hash` to the `attachments` method.
+NOTE: Mail will automatically Base64 encode an attachment. If you want something
+different, encode your content and pass in the encoded content and encoding in a
+`Hash` to the `attachments` method.
-* Pass the file name and specify headers and content and Action Mailer and Mail will use the settings you pass in.
+* Pass the file name and specify headers and content and Action Mailer and Mail
+ will use the settings you pass in.
```ruby
encoded_content = SpecialEncode(File.read('/path/to/filename.jpg'))
@@ -210,13 +230,14 @@ NOTE: Mail will automatically Base64 encode an attachment. If you want something
content: encoded_content }
```
-NOTE: If you specify an encoding, Mail will assume that your content is already encoded and not try to Base64 encode it.
+NOTE: If you specify an encoding, Mail will assume that your content is already
+encoded and not try to Base64 encode it.
#### Making Inline Attachments
Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in pre 3.0 versions, much simpler and trivial as they should be.
-* Firstly, to tell Mail to turn an attachment into an inline attachment, you just call `#inline` on the attachments method within your Mailer:
+* First, to tell Mail to turn an attachment into an inline attachment, you just call `#inline` on the attachments method within your Mailer:
```ruby
def welcome
@@ -224,7 +245,9 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p
end
```
-* Then in your view, you can just reference `attachments[]` as a hash and specify which attachment you want to show, calling `url` on it and then passing the result into the `image_tag` method:
+* Then in your view, you can just reference `attachments` as a hash and specify
+ which attachment you want to show, calling `url` on it and then passing the
+ result into the `image_tag` method:
```html+erb
<p>Hello there, this is our image</p>
@@ -232,7 +255,8 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p
<%= image_tag attachments['image.jpg'].url %>
```
-* As this is a standard call to `image_tag` you can pass in an options hash after the attachment URL as you could for any other image:
+* As this is a standard call to `image_tag` you can pass in an options hash
+ after the attachment URL as you could for any other image:
```html+erb
<p>Hello there, this is our image</p>
@@ -243,7 +267,10 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p
#### Sending Email To Multiple Recipients
-It is possible to send email to one or more recipients in one email (e.g., informing all admins of a new signup) by setting the list of emails to the `:to` key. The list of emails can be an array of email addresses or a single string with the addresses separated by commas.
+It is possible to send email to one or more recipients in one email (e.g.,
+informing all admins of a new signup) by setting the list of emails to the `:to`
+key. The list of emails can be an array of email addresses or a single string
+with the addresses separated by commas.
```ruby
class AdminMailer < ActionMailer::Base
@@ -257,12 +284,14 @@ class AdminMailer < ActionMailer::Base
end
```
-The same format can be used to set carbon copy (Cc:) and blind carbon copy (Bcc:) recipients, by using the `:cc` and `:bcc` keys respectively.
+The same format can be used to set carbon copy (Cc:) and blind carbon copy
+(Bcc:) recipients, by using the `:cc` and `:bcc` keys respectively.
#### Sending Email With Name
-Sometimes you wish to show the name of the person instead of just their email address when they receive the email. The trick to doing that is
-to format the email address in the format `"Name <email>"`.
+Sometimes you wish to show the name of the person instead of just their email
+address when they receive the email. The trick to doing that is to format the
+email address in the format `"Full Name <email>"`.
```ruby
def welcome_email(user)
@@ -274,7 +303,11 @@ end
### Mailer Views
-Mailer views are located in the `app/views/name_of_mailer_class` directory. The specific mailer view is known to the class because its name is the same as the mailer method. In our example from above, our mailer view for the `welcome_email` method will be in `app/views/user_mailer/welcome_email.html.erb` for the HTML version and `welcome_email.text.erb` for the plain text version.
+Mailer views are located in the `app/views/name_of_mailer_class` directory. The
+specific mailer view is known to the class because its name is the same as the
+mailer method. In our example from above, our mailer view for the
+`welcome_email` method will be in `app/views/user_mailer/welcome_email.html.erb`
+for the HTML version and `welcome_email.text.erb` for the plain text version.
To change the default mailer view for your action you do something like:
@@ -285,7 +318,7 @@ class UserMailer < ActionMailer::Base
def welcome_email(user)
@user = user
@url = 'http://example.com/login'
- mail(to: user.email,
+ mail(to: @user.email,
subject: 'Welcome to My Awesome Site',
template_path: 'notifications',
template_name: 'another')
@@ -293,9 +326,12 @@ class UserMailer < ActionMailer::Base
end
```
-In this case it will look for templates at `app/views/notifications` with name `another`. You can also specify an array of paths for `template_path`, and they will be searched in order.
+In this case it will look for templates at `app/views/notifications` with name
+`another`. You can also specify an array of paths for `template_path`, and they
+will be searched in order.
-If you want more flexibility you can also pass a block and render specific templates or even render inline or text without using a template file:
+If you want more flexibility you can also pass a block and render specific
+templates or even render inline or text without using a template file:
```ruby
class UserMailer < ActionMailer::Base
@@ -304,23 +340,28 @@ class UserMailer < ActionMailer::Base
def welcome_email(user)
@user = user
@url = 'http://example.com/login'
- mail(to: user.email,
+ mail(to: @user.email,
subject: 'Welcome to My Awesome Site') do |format|
format.html { render 'another_template' }
format.text { render text: 'Render text' }
end
end
-
end
```
-This will render the template 'another_template.html.erb' for the HTML part and use the rendered text for the text part. The render command is the same one used inside of Action Controller, so you can use all the same options, such as `:text`, `:inline` etc.
+This will render the template 'another_template.html.erb' for the HTML part and
+use the rendered text for the text part. The render command is the same one used
+inside of Action Controller, so you can use all the same options, such as
+`:text`, `:inline` etc.
### Action Mailer Layouts
-Just like controller views, you can also have mailer layouts. The layout name needs to be the same as your mailer, such as `user_mailer.html.erb` and `user_mailer.text.erb` to be automatically recognized by your mailer as a layout.
+Just like controller views, you can also have mailer layouts. The layout name
+needs to be the same as your mailer, such as `user_mailer.html.erb` and
+`user_mailer.text.erb` to be automatically recognized by your mailer as a
+layout.
-In order to use a different file just use:
+In order to use a different file, call `layout` in your mailer:
```ruby
class UserMailer < ActionMailer::Base
@@ -328,9 +369,11 @@ class UserMailer < ActionMailer::Base
end
```
-Just like with controller views, use `yield` to render the view inside the layout.
+Just like with controller views, use `yield` to render the view inside the
+layout.
-You can also pass in a `layout: 'layout_name'` option to the render call inside the format block to specify different layouts for different actions:
+You can also pass in a `layout: 'layout_name'` option to the render call inside
+the format block to specify different layouts for different actions:
```ruby
class UserMailer < ActionMailer::Base
@@ -343,13 +386,37 @@ class UserMailer < ActionMailer::Base
end
```
-Will render the HTML part using the `my_layout.html.erb` file and the text part with the usual `user_mailer.text.erb` file if it exists.
+Will render the HTML part using the `my_layout.html.erb` file and the text part
+with the usual `user_mailer.text.erb` file if it exists.
### Generating URLs in Action Mailer Views
-URLs can be generated in mailer views using `url_for` or named routes.
+Unlike controllers, the mailer instance doesn't have any context about the
+incoming request so you'll need to provide the `:host` parameter yourself.
+
+As the `:host` usually is consistent across the application you can configure it
+globally in `config/application.rb`:
+
+```ruby
+config.action_mailer.default_url_options = { host: 'example.com' }
+```
+
+#### generating URLs with `url_for`
+
+You need to pass the `only_path: false` option when using `url_for`. This will
+ensure that absolute URLs are generated because the `url_for` view helper will,
+by default, generate relative URLs when a `:host` option isn't explicitly
+provided.
+
+```erb
+<%= url_for(controller: 'welcome',
+ action: 'greeting',
+ only_path: false) %>
+```
+
+If you did not configure the `:host` option globally make sure to pass it to
+`url_for`.
-Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the `:host`, `:controller`, and `:action`:
```erb
<%= url_for(host: 'example.com',
@@ -357,27 +424,32 @@ Unlike controllers, the mailer instance doesn't have any context about the incom
action: 'greeting') %>
```
-When using named routes you only need to supply the `:host`:
+NOTE: When you explicitly pass the `:host` Rails will always generate absolute
+URLs, so there is no need to pass `only_path: false`.
-```erb
-<%= user_url(@user, host: 'example.com') %>
-```
+#### generating URLs with named routes
-Email clients have no web context and so paths have no base URL to form complete web addresses. Thus, when using named routes only the "_url" variant makes sense.
+Email clients have no web context and so paths have no base URL to form complete
+web addresses. Thus, you should always use the "_url" variant of named route
+helpers.
-It is also possible to set a default host that will be used in all mailers by setting the `:host` option as a configuration option in `config/application.rb`:
+If you did not configure the `:host` option globally make sure to pass it to the
+url helper.
-```ruby
-config.action_mailer.default_url_options = { host: 'example.com' }
+```erb
+<%= user_url(@user, host: 'example.com') %>
```
-If you use this setting, you should pass the `only_path: false` option when using `url_for`. This will ensure that absolute URLs are generated because the `url_for` view helper will, by default, generate relative URLs when a `:host` option isn't explicitly provided.
-
### Sending Multipart Emails
-Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have `welcome_email.text.erb` and `welcome_email.html.erb` in `app/views/user_mailer`, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts.
+Action Mailer will automatically send multipart emails if you have different
+templates for the same action. So, for our UserMailer example, if you have
+`welcome_email.text.erb` and `welcome_email.html.erb` in
+`app/views/user_mailer`, Action Mailer will automatically send a multipart email
+with the HTML and text versions setup as different parts.
-The order of the parts getting inserted is determined by the `:parts_order` inside of the `ActionMailer::Base.default` method.
+The order of the parts getting inserted is determined by the `:parts_order`
+inside of the `ActionMailer::Base.default` method.
### Sending Emails with Attachments
@@ -389,38 +461,51 @@ class UserMailer < ActionMailer::Base
@user = user
@url = user_url(@user)
attachments['terms.pdf'] = File.read('/path/terms.pdf')
- mail(to: user.email,
+ mail(to: @user.email,
subject: 'Please see the Terms and Conditions attached')
end
end
```
-The above will send a multipart email with an attachment, properly nested with the top level being `multipart/mixed` and the first part being a `multipart/alternative` containing the plain text and HTML email messages.
+The above will send a multipart email with an attachment, properly nested with
+the top level being `multipart/mixed` and the first part being a
+`multipart/alternative` containing the plain text and HTML email messages.
### Sending Emails with Dynamic Delivery Options
-If you wish to override the default delivery options (e.g. SMTP credentials) while delivering emails, you can do this using `delivery_method_options` in the mailer action.
+If you wish to override the default delivery options (e.g. SMTP credentials)
+while delivering emails, you can do this using `delivery_method_options` in the
+mailer action.
```ruby
class UserMailer < ActionMailer::Base
def welcome_email(user, company)
@user = user
@url = user_url(@user)
- delivery_options = { user_name: company.smtp_user, password: company.smtp_password, address: company.smtp_host }
- mail(to: user.email, subject: "Please see the Terms and Conditions attached", delivery_method_options: delivery_options)
+ delivery_options = { user_name: company.smtp_user,
+ password: company.smtp_password,
+ address: company.smtp_host }
+ mail(to: @user.email,
+ subject: "Please see the Terms and Conditions attached",
+ delivery_method_options: delivery_options)
end
end
```
### Sending Emails without Template Rendering
-There may be cases in which you want to skip the template rendering step and supply the email body as a string. You can achieve this using the `:body` option.
-In such cases don't forget to add the `:content_type` option. Rails will default to `text/plain` otherwise.
+There may be cases in which you want to skip the template rendering step and
+supply the email body as a string. You can achieve this using the `:body`
+option. In such cases don't forget to add the `:content_type` option. Rails
+will default to `text/plain` otherwise.
```ruby
class UserMailer < ActionMailer::Base
def welcome_email(user, email_body)
- mail(to: user.email, body: email_body, content_type: "text/html", subject: "Already rendered!")
+ mail(to: user.email,
+ body: email_body,
+ content_type: "text/html",
+ subject: "Already rendered!")
end
end
```
@@ -428,13 +513,21 @@ end
Receiving Emails
----------------
-Receiving and parsing emails with Action Mailer can be a rather complex endeavor. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need to:
+Receiving and parsing emails with Action Mailer can be a rather complex
+endeavor. Before your email reaches your Rails app, you would have had to
+configure your system to somehow forward emails to your app, which needs to be
+listening for that. So, to receive emails in your Rails app you'll need to:
* Implement a `receive` method in your mailer.
-* Configure your email server to forward emails from the address(es) you would like your app to receive to `/path/to/app/bin/rails runner 'UserMailer.receive(STDIN.read)'`.
+* Configure your email server to forward emails from the address(es) you would
+ like your app to receive to `/path/to/app/bin/rails runner
+ 'UserMailer.receive(STDIN.read)'`.
-Once a method called `receive` is defined in any mailer, Action Mailer will parse the raw incoming email into an email object, decode it, instantiate a new mailer, and pass the email object to the mailer `receive` instance method. Here's an example:
+Once a method called `receive` is defined in any mailer, Action Mailer will
+parse the raw incoming email into an email object, decode it, instantiate a new
+mailer, and pass the email object to the mailer `receive` instance
+method. Here's an example:
```ruby
class UserMailer < ActionMailer::Base
@@ -460,17 +553,23 @@ end
Action Mailer Callbacks
---------------------------
-Action Mailer allows for you to specify a `before_action`, `after_action` and `around_action`.
+Action Mailer allows for you to specify a `before_action`, `after_action` and
+`around_action`.
-* Filters can be specified with a block or a symbol to a method in the mailer class similar to controllers.
+* Filters can be specified with a block or a symbol to a method in the mailer
+ class similar to controllers.
-* You could use a `before_action` to prepopulate the mail object with defaults, delivery_method_options or insert default headers and attachments.
+* You could use a `before_action` to populate the mail object with defaults,
+ delivery_method_options or insert default headers and attachments.
-* You could use an `after_action` to do similar setup as a `before_action` but using instance variables set in your mailer action.
+* You could use an `after_action` to do similar setup as a `before_action` but
+ using instance variables set in your mailer action.
```ruby
class UserMailer < ActionMailer::Base
- after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers
+ after_action :set_delivery_options,
+ :prevent_delivery_to_guests,
+ :set_business_headers
def feedback_message(business, user)
@business = business
@@ -486,7 +585,8 @@ class UserMailer < ActionMailer::Base
private
def set_delivery_options
- # You have access to the mail instance and @business and @user instance variables here
+ # You have access to the mail instance,
+ # @business and @user instance variables here
if @business && @business.has_smtp_settings?
mail.delivery_method.settings.merge!(@business.smtp_settings)
end
@@ -511,12 +611,14 @@ end
Using Action Mailer Helpers
---------------------------
-Action Mailer now just inherits from Abstract Controller, so you have access to the same generic helpers as you do in Action Controller.
+Action Mailer now just inherits from `AbstractController`, so you have access to
+the same generic helpers as you do in Action Controller.
Action Mailer Configuration
---------------------------
-The following configuration options are best made in one of the environment files (environment.rb, production.rb, etc...)
+The following configuration options are best made in one of the environment
+files (environment.rb, production.rb, etc...)
| Configuration | Description |
|---------------|-------------|
@@ -529,9 +631,14 @@ The following configuration options are best made in one of the environment file
|`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.|
|`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).|
+For a complete writeup of possible configurations see the
+[Action Mailer section](configuring.html#configuring-action-mailer) in
+our Configuring Rails Applications guide.
+
### Example Action Mailer Configuration
-An example would be adding the following to your appropriate `config/environments/$RAILS_ENV.rb` file:
+An example would be adding the following to your appropriate
+`config/environments/$RAILS_ENV.rb` file:
```ruby
config.action_mailer.delivery_method = :sendmail
@@ -542,19 +649,20 @@ config.action_mailer.delivery_method = :sendmail
# }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
-config.action_mailer.default_options = {from: 'no-replay@example.org'}
+config.action_mailer.default_options = {from: 'no-replay@example.com'}
```
-### Action Mailer Configuration for GMail
+### Action Mailer Configuration for Gmail
-As Action Mailer now uses the Mail gem, this becomes as simple as adding to your `config/environments/$RAILS_ENV.rb` file:
+As Action Mailer now uses the Mail gem, this becomes as simple as adding to your
+`config/environments/$RAILS_ENV.rb` file:
```ruby
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
- domain: 'baci.lindsaar.net',
+ domain: 'example.com',
user_name: '<username>',
password: '<password>',
authentication: 'plain',
@@ -564,12 +672,15 @@ config.action_mailer.smtp_settings = {
Mailer Testing
--------------
-You can find detailed instructions on how to test your mailers in our
+You can find detailed instructions on how to test your mailers in the
[testing guide](testing.html#testing-your-mailers).
Intercepting Emails
-------------------
-There are situations where you need to edit an email before it's delivered. Fortunately Action Mailer provides hooks to intercept every email. You can register an interceptor to make modifications to mail messages right before they are handed to the delivery agents.
+There are situations where you need to edit an email before it's
+delivered. Fortunately Action Mailer provides hooks to intercept every
+email. You can register an interceptor to make modifications to mail messages
+right before they are handed to the delivery agents.
```ruby
class SandboxEmailInterceptor
@@ -579,10 +690,15 @@ class SandboxEmailInterceptor
end
```
-Before the interceptor can do its job you need to register it with the Action Mailer framework. You can do this in an initializer file `config/initializers/sandbox_email_interceptor.rb`
+Before the interceptor can do its job you need to register it with the Action
+Mailer framework. You can do this in an initializer file
+`config/initializers/sandbox_email_interceptor.rb`
```ruby
ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging?
```
-NOTE: The example above uses a custom environment called "staging" for a production like server but for testing purposes. You can read [Creating Rails environments](./configuring.html#creating-rails-environments) for more information about custom Rails environments.
+NOTE: The example above uses a custom environment called "staging" for a
+production like server but for testing purposes. You can read
+[Creating Rails environments](./configuring.html#creating-rails-environments)
+for more information about custom Rails environments.
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 4cdac43a7e..dea1ddef71 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -172,7 +172,7 @@ That code will pull in the partial from `app/views/shared/_menu.html.erb`.
#### Using Partials to simplify Views
-One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looked like this:
+One way to use partials is to treat them as the equivalent of subroutines; a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looks like this:
```html+erb
<%= render "shared/ad_banner" %>
@@ -269,12 +269,7 @@ Rails will render the `_product_ruler` partial (with no data passed to it) betwe
### Layouts
-TODO...
-
-Using Templates, Partials and Layouts "The Rails Way"
---------------------------------------------------------
-
-TODO...
+Layouts can be used to render a common view template around the results of Rails controller actions. Typically, every Rails has a couple of overall layouts that most pages are rendered within. For example, a site might have a layout for a logged in user, and a layout for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us." You would expect each layout to have a different look and feel. You can read more details about Layouts in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
Partial Layouts
---------------
@@ -492,7 +487,7 @@ image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png
#### image_url
-Computes the url to an image asset in the `app/asset/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
+Computes the url to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
```ruby
image_url("edit.png") # => http://www.example.com/assets/edit.png
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 68ac26c681..1d87646e49 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -14,7 +14,7 @@ Active Model is a library containing various modules used in developing framewor
### AttributeMethods
-The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes, which methods on the object will use them.
+The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes and which methods on the object will use them.
```ruby
class Person
@@ -45,7 +45,7 @@ person.age_highest? # false
### Callbacks
-Callbacks gives Active Record style callbacks. This provides the ability to define the callbacks and those will run at appropriate time. After defining a callbacks you can wrap with before, after and around custom methods.
+Callbacks gives Active Record style callbacks. This provides an ability to define callbacks which run at appropriate times. After defining callbacks, you can wrap them with before, after and around custom methods.
```ruby
class Person
@@ -57,19 +57,19 @@ class Person
def update
run_callbacks(:update) do
- # This will call when we are trying to call update on object.
+ # This method is called when update is called on an object.
end
end
def reset_me
- # This method will call when you are calling update on object as a before_update callback as defined.
+ # This method is called when update is called on an object as a before_update callback is defined.
end
end
```
### Conversion
-If a class defines `persisted?` and `id` methods then you can include `Conversion` module in that class and you can able to call Rails conversion methods to objects of that class.
+If a class defines `persisted?` and `id` methods, then you can include the `Conversion` module in that class and call the Rails conversion methods on objects of that class.
```ruby
class Person
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 69d7333e6f..fc8fac4651 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -1,14 +1,14 @@
Active Record Basics
====================
-
+
This guide is an introduction to Active Record.
After reading this guide, you will know:
-* What Object Relational Mapping and Active Record are and how they are used in
+* What Object Relational Mapping and Active Record are and how they are used in
Rails.
* How Active Record fits into the Model-View-Controller paradigm.
-* How to use Active Record models to manipulate data stored in a relational
+* How to use Active Record models to manipulate data stored in a relational
database.
* Active Record schema naming conventions.
* The concepts of database migrations, validations and callbacks.
@@ -18,33 +18,34 @@ After reading this guide, you will know:
What is Active Record?
----------------------
-Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the
-model - which is the layer of the system responsible for representing business
-data and logic. Active Record facilitates the creation and use of business
-objects whose data requires persistent storage to a database. It is an
-implementation of the Active Record pattern which itself is a description of an
+Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the
+model - which is the layer of the system responsible for representing business
+data and logic. Active Record facilitates the creation and use of business
+objects whose data requires persistent storage to a database. It is an
+implementation of the Active Record pattern which itself is a description of an
Object Relational Mapping system.
### The Active Record Pattern
-Active Record was described by Martin Fowler in his book _Patterns of Enterprise
-Application Architecture_. In Active Record, objects carry both persistent data
-and behavior which operates on that data. Active Record takes the opinion that
-ensuring data access logic is part of the object will educate users of that
+[Active Record was described by Martin Fowler](http://www.martinfowler.com/eaaCatalog/activeRecord.html)
+in his book _Patterns of Enterprise Application Architecture_. In
+Active Record, objects carry both persistent data and behavior which
+operates on that data. Active Record takes the opinion that ensuring
+data access logic is part of the object will educate users of that
object on how to write to and read from the database.
### Object Relational Mapping
-Object-Relational Mapping, commonly referred to as its abbreviation ORM, is
-a technique that connects the rich objects of an application to tables in
-a relational database management system. Using ORM, the properties and
-relationships of the objects in an application can be easily stored and
-retrieved from a database without writing SQL statements directly and with less
+Object-Relational Mapping, commonly referred to as its abbreviation ORM, is
+a technique that connects the rich objects of an application to tables in
+a relational database management system. Using ORM, the properties and
+relationships of the objects in an application can be easily stored and
+retrieved from a database without writing SQL statements directly and with less
overall database access code.
### Active Record as an ORM Framework
-Active Record gives us several mechanisms, the most important being the ability
+Active Record gives us several mechanisms, the most important being the ability
to:
* Represent models and their data
@@ -56,29 +57,29 @@ to:
Convention over Configuration in Active Record
----------------------------------------------
-When writing applications using other programming languages or frameworks, it
-may be necessary to write a lot of configuration code. This is particularly true
-for ORM frameworks in general. However, if you follow the conventions adopted by
-Rails, you'll need to write very little configuration (in some case no
-configuration at all) when creating Active Record models. The idea is that if
-you configure your applications in the very same way most of the times then this
-should be the default way. In this cases, explicit configuration would be needed
+When writing applications using other programming languages or frameworks, it
+may be necessary to write a lot of configuration code. This is particularly true
+for ORM frameworks in general. However, if you follow the conventions adopted by
+Rails, you'll need to write very little configuration (in some case no
+configuration at all) when creating Active Record models. The idea is that if
+you configure your applications in the very same way most of the times then this
+should be the default way. In this cases, explicit configuration would be needed
only in those cases where you can't follow the conventions for any reason.
### Naming Conventions
-By default, Active Record uses some naming conventions to find out how the
-mapping between models and database tables should be created. Rails will
-pluralize your class names to find the respective database table. So, for
-a class `Book`, you should have a database table called **books**. The Rails
-pluralization mechanisms are very powerful, being capable to pluralize (and
-singularize) both regular and irregular words. When using class names composed
-of two or more words, the model class name should follow the Ruby conventions,
-using the CamelCase form, while the table name must contain the words separated
+By default, Active Record uses some naming conventions to find out how the
+mapping between models and database tables should be created. Rails will
+pluralize your class names to find the respective database table. So, for
+a class `Book`, you should have a database table called **books**. The Rails
+pluralization mechanisms are very powerful, being capable to pluralize (and
+singularize) both regular and irregular words. When using class names composed
+of two or more words, the model class name should follow the Ruby conventions,
+using the CamelCase form, while the table name must contain the words separated
by underscores. Examples:
* Database Table - Plural with underscores separating words (e.g., `book_clubs`)
-* Model Class - Singular with the first letter of each word capitalized (e.g.,
+* Model Class - Singular with the first letter of each word capitalized (e.g.,
`BookClub`)
| Model / Class | Table / Schema |
@@ -92,33 +93,35 @@ by underscores. Examples:
### Schema Conventions
-Active Record uses naming conventions for the columns in database tables,
+Active Record uses naming conventions for the columns in database tables,
depending on the purpose of these columns.
-* **Foreign keys** - These fields should be named following the pattern
- `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the
- fields that Active Record will look for when you create associations between
+* **Foreign keys** - These fields should be named following the pattern
+ `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the
+ fields that Active Record will look for when you create associations between
your models.
-* **Primary keys** - By default, Active Record will use an integer column named
- `id` as the table's primary key. When using [Rails
- Migrations](migrations.html) to create your tables, this column will be
+* **Primary keys** - By default, Active Record will use an integer column named
+ `id` as the table's primary key. When using [Rails
+ Migrations](migrations.html) to create your tables, this column will be
automatically created.
-There are also some optional column names that will create additional features
+There are also some optional column names that will create additional features
to Active Record instances:
-* `created_at` - Automatically gets set to the current date and time when the
+* `created_at` - Automatically gets set to the current date and time when the
record is first created.
-* `updated_at` - Automatically gets set to the current date and time whenever
+* `updated_at` - Automatically gets set to the current date and time whenever
the record is updated.
-* `lock_version` - Adds [optimistic
- locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to
+* `lock_version` - Adds [optimistic
+ locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to
a model.
-* `type` - Specifies that the model uses [Single Table
+* `type` - Specifies that the model uses [Single Table
Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
-* `(table_name)_count` - Used to cache the number of belonging objects on
- associations. For example, a `comments_count` column in a `Post` class that
- has many instances of `Comment` will cache the number of existent comments
+* `(association_name)_type` - Stores the type for
+ [polymorphic associations](association_basics.html#polymorphic-associations).
+* `(table_name)_count` - Used to cache the number of belonging objects on
+ associations. For example, a `comments_count` column in a `Post` class that
+ has many instances of `Comment` will cache the number of existent comments
for each post.
NOTE: While these column names are optional, they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, `type` is a reserved keyword used to designate a table using Single Table Inheritance (STI). If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling.
@@ -126,7 +129,7 @@ NOTE: While these column names are optional, they are in fact reserved by Active
Creating Active Record Models
-----------------------------
-It is very easy to create Active Record models. All you have to do is to
+It is very easy to create Active Record models. All you have to do is to
subclass the `ActiveRecord::Base` class and you're good to go:
```ruby
@@ -134,9 +137,9 @@ class Product < ActiveRecord::Base
end
```
-This will create a `Product` model, mapped to a `products` table at the
-database. By doing this you'll also have the ability to map the columns of each
-row in that table with the attributes of the instances of your model. Suppose
+This will create a `Product` model, mapped to a `products` table at the
+database. By doing this you'll also have the ability to map the columns of each
+row in that table with the attributes of the instances of your model. Suppose
that the `products` table was created using an SQL sentence like:
```sql
@@ -147,7 +150,7 @@ CREATE TABLE products (
);
```
-Following the table schema above, you would be able to write code like the
+Following the table schema above, you would be able to write code like the
following:
```ruby
@@ -159,11 +162,11 @@ puts p.name # "Some Book"
Overriding the Naming Conventions
---------------------------------
-What if you need to follow a different naming convention or need to use your
-Rails application with a legacy database? No problem, you can easily override
+What if you need to follow a different naming convention or need to use your
+Rails application with a legacy database? No problem, you can easily override
the default conventions.
-You can use the `ActiveRecord::Base.table_name=` method to specify the table
+You can use the `ActiveRecord::Base.table_name=` method to specify the table
name that should be used:
```ruby
@@ -172,8 +175,8 @@ class Product < ActiveRecord::Base
end
```
-If you do so, you will have to define manually the class name that is hosting
-the fixtures (class_name.yml) using the `set_fixture_class` method in your test
+If you do so, you will have to define manually the class name that is hosting
+the fixtures (class_name.yml) using the `set_fixture_class` method in your test
definition:
```ruby
@@ -184,7 +187,7 @@ class FunnyJoke < ActiveSupport::TestCase
end
```
-It's also possible to override the column that should be used as the table's
+It's also possible to override the column that should be used as the table's
primary key using the `ActiveRecord::Base.set_primary_key` method:
```ruby
@@ -196,17 +199,17 @@ end
CRUD: Reading and Writing Data
------------------------------
-CRUD is an acronym for the four verbs we use to operate on data: **C**reate,
-**R**ead, **U**pdate and **D**elete. Active Record automatically creates methods
+CRUD is an acronym for the four verbs we use to operate on data: **C**reate,
+**R**ead, **U**pdate and **D**elete. Active Record automatically creates methods
to allow an application to read and manipulate data stored within its tables.
### Create
-Active Record objects can be created from a hash, a block or have their
-attributes manually set after creation. The `new` method will return a new
+Active Record objects can be created from a hash, a block or have their
+attributes manually set after creation. The `new` method will return a new
object while `create` will return the object and save it to the database.
-For example, given a model `User` with attributes of `name` and `occupation`,
+For example, given a model `User` with attributes of `name` and `occupation`,
the `create` method call will create and save a new record into the database:
```ruby
@@ -223,7 +226,7 @@ user.occupation = "Code Artist"
A call to `user.save` will commit the record to the database.
-Finally, if a block is provided, both `create` and `new` will yield the new
+Finally, if a block is provided, both `create` and `new` will yield the new
object to that block for initialization:
```ruby
@@ -235,7 +238,7 @@ end
### Read
-Active Record provides a rich API for accessing data within a database. Below
+Active Record provides a rich API for accessing data within a database. Below
are a few examples of different data access methods provided by Active Record.
```ruby
@@ -258,12 +261,12 @@ david = User.find_by_name('David')
users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC')
```
-You can learn more about querying an Active Record model in the [Active Record
+You can learn more about querying an Active Record model in the [Active Record
Query Interface](active_record_querying.html) guide.
### Update
-Once an Active Record object has been retrieved, its attributes can be modified
+Once an Active Record object has been retrieved, its attributes can be modified
and it can be saved to the database.
```ruby
@@ -272,7 +275,7 @@ user.name = 'Dave'
user.save
```
-A shorthand for this is to use a hash mapping attribute names to the desired
+A shorthand for this is to use a hash mapping attribute names to the desired
value, like so:
```ruby
@@ -280,8 +283,8 @@ user = User.find_by_name('David')
user.update(name: 'Dave')
```
-This is most useful when updating several attributes at once. If, on the other
-hand, you'd like to update several records in bulk, you may find the
+This is most useful when updating several attributes at once. If, on the other
+hand, you'd like to update several records in bulk, you may find the
`update_all` class method useful:
```ruby
@@ -290,7 +293,7 @@ User.update_all "max_login_attempts = 3, must_change_password = 'true'"
### Delete
-Likewise, once retrieved an Active Record object can be destroyed which removes
+Likewise, once retrieved an Active Record object can be destroyed which removes
it from the database.
```ruby
@@ -301,46 +304,46 @@ user.destroy
Validations
-----------
-Active Record allows you to validate the state of a model before it gets written
-into the database. There are several methods that you can use to check your
-models and validate that an attribute value is not empty, is unique and not
+Active Record allows you to validate the state of a model before it gets written
+into the database. There are several methods that you can use to check your
+models and validate that an attribute value is not empty, is unique and not
already in the database, follows a specific format and many more.
-Validation is a very important issue to consider when persisting to database, so
-the methods `create`, `save` and `update` take it into account when
-running: they return `false` when validation fails and they didn't actually
-perform any operation on database. All of these have a bang counterpart (that
-is, `create!`, `save!` and `update!`), which are stricter in that
-they raise the exception `ActiveRecord::RecordInvalid` if validation fails.
+Validation is a very important issue to consider when persisting to database, so
+the methods `create`, `save` and `update` take it into account when
+running: they return `false` when validation fails and they didn't actually
+perform any operation on database. All of these have a bang counterpart (that
+is, `create!`, `save!` and `update!`), which are stricter in that
+they raise the exception `ActiveRecord::RecordInvalid` if validation fails.
A quick example to illustrate:
```ruby
class User < ActiveRecord::Base
- validates_presence_of :name
+ validates :name, presence: true
end
User.create # => false
User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
```
-You can learn more about validations in the [Active Record Validations
+You can learn more about validations in the [Active Record Validations
guide](active_record_validations.html).
Callbacks
---------
-Active Record callbacks allow you to attach code to certain events in the
-life-cycle of your models. This enables you to add behavior to your models by
-transparently executing code when those events occur, like when you create a new
-record, update it, destroy it and so on. You can learn more about callbacks in
+Active Record callbacks allow you to attach code to certain events in the
+life-cycle of your models. This enables you to add behavior to your models by
+transparently executing code when those events occur, like when you create a new
+record, update it, destroy it and so on. You can learn more about callbacks in
the [Active Record Callbacks guide](active_record_callbacks.html).
Migrations
----------
-Rails provides a domain-specific language for managing a database schema called
-migrations. Migrations are stored in files which are executed against any
-database that Active Record support using `rake`. Here's a migration that
+Rails provides a domain-specific language for managing a database schema called
+migrations. Migrations are stored in files which are executed against any
+database that Active Record support using `rake`. Here's a migration that
creates a table:
```ruby
@@ -361,10 +364,10 @@ class CreatePublications < ActiveRecord::Migration
end
```
-Rails keeps track of which files have been committed to the database and
+Rails keeps track of which files have been committed to the database and
provides rollback features. To actually create the table, you'd run `rake db:migrate`
and to roll it back, `rake db:rollback`.
-Note that the above code is database-agnostic: it will run in MySQL, postgresql,
-Oracle and others. You can learn more about migrations in the [Active Record
+Note that the above code is database-agnostic: it will run in MySQL, postgresql,
+Oracle and others. You can learn more about migrations in the [Active Record
Migrations guide](migrations.html)
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 7355f6816c..c4d69908ed 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -506,19 +506,15 @@ This code will generate SQL like this:
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
```
-### NOT, LIKE, and NOT LIKE Conditions
+### NOT Conditions
-`NOT`, `LIKE`, and `NOT LIKE` SQL queries can be built by `where.not`, `where.like`, and `where.not_like` respectively.
+`NOT` SQL queries can be built by `where.not`.
```ruby
Post.where.not(author: author)
-
-Author.where.like(name: 'Nari%')
-
-Developer.where.not_like(name: 'Tenderl%')
```
-In other words, these sort of queries can be generated by calling `where` with no argument, then immediately chain with `not`, `like`, or `not_like` passing `where` conditions.
+In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions.
Ordering
--------
@@ -711,7 +707,7 @@ Post.order('id DESC').limit(20).unscope(:order, :limit) = Post.all
You can additionally unscope specific where clauses. For example:
```ruby
-Post.where(:id => 10).limit(1).unscope(:where => :id, :limit).order('id DESC') = Post.order('id DESC')
+Post.where(:id => 10).limit(1).unscope(where: :id, :limit).order('id DESC') = Post.order('id DESC')
```
### `only`
@@ -736,7 +732,7 @@ The `reorder` method overrides the default scope order. For example:
class Post < ActiveRecord::Base
..
..
- has_many :comments, order: 'posted_at DESC'
+ has_many :comments, -> { order('posted_at DESC') }
end
Post.find(10).comments.reorder('name')
@@ -971,7 +967,7 @@ SELECT categories.* FROM categories
INNER JOIN posts ON posts.category_id = 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)")`.
+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).uniq`.
#### Joining Multiple Associations
@@ -1206,6 +1202,7 @@ class User < ActiveRecord::Base
scope :active, -> { where state: 'active' }
scope :inactive, -> { where state: 'inactive' }
end
+```
```ruby
User.active.inactive
@@ -1298,13 +1295,18 @@ recommended that you use the block form of `unscoped`:
```ruby
Client.unscoped {
- Client.created_before(Time.zome.now)
+ Client.created_before(Time.zone.now)
}
```
Dynamic Finders
---------------
+NOTE: Dynamic finders have been deprecated in Rails 4.0 and will be
+removed in Rails 4.1. The best practice is to use Active Record scopes
+instead. You can find the deprecation gem at
+https://github.com/rails/activerecord-deprecated_finders
+
For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods.
You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")`
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 32641d04c1..621d2222ff 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -162,8 +162,8 @@ Person.create(name: nil).valid? # => false
```
After Active Record has performed validations, any errors found can be accessed
-through the `errors` instance method, which returns a collection of errors. By
-definition, an object is valid if this collection is empty after running
+through the `errors.messages` instance method, which returns a collection of errors.
+By definition, an object is valid if this collection is empty after running
validations.
Note that an object instantiated with `new` will not report errors even if it's
@@ -176,17 +176,17 @@ end
>> p = Person.new
#=> #<Person id: nil, name: nil>
->> p.errors
+>> p.errors.messages
#=> {}
>> p.valid?
#=> false
->> p.errors
+>> p.errors.messages
#=> {name:["can't be blank"]}
>> p = Person.create
#=> #<Person id: nil, name: nil>
->> p.errors
+>> p.errors.messages
#=> {name:["can't be blank"]}
>> p.save
@@ -434,7 +434,7 @@ end
Note that the default error messages are plural (e.g., "is too short (minimum
is %{count} characters)"). For this reason, when `:minimum` is 1 you should
-provide a personalized message or use `validates_presence_of` instead. When
+provide a personalized message or use `presence: true` instead. When
`:in` or `:within` have a lower limit of 1, you should either provide a
personalized message or call `presence` prior to `length`.
@@ -530,6 +530,47 @@ field you should use `validates :field_name, inclusion: { in: [true, false] }`.
The default error message is _"can't be empty"_.
+### `absence`
+
+This helper validates that the specified attributes are absent. It uses the
+`present?` method to check if the value is not either nil or a blank string, that
+is, a string that is either empty or consists of whitespace.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, :login, :email, absence: true
+end
+```
+
+If you want to be sure that an association is absent, you'll need to test
+whether the associated object itself is absent, and not the foreign key used
+to map the association.
+
+```ruby
+class LineItem < ActiveRecord::Base
+ belongs_to :order
+ validates :order, absence: true
+end
+```
+
+In order to validate associated records whose absence is required, you must
+specify the `:inverse_of` option for the association:
+
+```ruby
+class Order < ActiveRecord::Base
+ has_many :line_items, inverse_of: :order
+end
+```
+
+If you validate the absence of an object associated via a `has_one` or
+`has_many` relationship, it will check that the object is neither `present?` nor
+`marked_for_destruction?`.
+
+Since `false.present?` is false, if you want to validate the absence of a boolean
+field you should use `validates :field_name, exclusion: { in: [true, false] }`.
+
+The default error message is _"must be blank"_.
+
### `uniqueness`
This helper validates that the attribute's value is unique right before the
@@ -727,6 +768,7 @@ class Person < ActiveRecord::Base
validates :name, presence: true, on: :save
end
```
+The last line is in review state and as of now, it is not running in any version of Rails 3.2.x as discussed in this [issue](https://github.com/rails/rails/issues/10248)
Strict Validations
------------------
@@ -951,12 +993,12 @@ end
person = Person.new
person.valid? # => false
-person.errors
+person.errors.messages
# => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
person = Person.new(name: "John Doe")
person.valid? # => true
-person.errors # => []
+person.errors.messages # => {}
```
### `errors[]`
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 517db0d222..c012ded888 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -10,7 +10,7 @@ After reading this guide, you will know:
* What Core Extensions are.
* How to load all extensions.
* How to cherry-pick just the extensions you want.
-* What extensions ActiveSupport provides.
+* What extensions Active Support provides.
--------------------------------------------------------------------------------
@@ -166,7 +166,7 @@ NOTE: Defined in `active_support/core_ext/object/duplicable.rb`.
### `deep_dup`
-The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this:
+The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, Ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this:
```ruby
array = ['string']
@@ -476,12 +476,11 @@ NOTE: Defined in `active_support/core_ext/kernel/reporting.rb`.
### `in?`
-The predicate `in?` tests if an object is included in another object or a list of objects. An `ArgumentError` exception will be raised if a single argument is passed and it does not respond to `include?`.
+The predicate `in?` tests if an object is included in another object. An `ArgumentError` exception will be raised if the argument passed does not respond to `include?`.
Examples of `in?`:
```ruby
-1.in?(1,2) # => true
1.in?([1,2]) # => true
"lo".in?("hello") # => true
25.in?(30..50) # => false
@@ -1039,6 +1038,8 @@ For convenience `class_attribute` also defines an instance predicate which is th
When `:instance_reader` is `false`, the instance predicate returns a `NoMethodError` just like the reader method.
+If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined.
+
NOTE: Defined in `active_support/core_ext/class/attribute.rb`
#### `cattr_reader`, `cattr_writer`, and `cattr_accessor`
@@ -1344,7 +1345,7 @@ The second argument, `indent_string`, specifies which indent string to use. The
"foo".indent(2, "\t") # => "\t\tfoo"
```
-While `indent_string` is tipically one space or tab, it may be any string.
+While `indent_string` is typically one space or tab, it may be any string.
The third argument, `indent_empty_lines`, is a flag that says whether empty lines should be indented. Default is false.
@@ -2198,7 +2199,7 @@ This method accepts three options:
* `:words_connector`: What is used to join the elements of arrays with 3 or more elements, except for the last two. Default is ", ".
* `:last_word_connector`: What is used to join the last items of an array with 3 or more elements. Default is ", and ".
-The defaults for these options can be localised, their keys are:
+The defaults for these options can be localized, their keys are:
| Option | I18n key |
| ---------------------- | ----------------------------------- |
@@ -2214,7 +2215,9 @@ NOTE: Defined in `active_support/core_ext/array/conversions.rb`.
The method `to_formatted_s` acts like `to_s` by default.
-If the array contains items that respond to `id`, however, it may be passed the symbol `:db` as argument. That's typically used with collections of ARs. Returned strings are:
+If the array contains items that respond to `id`, however, the symbol
+`:db` may be passed as argument. That's typically used with
+collections of Active Record objects. Returned strings are:
```ruby
[].to_formatted_s(:db) # => "null"
@@ -2370,7 +2373,8 @@ NOTE: Defined in `active_support/core_ext/array/wrap.rb`.
### 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.
+The method `Array.deep_dup` duplicates itself and all objects inside
+recursively with Active Support method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside.
```ruby
array = [1, [2, 3]]
@@ -2591,7 +2595,8 @@ NOTE: Defined in `active_support/core_ext/hash/deep_merge.rb`.
### 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.
+The method `Hash.deep_dup` duplicates itself and all keys and values
+inside recursively with Active Support 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] } }
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index d08000eb69..38dbfd3152 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -273,7 +273,7 @@ Action Mailer
to: ["users@rails.com", "ddh@rails.com"],
from: ["me@rails.com"],
date: Sat, 10 Mar 2012 14:18:09 +0100,
- mail: "..." # ommitted for beverity
+ mail: "..." # omitted for brevity
}
```
@@ -299,7 +299,7 @@ Action Mailer
to: ["users@rails.com", "ddh@rails.com"],
from: ["me@rails.com"],
date: Sat, 10 Mar 2012 14:18:09 +0100,
- mail: "..." # ommitted for beverity
+ mail: "..." # omitted for brevity
}
```
@@ -428,7 +428,7 @@ end
```
Defining all those block arguments each time can be tedious. You can easily create an `ActiveSupport::Notifications::Event`
-from block args like this:
+from block arguments like this:
```ruby
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
@@ -442,7 +442,7 @@ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*a
end
```
-Most times you only care about the data itself. Here is a shortuct to just get the data.
+Most times you only care about the data itself. Here is a shortcut to just get the data.
```ruby
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
@@ -465,7 +465,7 @@ Creating custom events
Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of
all the heavy lifting for you. Simply call `instrument` with a `name`, `payload` and a block.
The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times
-as well as the unique ID. All data passed into the `insturment` call will make it into the payload.
+as well as the unique ID. All data passed into the `instrument` call will make it into the payload.
Here's an example:
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index e939606c88..3b3707d9e5 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -75,7 +75,7 @@ The query string strategy has several disadvantages:
2. **The file name can change between nodes in multi-server environments.**<br />
The default query string in Rails 2.x is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request.
3. **Too much cache invalidation**<br />
- When static assets are deployed with each new release of code, the mtime of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed.
+ When static assets are deployed with each new release of code, the mtime(time of last modification) of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed.
Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content.
@@ -416,7 +416,7 @@ You can call this task on the server during deployment to create compiled versio
The rake task is:
```bash
-$ bundle exec rake assets:precompile
+$ RAILS_ENV=production bundle exec rake assets:precompile
```
For faster asset precompiles, you can partially load your application by setting
@@ -430,7 +430,7 @@ in scope in development mode regardless of the value of this flag. Changing this
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.
-Capistrano (v2.8.0 and above) includes a recipe to handle this in deployment. Add the following line to `Capfile`:
+Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment. Add the following line to `Capfile`:
```ruby
load 'deploy/assets'
@@ -450,7 +450,7 @@ The default matcher for compiling files includes `application.js`, `application.
NOTE. The matcher (and other members of the precompile array; see below) is applied to final compiled file names. This means that anything that compiles to JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and `.scss` files are **not** automatically included as they compile to JS/CSS.
-If you have other manifests or individual stylesheets and JavaScript files to include, you can add them to the `precompile` array:
+If you have other manifests or individual stylesheets and JavaScript files to include, you can add them to the `precompile` array in `config/application.rb`:
```ruby
config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
@@ -459,7 +459,7 @@ config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
Or you can opt to precompile all assets with something like this:
```ruby
-# config/environments/production.rb
+# config/application.rb
config.assets.precompile << Proc.new do |path|
if path =~ /\.(css|js)\z/
full_path = Rails.application.assets.resolve(path).to_path
@@ -707,7 +707,7 @@ config.assets.cache_store = :memory_store
The options accepted by the assets cache store are the same as the application's cache store.
```ruby
-config.assets.cache_store = :memory_store, { :size => 32.megabytes }
+config.assets.cache_store = :memory_store, { size: 32.megabytes }
```
Adding Assets to Your Gems
@@ -740,7 +740,7 @@ end
```
Now that you have a `Template` class, it's time to associate it with an
-extenstion for template files:
+extension for template files:
```ruby
Sprockets.register_engine '.bang', BangBang::Template
@@ -815,18 +815,16 @@ end
If you use the `assets` group with Bundler, please make sure that your `config/application.rb` has the following Bundler require statement:
```ruby
-if defined?(Bundler)
- # If you precompile assets before deploying to production, use this line
- Bundler.require *Rails.groups(:assets => %w(development test))
- # If you want your assets lazily compiled in production, use this line
- # Bundler.require(:default, :assets, Rails.env)
-end
+# If you precompile assets before deploying to production, use this line
+Bundler.require *Rails.groups(:assets => %w(development test))
+# If you want your assets lazily compiled in production, use this line
+# Bundler.require(:default, :assets, Rails.env)
```
-Instead of the old Rails 3.0 version:
+Instead of the generated version:
```ruby
-# If you have a Gemfile, require the gems listed there, including any gems
+# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
-Bundler.require(:default, Rails.env) if defined?(Bundler)
+Bundler.require(:default, Rails.env)
```
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 65c8154064..16a5241319 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -572,7 +572,7 @@ end
These need to be backed up by a migration to create the `assemblies_parts` table. This table should be created without a primary key:
```ruby
-class CreateAssemblyPartJoinTable < ActiveRecord::Migration
+class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
def change
create_table :assemblies_parts, id: false do |t|
t.integer :assembly_id
@@ -693,6 +693,17 @@ There are a few limitations to `inverse_of` support:
* They do not work with `:as` associations.
* For `belongs_to` associations, `has_many` inverse associations are ignored.
+Every association will attempt to automatically find the inverse association
+and set the `:inverse_of` option heuristically (based on the association name).
+Most associations with standard names will be supported. However, associations
+that contain the following options will not have their inverses set
+automatically:
+
+* :conditions
+* :through
+* :polymorphic
+* :foreign_key
+
Detailed Association Reference
------------------------------
@@ -1648,9 +1659,10 @@ The `select` method lets you override the SQL `SELECT` clause that is used to re
WARNING: If you specify your own `select`, be sure to include the primary key and foreign key columns of the associated model. If you do not, Rails will throw an error.
-##### `uniq`
+##### `distinct`
-Use the `uniq` method to keep the collection free of duplicates. This is mostly useful together with the `:through` option.
+Use the `distinct` method to keep the collection free of duplicates. This is
+mostly useful together with the `:through` option.
```ruby
class Person < ActiveRecord::Base
@@ -1666,14 +1678,15 @@ person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]
```
-In the above case there are two readings and `person.posts` brings out both of them even though these records are pointing to the same post.
+In the above case there are two readings and `person.posts` brings out both of
+them even though these records are pointing to the same post.
-Now let's set `uniq`:
+Now let's set `distinct`:
```ruby
class Person
has_many :readings
- has_many :posts, -> { uniq }, through: :readings
+ has_many :posts, -> { distinct }, through: :readings
end
person = Person.create(name: 'Honda')
@@ -1684,7 +1697,29 @@ person.posts.inspect # => [#<Post id: 7, name: "a1">]
Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]
```
-In the above case there are still two readings. However `person.posts` shows only one post because the collection loads only unique records.
+In the above case there are still two readings. However `person.posts` shows
+only one post because the collection loads only unique records.
+
+If you want to make sure that, upon insertion, all of the records in the
+persisted association are distinct (so that you can be sure that when you
+inspect the association that you will never find duplicate records), you should
+add a unique index on the table itself. For example, if you have a table named
+``person_posts`` and you want to make sure all the posts are unique, you could
+add the following in a migration:
+
+```ruby
+add_index :person_posts, :post, :unique => true
+```
+
+Note that checking for uniqueness using something like ``include?`` is subject
+to race conditions. Do not attempt to use ``include?`` to enforce distinctness
+in an association. For instance, using the post example from above, the
+following code would be racy because multiple users could be attempting this
+at the same time:
+
+```ruby
+person.posts << post unless person.posts.include?(post)
+```
#### When are Objects Saved?
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index abab3dd983..456abaf612 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -5,8 +5,8 @@ This guide will teach you what you need to know about avoiding that expensive ro
After reading this guide, you will know:
-* Page, action, and fragment caching.
-* Sweepers.
+* Page and action caching (moved to separate gems as of Rails 4).
+* Fragment caching.
* Alternative cache stores.
* Conditional GET support.
@@ -30,13 +30,13 @@ config.action_controller.perform_caching = true
Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
-INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching)
+INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
### Action Caching
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.
-INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching)
+INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
### Fragment Caching
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 9d1fb03fab..e0b44bbf93 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -82,7 +82,7 @@ The server can be run on a different port using the `-p` option. The default dev
$ rails server -e production -p 4000
```
-The `-b` option binds Rails to the specified ip, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option.
+The `-b` option binds Rails to the specified IP, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option.
### `rails generate`
@@ -201,7 +201,7 @@ Usage:
...
-ActiveRecord options:
+Active Record options:
[--migration] # Indicates when to generate migration
# Default: true
@@ -414,7 +414,7 @@ app/controllers/admin/users_controller.rb:
* [ 20] [TODO] any other way to do this?
* [132] [FIXME] high priority for next deploy
-app/model/school.rb:
+app/models/school.rb:
* [ 13] [OPTIMIZE] refactor this code to make it faster
* [ 17] [FIXME]
```
@@ -427,7 +427,7 @@ $ rake notes:fixme
app/controllers/admin/users_controller.rb:
* [132] high priority for next deploy
-app/model/school.rb:
+app/models/school.rb:
* [ 17]
```
@@ -436,7 +436,7 @@ You can also use custom annotations in your code and list them using `rake notes
```bash
$ rake notes:custom ANNOTATION=BUG
(in /home/foobar/commandsapp)
-app/model/post.rb:
+app/models/post.rb:
* [ 23] Have to fix this one before pushing!
```
@@ -445,12 +445,12 @@ NOTE. When using specific annotations and custom annotations, the annotation nam
By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` 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`.
```bash
-$ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor'
+$ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor'
$ rake notes
(in /home/foobar/commandsapp)
-app/model/user.rb:
+app/models/user.rb:
* [ 35] [FIXME] User should have a subscription at this point
-rspec/model/user_spec.rb:
+spec/models/user_spec.rb:
* [122] [TODO] Verify the user that has a subscription works
```
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index dbbeec7126..9e40165d15 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -268,7 +268,7 @@ config.middleware.delete "Rack::MethodOverride"
* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default.
-* +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+.
+* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:number`.
The MySQL adapter adds one additional configuration option:
@@ -646,7 +646,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `active_support.initialize_time_zone` Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC".
-* `active_support.initialize_beginning_of_week` Sets the default beginnig of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`.
+* `active_support.initialize_beginning_of_week` Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`.
* `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`.
@@ -698,7 +698,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `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.
+* `add_generator_templates` Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference.
* `ensure_autoload_once_paths_as_subset` Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised.
@@ -729,7 +729,7 @@ development:
timeout: 5000
```
-Since the connection pooling is handled inside of ActiveRecord by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit.
+Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit.
Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index cc4e369e7d..0bfa646b81 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -24,12 +24,20 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge
### Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues).
+If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself — and others — to replicate the bug and figure out a fix.
Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment.
+### Create a Self-Contained gist for Active Record Issues
+
+If you are filing a bug report for Active Record, please use
+[this template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb)
+if the bug is found in a published gem, and
+[this template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb)
+if the bug happens in the master branch.
+
### Special Treatment for Security Issues
WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The [Rails security policy page](http://rubyonrails.org/security) details the procedure to follow for security issues.
@@ -53,6 +61,22 @@ The easiest and recommended way to get a development environment ready to hack i
In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html).
+
+Running an Application Against Your Local Branch
+------------------------------------------------
+
+The `--dev` flag of `rails new` generates an application that uses your local
+branch:
+
+```bash
+$ cd rails
+$ bundle exec rails new ~/my-test-app --dev
+```
+
+The application generated in `~/my-test-app` runs against your local branch
+and in particular sees any modifications upon server reboot.
+
+
Testing Active Record
---------------------
@@ -158,9 +182,9 @@ Contributing to the Rails Documentation
Ruby on Rails has two main sets of documentation: the guides help you in learning about Ruby on Rails, and the API is a reference.
-You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/lifo/docrails/translating-rails-guides).
+You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides).
-If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/lifo/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
+If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/rails/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub.
@@ -190,7 +214,7 @@ $ cd rails
$ git checkout -b my_new_branch
```
-It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on Github. It won't be part of the Rails Git repository.
+It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository.
### Write Your Code
@@ -201,6 +225,17 @@ Now get busy and add or edit code. You’re on your branch now, so you can write
* Include tests that fail without your code, and pass with it.
* Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution.
+It is not customary in Rails to run the full test suite before pushing
+changes. The railties test suite in particular takes a long time, and even
+more if the source code is mounted in `/vagrant` as happens in the recommended
+workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
+
+As a compromise, test what your code obviously affects, and if the change is
+not in railties run the whole test suite of the affected component. If all is
+green that's enough to propose your contribution. We have [Travis CI](https
+://travis-ci.org/) as a safety net for catching unexpected breakages
+elsewhere.
+
TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted.
### Follow the Coding Conventions
@@ -225,7 +260,7 @@ The above are guidelines — please use your best judgment in using them.
The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version.
-You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, commiting a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
+You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
A CHANGELOG entry should summarize what was changed and should end with author's name. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry:
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index ff76fa2b85..10dd8178fb 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -28,11 +28,11 @@ Ruby on Rails Guides: Credits
<h3 class="section">Rails Guides Authors</h3>
<%= author('Ryan Bigg', 'radar', 'radar.png') do %>
-Ryan Bigg works as a consultant at <a href="http://rubyx.com">RubyX</a> and has been working with Rails since 2006. He's co-authoring a book called <a href="http://manning.com/katz">Rails 3 in Action</a> and he's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>.
+ Ryan Bigg works as the Community Manager at <a href="http://spreecommerce.com">Spree Commerce</a> and has been working with Rails since 2006. He's the author of <a href="https://leanpub.com/multi-tenancy-rails">Multi Tenancy With Rails</a> and co-author of <a href="http://manning.com/bigg2">Rails 4 in Action</a>. He's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>.
<% end %>
<%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %>
-Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">Github account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>.
+Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">GitHub account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>.
<% end %>
<%= author('Frederick Cheung', 'fcheung') do %>
@@ -40,7 +40,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi
<% end %>
<%= author('Tore Darell', 'toretore') do %>
- Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. His home on the internet is his blog <a href="http://tore.darell.no">Sneaky Abstractions</a>.
+ Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. His home on the Internet is his blog <a href="http://tore.darell.no">Sneaky Abstractions</a>.
<% end %>
<%= author('Jeff Dean', 'zilkey') do %>
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 5531dee343..70055c1d7d 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -23,7 +23,7 @@ One common task is to inspect the contents of a variable. In Rails, you can do t
### `debug`
-The `debug` helper will return a \<pre>-tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:
+The `debug` helper will return a \<pre> tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:
```html+erb
<%= debug @post %>
@@ -174,7 +174,7 @@ class PostsController < ApplicationController
end
```
-Here's an example of the log generated by this method:
+Here's an example of the log generated when this controller action is executed:
```
Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
@@ -194,11 +194,13 @@ Redirected to #<Post:0x20af760>
Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts]
```
-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.
+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.
### Tagged Logging
-When running multi-user, multi-account applications, it’s often useful to be able to filter the logs using some custom rules. `TaggedLogging` in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
+When running multi-user, multi-account applications, it’s often useful
+to be able to filter the logs using some custom rules. `TaggedLogging`
+in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
```ruby
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
@@ -216,7 +218,7 @@ The debugger can also help you if you want to learn about the Rails source code
### Setup
-Rails uses the `debugger` gem to set breakpoints and step through live code. To install it, just run:
+You can use the `debugger` gem to set breakpoints and step through live code in Rails. To install it, just run:
```bash
$ gem install debugger
@@ -235,7 +237,7 @@ class PeopleController < ApplicationController
end
```
-If you see the message in the console or logs:
+If you see this message in the console or logs:
```
***** Debugger requested, but was not available: Start server with --debugger to enable *****
@@ -246,12 +248,12 @@ Make sure you have started your web server with the option `--debugger`:
```bash
$ rails server --debugger
=> Booting WEBrick
-=> Rails 3.0.0 application starting on http://0.0.0.0:3000
+=> Rails 3.2.13 application starting on http://0.0.0.0:3000
=> Debugger enabled
...
```
-TIP: In development mode, you can dynamically `require \'debugger\'` 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, even if it was started without `--debugger`.
### The Shell
@@ -266,7 +268,7 @@ For example:
(rdb:7)
```
-Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help... so type: `help` (You didn't see that coming, right?)
+Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help. Type: `help`
```
(rdb:7) help
@@ -281,7 +283,7 @@ condition down finish list ps save thread var
continue edit frame method putl set tmate where
```
-TIP: To view the help menu for any command use `help <command-name>` in active debug mode. For example: _`help var`_
+TIP: To view the help menu for any command use `help <command-name>` at the debugger prompt. For example: _`help var`_
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.
@@ -289,7 +291,7 @@ This command shows you where you are in the code by printing 10 lines centered a
```
(rdb:7) list
-[1, 10] in /PathToProject/posts_controller.rb
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
@@ -325,7 +327,7 @@ On the other hand, to see the previous ten lines you should type `list-` (or `l-
```
(rdb:7) l-
-[1, 10] in /PathToProject/posts_controller.rb
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
@@ -343,7 +345,7 @@ Finally, to see where you are in the code again you can type `list=`
```
(rdb:7) list=
-[1, 10] in /PathToProject/posts_controller.rb
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
@@ -502,7 +504,7 @@ TIP: You can use the debugger while using `rails console`. Just remember to `req
```
$ rails console
-Loading development environment (Rails 3.1.0)
+Loading development environment (Rails 3.2.13)
>> require "debugger"
=> []
>> author = Author.first
@@ -655,21 +657,18 @@ Plugins for Debugging
There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging:
-* [Footnotes](https://github.com/josevalim/rails-footnotes:) Every Rails page has footnotes that give request information and link back to your source via TextMate.
-* [Query Trace](https://github.com/ntalbott/query_trace/tree/master:) Adds query origin tracing to your logs.
-* [Query Reviewer](https://github.com/nesquena/query_reviewer:) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed.
-* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master:) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
+* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has footnotes that give request information and link back to your source via TextMate.
+* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query origin tracing to your logs.
+* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed.
+* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
References
----------
* [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html)
* [debugger Homepage](https://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/)
+* [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/)
* [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/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 6493c1e1ec..5647a4c1b7 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -5,6 +5,10 @@ This guide covers how to setup an environment for Ruby on Rails core development
After reading this guide, you will know:
+* How to set up your machine for Rails development
+* How to run specific groups of unit tests from the Rails test suite
+* How the ActiveRecord portion of the Rails test suite operates
+
--------------------------------------------------------------------------------
The Easy Way
@@ -21,10 +25,10 @@ In case you can't use the Rails development box, see section above, these are th
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:
-* [Try Git course](http://try.github.com/) is an interactive course that will teach you the basics.
+* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics.
* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git
-* [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.
+* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by.
+* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow.
* [GitHub](http://help.github.com) offers links to a variety of Git resources.
* [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license.
@@ -53,7 +57,7 @@ If you are on Fedora or CentOS, you can run
$ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel
```
-If you have any problems with these libraries, you should install them manually compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) .
+If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) .
Also, SQLite3 and its development files for the `sqlite3-ruby` gem — in Ubuntu you're done with just
@@ -93,7 +97,7 @@ $ cd actionpack
$ bundle exec rake test
```
-If you want to run the tests located in a specific directory use the `TEST_DIR` environment variable. For example, this will run the tests of the `railties/test/generators` directory only:
+If you want to run the tests located in a specific directory use the `TEST_DIR` environment variable. For example, this will run the tests in the `railties/test/generators` directory only:
```bash
$ cd railties
@@ -133,14 +137,14 @@ $ sudo yum install mysql-server mysql-devel
$ sudo yum install postgresql-server postgresql-devel
```
-After that run:
+After that, run:
```bash
$ rm .bundle/config
$ bundle install
```
-We need first to delete `.bundle/config` because Bundler remembers in that file that we didn't want to install the "db" group (alternatively you can edit the file).
+First, we need to delete `.bundle/config` because Bundler remembers in that file that we didn't want to install the "db" group (alternatively you can edit the file).
In order to be able to run the test suite against MySQL you need to create a user named `rails` with privileges on the test databases:
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index c73bbeb90d..1b16f4e516 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -36,6 +36,11 @@
name: Views
documents:
-
+ name: Action View Overview
+ url: action_view_overview.html
+ description: This guide provides an introduction to Action View and introduces a few of the more common view helpers.
+ work_in_progress: true
+ -
name: Layouts and Rendering in Rails
url: layouts_and_rendering.html
description: This guide covers the basic layout features of Action Controller and Action View, including rendering and redirecting, using content_for blocks, and working with partials.
@@ -68,7 +73,6 @@
-
name: Action Mailer Basics
url: action_mailer_basics.html
- work_in_progress: true
description: This guide describes how to use Action Mailer to send and receive emails.
-
name: Testing Rails Applications
diff --git a/guides/source/engines.md b/guides/source/engines.md
index ac76f00832..bc66ed256e 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -461,7 +461,7 @@ NOTE: Other engines, such as Devise, handle this a little differently by making
The engine contains migrations for the `blorgh_posts` and `blorgh_comments` table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the application use this command:
```bash
-$ rake blorgh:install:migrations
+$ rake blorgh_engine:install:migrations
```
If you have multiple engines that need migrations copied over, use `railties:install:migrations` instead:
@@ -612,50 +612,50 @@ This section covers how to make the `User` class configurable, followed by gener
#### Setting configuration settings in the application
-The next step is to make the class that represents a `User` in the application customizable for the engine. This is because, as explained before, that class may not always be `User`. To make this customizable, the engine will have a configuration setting called `user_class` that will be used to specify what the class representing users is inside the application.
+The next step is to make the class that represents a `User` in the application customizable for the engine. This is because, as explained before, that class may not always be `User`. To make this customizable, the engine will have a configuration setting called `author_class` that will be used to specify what the class representing users is inside the application.
To define this configuration setting, you should use a `mattr_accessor` inside the `Blorgh` module for the engine, located at `lib/blorgh.rb` inside the engine. Inside this module, put this line:
```ruby
-mattr_accessor :user_class
+mattr_accessor :author_class
```
-This method works like its brothers `attr_accessor` and `cattr_accessor`, but provides a setter and getter method on the module with the specified name. To use it, it must be referenced using `Blorgh.user_class`.
+This method works like its brothers `attr_accessor` and `cattr_accessor`, but provides a setter and getter method on the module with the specified name. To use it, it must be referenced using `Blorgh.author_class`.
The next step is switching the `Blorgh::Post` model over to this new setting. For the `belongs_to` association inside this model (`app/models/blorgh/post.rb`), it will now become this:
```ruby
-belongs_to :author, class_name: Blorgh.user_class
+belongs_to :author, class_name: Blorgh.author_class
```
The `set_author` method also located in this class should also use this class:
```ruby
-self.author = Blorgh.user_class.constantize.find_or_create_by(name: author_name)
+self.author = Blorgh.author_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:
+To save having to call `constantize` on the `author_class` result all the time, you could instead just override the `author_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
+def self.author_class
+ @@author_class.constantize
end
```
This would then turn the above code for `set_author` into this:
```ruby
-self.author = Blorgh.user_class.find_or_create_by(name: author_name)
+self.author = Blorgh.author_class.find_or_create_by(name: author_name)
```
-Resulting in something a little shorter, and more implicit in its behavior. The `user_class` method should always return a `Class` object.
+Resulting in something a little shorter, and more implicit in its behavior. The `author_class` method should always return a `Class` object.
-Since we changed the `user_class` method to no longer return a
+Since we changed the `author_class` method to no longer return a
`String` but a `Class` we must also modify our `belongs_to` definition
in the `Blorgh::Post` model:
```ruby
-belongs_to :author, class_name: Blorgh.user_class.to_s
+belongs_to :author, class_name: Blorgh.author_class.to_s
```
To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing.
@@ -663,7 +663,7 @@ To set this configuration setting within the application, an initializer should
Create a new initializer at `config/initializers/blorgh.rb` inside the application where the `blorgh` engine is installed and put this content in it:
```ruby
-Blorgh.user_class = "User"
+Blorgh.author_class = "User"
```
WARNING: It's very important here to use the `String` version of the class, rather than the class itself. If you were to use the class, Rails would attempt to load that class and then reference the related table, which could lead to problems if the table wasn't already existing. Therefore, a `String` should be used and then converted to a class using `constantize` in the engine later on.
@@ -719,6 +719,32 @@ Engine model and controller classes can be extended by open classing them in the
For simple class modifications use `Class#class_eval`, and for complex class modifications, consider using `ActiveSupport::Concern`.
+#### A note on Decorators and loading code
+
+Because these decorators are not referenced by your Rails application itself,
+Rails' autoloading system will not kick in and load your decorators. This
+means that you need to require them yourself.
+
+Here is some sample code to do this:
+
+```ruby
+# lib/blorgh/engine.rb
+module Blorgh
+ class Engine < ::Rails::Engine
+ isolate_namespace Blorgh
+
+ config.to_prepare do
+ Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
+ require_dependency(c)
+ end
+ end
+ end
+end
+```
+
+This doesn't apply to just Decorators, but anything that you add in an engine
+that isn't referenced by your main application.
+
#### Implementing Decorator Pattern Using Class#class_eval
**Adding** `Post#time_since_created`,
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 817a732051..a4dab39d55 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -221,7 +221,7 @@ Upon form submission the value entered by the user will be stored in `params[:pe
WARNING: You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object.
-Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations and Callbacks](./active_record_validations_callbacks.html#displaying-validation-errors-in-the-view) guide.
+Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations](./active_record_validations.html#displaying-validation-errors-in-views) guide.
### Binding a Form to an Object
@@ -423,7 +423,7 @@ 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.
+WARNING: when `:include_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.
You can add arbitrary attributes to the options using hashes:
@@ -836,17 +836,14 @@ Active Record provides model level support via the `accepts_nested_attributes_f
class Person < ActiveRecord::Base
has_many :addresses
accepts_nested_attributes_for :addresses
-
- attr_accessible :name, :addresses_attributes
end
class Address < ActiveRecord::Base
belongs_to :person
- attr_accessible :kind, :street
end
```
-This creates an `addresses_attributes=` method on `Person` that allows you to create, update and (optionally) destroy addresses. When using `attr_accessible` or `attr_protected` you must mark `addresses_attributes` as accessible as well as the other attributes of `Person` and `Address` that should be mass assigned.
+This creates an `addresses_attributes=` method on `Person` that allows you to create, update and (optionally) destroy addresses.
### Building the Form
diff --git a/guides/source/generators.md b/guides/source/generators.md
index d7c789e2d8..a8a34d0ac4 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -589,11 +589,11 @@ Creates an initializer in the `config/initializers` directory of the application
initializer "begin.rb", "puts 'this is the beginning'"
```
-This method also takes a block:
+This method also takes a block, expected to return a string:
```ruby
initializer "begin.rb" do
- puts "Almost done!"
+ "puts 'this is the beginning'"
end
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index cd23b5ee15..e26ebae0fb 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -29,7 +29,7 @@ prerequisites installed:
Rails is a web application framework running on the Ruby programming language.
If you have no prior experience with Ruby, you will find a very steep learning
curve diving straight into Rails. There are some good free resources on the
-internet for learning Ruby, including:
+Internet for learning Ruby, including:
* [Mr. Neighborly's Humble Little Ruby Book](http://www.humblelittlerubybook.com)
* [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)
@@ -64,7 +64,7 @@ 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).
+[here](https://github.com/rails/docrails/tree/master/guides/code/getting_started).
By following along with this guide, you'll create a Rails project called
`blog`, a
@@ -165,7 +165,7 @@ TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the
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>. You should see the Rails default information page:
-![Welcome Aboard screenshot](images/rails_welcome.png)
+![Welcome Aboard screenshot](images/getting_started/rails_welcome.png)
TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt cursor again. For most UNIX-like systems including Mac OS X this will be a dollar sign `$`. 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.
@@ -252,29 +252,43 @@ 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 "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this:
+Rails provides a `resources` method which can be used to declare a
+standard REST resource. Here's how `config/routes.rb` will look like.
-![The new post form](images/getting_started/new_post.png)
+```ruby
+Blog::Application.routes.draw do
-It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards.
+ resources :posts
-### Laying down the ground work
+ root to: "welcome#index"
+end
+```
-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> — Rails will give you a routing error:
+If you run `rake routes`, you'll see that all the routes for the
+standard RESTful actions.
-![A routing error, no route matches /posts/new](images/getting_started/routing_error_no_route_matches.png)
+```bash
+$ 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
+ PATCH /posts/:id(.:format) posts#update
+ PUT /posts/:id(.:format) posts#update
+ DELETE /posts/:id(.:format) posts#destroy
+ root / welcome#index
+```
-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.
+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:
- 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:
+![The new post form](images/getting_started/new_post.png)
-```ruby
-get "posts/new"
-```
+It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards.
-This route is a super-simple route: it defines a new route that only responds to `GET` requests, and that the route is at `posts/new`. But how does it know where to go without the use of the `:to` option? Well, Rails uses a sensible default here: Rails will assume that you want this route to go to the new action inside the posts controller.
+### Laying down the ground work
-With the route defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see another routing error:
+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`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see a routing error:
![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png)
@@ -377,14 +391,10 @@ 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:
```html+erb
-<%= form_for :post, url: { action: :create } do |f| %>
+<%= form_for :post, url: posts_path do |f| %>
```
-In this example, a `Hash` object is passed to the `:url` option. What Rails will do with this is that it will point the form to the `create` action of the current controller, the `PostsController`, and will send a `POST` request to that route. For this to work, you will need to add a route to `config/routes.rb`, right underneath the one for "posts/new":
-
-```ruby
-post "posts" => "posts#create"
-```
+In this example, the `posts_path` helper is passed to the `:url` option. What Rails will do with this is that it will point the form to the `create` action of the current controller, the `PostsController`, and will send a `POST` request to that route.
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.
@@ -521,21 +531,28 @@ and change the `create` action to look like this:
```ruby
def create
- @post = Post.new(params[:post])
-
+ @post = Post.new(post_params)
+
@post.save
- redirect_to action: :show, id: @post.id
+ redirect_to @post
end
+
+private
+ def post_params
+ params.require(:post).permit(:title, :text)
+ end
```
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_params` 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,
which we'll define later.
+TIP: Note that `def post_params` is private. This new approach prevents an attacker from setting the model's attributes by manipulating the hash passed to the model. For more information, refer to [this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
+
TIP: As we'll see later, `@post.save` returns a boolean indicating
whether the model was saved or not.
@@ -543,16 +560,14 @@ whether the model was saved or not.
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:
+`show` action before proceeding.
```ruby
-get "posts/:id" => "posts#show"
+post GET /posts/:id(.:format) posts#show
```
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.
+parameter, which in our case will be the id of the post.
As we did before, we need to add the `show` action in
`app/controllers/posts_controller.rb` and its respective view.
@@ -601,7 +616,7 @@ look like this:
@post = Post.new(params[:post].permit(:title, :text))
@post.save
- redirect_to action: :show, id: @post.id
+ redirect_to @post
end
```
@@ -613,11 +628,11 @@ Visit <http://localhost:3000/posts/new> and give it a try!
### 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`:
+We still need a way to list all our posts, so let's do that.
+We'll use a specific route from `config/routes.rb`:
```ruby
-get "posts" => "posts#index"
+posts GET /posts(.:format) posts#index
```
And an action for that route inside the `PostsController` in the `app/controllers/posts_controller.rb` file:
@@ -669,7 +684,7 @@ for posts.
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 %>
+<%= link_to 'New post', new_post_path %>
```
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:
@@ -679,7 +694,7 @@ This link will allow you to bring up the form that lets you create a new post. Y
...
<% end %>
-<%= link_to 'Back', action: :index %>
+<%= link_to 'Back', posts_path %>
```
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:
@@ -695,7 +710,7 @@ Finally, add another link to the `app/views/posts/show.html.erb` template to go
<%= @post.text %>
</p>
-<%= link_to 'Back', action: :index %>
+<%= link_to 'Back', posts_path %>
```
TIP: If you want to link to an action in the same controller, you don't
@@ -755,7 +770,7 @@ def create
@post = Post.new(params[:post].permit(:title, :text))
if @post.save
- redirect_to action: :show, id: @post.id
+ redirect_to @post
else
render 'new'
end
@@ -776,7 +791,7 @@ something went wrong. To do that, you'll modify
`app/views/posts/new.html.erb` to check for error messages:
```html+erb
-<%= form_for :post, url: { action: :create } do |f| %>
+<%= form_for :post, url: posts_path do |f| %>
<% if @post.errors.any? %>
<div id="errorExplanation">
<h2><%= pluralize(@post.errors.count, "error") %> prohibited
@@ -803,7 +818,7 @@ something went wrong. To do that, you'll modify
</p>
<% end %>
-<%= link_to 'Back', action: :index %>
+<%= link_to 'Back', posts_path %>
```
A few things are going on. We check if there are any errors with
@@ -832,14 +847,6 @@ We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating p
The first step we'll take is adding an `edit` action to `posts_controller`.
-Start by adding a route to `config/routes.rb`:
-
-```ruby
-get "posts/:id/edit" => "posts#edit"
-```
-
-And then add the controller action:
-
```ruby
def edit
@post = Post.find(params[:id])
@@ -853,8 +860,7 @@ it look as follows:
```html+erb
<h1>Editing post</h1>
-<%= form_for :post, url: { action: :update, id: @post.id },
-method: :patch do |f| %>
+<%= form_for :post, url: post_path(@post.id), method: :patch do |f| %>
<% if @post.errors.any? %>
<div id="errorExplanation">
<h2><%= pluralize(@post.errors.count, "error") %> prohibited
@@ -881,7 +887,7 @@ method: :patch do |f| %>
</p>
<% end %>
-<%= link_to 'Back', action: :index %>
+<%= link_to 'Back', posts_path %>
```
This time we point the form to the `update` action, which is not defined yet
@@ -893,21 +899,14 @@ via the `PATCH` HTTP method which is the HTTP method you're expected to use to
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
-patch "posts/:id" => "posts#update"
-```
-
-And then create the `update` action in `app/controllers/posts_controller.rb`:
+Next we need to create the `update` action in `app/controllers/posts_controller.rb`:
```ruby
def update
@post = Post.find(params[:id])
if @post.update(params[:post].permit(:title, :text))
- redirect_to action: :show, id: @post.id
+ redirect_to @post
else
render 'edit'
end
@@ -941,8 +940,8 @@ appear next to the "Show" link:
<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 'Show', post_path(post) %></td>
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
</tr>
<% end %>
</table>
@@ -955,8 +954,8 @@ the template:
```html+erb
...
-<%= link_to 'Back', action: :index %>
-| <%= link_to 'Edit', action: :edit, id: @post.id %>
+<%= link_to 'Back', posts_path %>
+| <%= link_to 'Edit', edit_post_path(@post) %>
```
And here's how our app looks so far:
@@ -1016,7 +1015,7 @@ completely:
<%= render 'form' %>
-<%= link_to 'Back', action: :index %>
+<%= link_to 'Back', posts_path %>
```
Then do the same for the `app/views/posts/edit.html.erb` view:
@@ -1026,66 +1025,17 @@ Then do the same for the `app/views/posts/edit.html.erb` view:
<%= render 'form' %>
-<%= link_to 'Back', action: :index %>
-```
-
-Point your browser to <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:
-
-![Undefined method post_path](images/getting_started/undefined_method_post_path.png)
-
-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. With your server running you can view your routes by visiting [localhost:3000/rails/info/routes](http://localhost:3000/rails/info/routes), or you can generate them from the command line by running `rake routes`:
-
-```bash
-$ rake routes
-
- posts GET /posts(.:format) posts#index
-posts_new GET /posts/new(.:format) posts#new
- POST /posts(.: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
-```
-
-To fix this, open `config/routes.rb` and modify the `get "posts/:id"`
-line like this:
-
-```ruby
-get "posts/:id" => "posts#show", as: :post
+<%= link_to 'Back', posts_path %>
```
-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.
-
-NOTE: The `:as` option is available on the `post`, `patch`, `put`, `delete` and `match`
-routing methods also.
-
### Deleting Posts
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`:
+database. Following the REST convention, the route for
+deleting posts in the `config/routes.rb` is:
```ruby
-delete "posts/:id" => "posts#destroy"
+DELETE /posts/:id(.:format) posts#destroy
```
The `delete` routing method should be used for routes that destroy
@@ -1105,7 +1055,7 @@ def destroy
@post = Post.find(params[:id])
@post.destroy
- redirect_to action: :index
+ redirect_to posts_path
end
```
@@ -1132,18 +1082,17 @@ together.
<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 },
+ <td><%= link_to 'Show', post_path(post) %></td>
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
+ <td><%= link_to 'Destroy', post_path(post),
method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
```
-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 `:'data-confirm'`
+Here we're using `link_to` in a different way. We pass the named route as the first argument,
+and then the final two keys as another argument. The `:method` and `:'data-confirm'`
options are used as HTML5 attributes so that when the link is clicked,
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
@@ -1153,61 +1102,11 @@ generated the application. Without this file, the confirmation dialog box wouldn
![Confirm Dialog](images/getting_started/confirm_dialog.png)
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.
-
-### Going Deeper into REST
-
-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:
-
-```ruby
-get "posts" => "posts#index"
-get "posts/new"
-post "posts" => "posts#create"
-get "posts/:id" => "posts#show", as: :post
-get "posts/:id/edit" => "posts#edit"
-patch "posts/:id" => "posts#update"
-delete "posts/:id" => "posts#destroy"
-```
-
-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
-Blog::Application.routes.draw do
-
- resources :posts
-
- root to: "welcome#index"
-end
-```
-
-If you run `rake routes`, you'll see that all the routes that we
-declared before are still available:
-
-```bash
-$ 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
-```
-
-Also, if you go through the motions of creating, updating and deleting
-posts the app still works as before.
+posts.
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
+of declaring routes manually.
+For more information about routing, see
[Rails Routing from the Outside In](routing.html).
Adding a Second Model
@@ -1256,19 +1155,17 @@ class CreateComments < ActiveRecord::Migration
create_table :comments do |t|
t.string :commenter
t.text :body
- t.references :post
+ t.references :post, index: true
t.timestamps
end
-
- add_index :comments, :post_id
end
end
```
The `t.references` line sets up a foreign key column for the association between
-the two models. And the `add_index` line sets up an index for this association
-column. Go ahead and run the migration:
+the two models. An index for this association is also created on this column.
+Go ahead and run the migration:
```bash
$ rake db:migrate
@@ -1280,10 +1177,8 @@ run against the current database, so in this case you will just see:
```bash
== CreateComments: migrating =================================================
-- create_table(:comments)
- -> 0.0008s
--- add_index(:comments, :post_id)
- -> 0.0003s
-== CreateComments: migrated (0.0012s) ========================================
+ -> 0.0115s
+== CreateComments: migrated (0.0119s) ========================================
```
### Associating Models
@@ -1721,7 +1616,7 @@ class CommentsController < ApplicationController
Now if you try to create a new post, you will be greeted with a basic HTTP
Authentication challenge
-![Basic HTTP Authentication Challenge](images/challenge.png)
+![Basic HTTP Authentication Challenge](images/getting_started/challenge.png)
What's Next?
------------
@@ -1761,7 +1656,7 @@ cannot be automatically detected by Rails and corrected.
Two very common sources of data that are not UTF-8:
-* Your text editor: Most text editors (such as Textmate), default to saving files as
+* Your text editor: Most text editors (such as TextMate), default to saving files as
UTF-8. If your text editor does not, this can result in special characters that you
enter in your templates (such as é) to appear as a diamond with a question mark inside
in the browser. This also applies to your i18n translation files.
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 5304ca4285..65a85efa69 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -258,7 +258,7 @@ match '/:locale' => 'dashboard#index'
Do take special care about the **order of your routes**, so this route declaration does not "eat" other ones. (You may want to add it directly before the `root :to` declaration.)
-NOTE: Have a look at two plugins which simplify work with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master).
+NOTE: Have a look at two plugins which simplify work with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master) and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master).
### Setting the Locale from the Client Supplied Information
@@ -417,7 +417,7 @@ So that would give you:
![rails i18n demo localized time to pirate](images/i18n/demo_localized_pirate.png)
-TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by **translating Rails' defaults for your locale**. See the [rails-i18n repository at Github](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for an archive of various locale files. When you put such file(s) in `config/locales/` directory, they will automatically be ready for use.
+TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by **translating Rails' defaults for your locale**. See the [rails-i18n repository at GitHub](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for an archive of various locale files. When you put such file(s) in `config/locales/` directory, they will automatically be ready for use.
### Inflection Rules For Other Locales
@@ -837,6 +837,28 @@ en:
NOTE: In order to use this helper, you need to install [DynamicForm](https://github.com/joelmoss/dynamic_form)
gem by adding this line to your Gemfile: `gem 'dynamic_form'`.
+### Translations for Action Mailer E-Mail Subjects
+
+If you don't pass a subject to the `mail` method, Action Mailer will try to find
+it in your translations. The performed lookup will use the pattern
+`<mailer_scope>.<action_name>.subject` to construct the key.
+
+```ruby
+# user_mailer.rb
+class UserMailer < ActionMailer::Base
+ def welcome(user)
+ #...
+ end
+end
+```
+
+```yaml
+en:
+ user_mailer:
+ welcome:
+ subject: "Welcome to Rails Guides!"
+```
+
### Overview of Other Built-In Methods that Provide I18n Support
Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview.
@@ -958,8 +980,8 @@ Resources
* [rails-i18n.org](http://rails-i18n.org) - Homepage of the rails-i18n project. You can find lots of useful resources on the [wiki](http://rails-i18n.org/wiki).
* [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list.
-* [Github: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases.
-* [Github: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem.
+* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases.
+* [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem.
* [Lighthouse: rails-i18n](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview) - Issue tracker for the rails-i18n project.
* [Lighthouse: i18n](http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview) - Issue tracker for the i18n gem.
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 412f2faaaa..9fcd530183 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -59,35 +59,33 @@ dependencies of the application. `config/boot.rb` sets
`ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile
exists, `bundler/setup` is then required.
-The gems that a Rails 4 application depends on are as follows:
-
-TODO: change these when the Rails 4 release is near.
-
-* abstract (1.0.0)
-* actionmailer (4.0.0.beta)
-* actionpack (4.0.0.beta)
-* activemodel (4.0.0.beta)
-* activerecord (4.0.0.beta)
-* activesupport (4.0.0.beta)
-* arel (2.0.7)
-* builder (3.0.0)
-* bundler (1.0.6)
-* erubis (2.6.6)
-* i18n (0.5.0)
-* mail (2.2.12)
-* mime-types (1.16)
-* polyglot (0.3.1)
-* rack (1.2.1)
-* rack-cache (0.5.3)
-* rack-mount (0.6.13)
-* rack-test (0.5.6)
-* rails (4.0.0.beta)
-* railties (4.0.0.beta)
-* rake (0.8.7)
-* sqlite3-ruby (1.3.2)
-* thor (0.14.6)
-* treetop (1.4.9)
-* tzinfo (0.3.23)
+A standard Rails application depends on several gems, specifically:
+
+* abstract
+* actionmailer
+* actionpack
+* activemodel
+* activerecord
+* activesupport
+* arel
+* builder
+* bundler
+* erubis
+* i18n
+* mail
+* mime-types
+* polyglot
+* rack
+* rack-cache
+* rack-mount
+* rack-test
+* rails
+* railties
+* rake
+* sqlite3-ruby
+* thor
+* treetop
+* tzinfo
### `rails/commands.rb`
@@ -116,7 +114,7 @@ If we used `s` rather than `server`, Rails will use the `aliases` defined in the
```ruby
when 'server'
- # Change to the application's path if there is no config.ru file in current dir.
+ # Change to the application's path if there is no config.ru file in current directory.
# This allows us to run `rails server` from other directories, but still get
# the main config.ru and properly set the tmp directory.
Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))
@@ -131,7 +129,7 @@ when 'server'
end
```
-This file will change into the root of the directory (a path two directories back from `APP_PATH` which points at `config/application.rb`), but only if the `config.ru` file isn't found. This then requires `rails/commands/server` which sets up the `Rails::Server` class.
+This file will change into the Rails root directory (a path two directories up from `APP_PATH` which points at `config/application.rb`), but only if the `config.ru` file isn't found. This then requires `rails/commands/server` which sets up the `Rails::Server` class.
```ruby
require 'fileutils'
@@ -147,11 +145,11 @@ module Rails
### `actionpack/lib/action_dispatch.rb`
Action Dispatch is the routing component of the Rails framework.
-It adds functionalities like routing, session, and common middlewares.
+It adds functionality like routing, session, and common middlewares.
### `rails/commands/server.rb`
-The `Rails::Server` class is defined in this file as inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`:
+The `Rails::Server` class is defined in this file by inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`:
```ruby
def initialize(*)
@@ -441,14 +439,14 @@ inside each of those frameworks, but you're encouraged to try and
explore them on your own.
For now, just keep in mind that common functionality like Rails engines,
-I18n and Rails configuration is all being defined here.
+I18n and Rails configuration are all being defined here.
### Back to `config/environment.rb`
When `config/application.rb` has finished loading Rails, and defined
-your application namespace, you go back to `config/environment.rb`,
-where your application is initialized. For example, if you application was called
-`Blog`, here you would find `Blog::Application.initialize!`, which is
+the application namespace, we go back to `config/environment.rb`,
+where the application is initialized. For example, if the application was called
+`Blog`, here we would find `Blog::Application.initialize!`, which is
defined in `rails/application.rb`
### `railties/lib/rails/application.rb`
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 397dd62638..ef2bdf0753 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -102,10 +102,10 @@
</p>
<p>
If you see any typos or factual errors you are confident to
- patch, please clone <%= link_to 'docrails', 'https://github.com/lifo/docrails' %>
+ patch, please clone <%= link_to 'docrails', 'https://github.com/rails/docrails' %>
and push the change yourself. That branch of Rails has public write access.
Commits are still reviewed, but that happens after you've submitted your
- contribution. <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> is
+ contribution. <%= link_to 'docrails', 'https://github.com/rails/docrails' %> is
cross-merged with master periodically.
</p>
<p>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index bfd1a7c61b..1ab841b137 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -1,7 +1,7 @@
Layouts and Rendering in Rails
==============================
-This guide covers the basic layout features of Action Controller and Action View. By referring to this guide, you will be able to:
+This guide covers the basic layout features of Action Controller and Action View.
After reading this guide, you will know:
@@ -283,8 +283,8 @@ Calls to the `render` method generally accept four options:
* `:content_type`
* `:layout`
-* `:status`
* `:location`
+* `:status`
##### The `:content_type` Option
@@ -310,25 +310,86 @@ You can also tell Rails to render with no layout at all:
render layout: false
```
-##### The `:status` Option
+##### The `:location` Option
-Rails will automatically generate a response with the correct HTTP status code (in most cases, this is `200 OK`). You can use the `:status` option to change this:
+You can use the `:location` option to set the HTTP `Location` header:
```ruby
-render status: 500
-render status: :forbidden
+render xml: photo, location: photo_url(photo)
```
-Rails understands both numeric and symbolic status codes.
-
-##### The `:location` Option
+##### The `:status` Option
-You can use the `:location` option to set the HTTP `Location` header:
+Rails will automatically generate a response with the correct HTTP status code (in most cases, this is `200 OK`). You can use the `:status` option to change this:
```ruby
-render xml: photo, location: photo_url(photo)
+render status: 500
+render status: :forbidden
```
+Rails understands both numeric status codes and the corresponding symbols shown below.
+
+| Response Class | HTTP Status Code | Symbol |
+| ------------------- | ---------------- | -------------------------------- |
+| **Informational** | 100 | :continue |
+| | 101 | :switching_protocols |
+| | 102 | :processing |
+| **Success** | 200 | :ok |
+| | 201 | :created |
+| | 202 | :accepted |
+| | 203 | :non_authoritative_information |
+| | 204 | :no_content |
+| | 205 | :reset_content |
+| | 206 | :partial_content |
+| | 207 | :multi_status |
+| | 208 | :already_reported |
+| | 226 | :im_used |
+| **Redirection** | 300 | :multiple_choices |
+| | 301 | :moved_permanently |
+| | 302 | :found |
+| | 303 | :see_other |
+| | 304 | :not_modified |
+| | 305 | :use_proxy |
+| | 306 | :reserved |
+| | 307 | :temporary_redirect |
+| | 308 | :permanent_redirect |
+| **Client Error** | 400 | :bad_request |
+| | 401 | :unauthorized |
+| | 402 | :payment_required |
+| | 403 | :forbidden |
+| | 404 | :not_found |
+| | 405 | :method_not_allowed |
+| | 406 | :not_acceptable |
+| | 407 | :proxy_authentication_required |
+| | 408 | :request_timeout |
+| | 409 | :conflict |
+| | 410 | :gone |
+| | 411 | :length_required |
+| | 412 | :precondition_failed |
+| | 413 | :request_entity_too_large |
+| | 414 | :request_uri_too_long |
+| | 415 | :unsupported_media_type |
+| | 416 | :requested_range_not_satisfiable |
+| | 417 | :expectation_failed |
+| | 422 | :unprocessable_entity |
+| | 423 | :locked |
+| | 424 | :failed_dependency |
+| | 426 | :upgrade_required |
+| | 423 | :precondition_required |
+| | 424 | :too_many_requests |
+| | 426 | :request_header_fields_too_large |
+| **Server Error** | 500 | :internal_server_error |
+| | 501 | :not_implemented |
+| | 502 | :bad_gateway |
+| | 503 | :service_unavailable |
+| | 504 | :gateway_timeout |
+| | 505 | :http_version_not_supported |
+| | 506 | :variant_also_negotiates |
+| | 507 | :insufficient_storage |
+| | 508 | :loop_detected |
+| | 510 | :not_extended |
+| | 511 | :network_authentication_required |
+
#### Finding Layouts
To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.
@@ -363,7 +424,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])
@@ -578,7 +639,7 @@ This would detect that there are no books with the specified ID, populate the `@
### Using `head` To Build Header-Only Responses
-The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method takes one parameter, which is interpreted as a hash of header names and values. For example, you can return only an error header:
+The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header:
```ruby
head :bad_request
@@ -654,7 +715,7 @@ There are three tag options available for the `auto_discovery_link_tag`:
* `:rel` specifies the `rel` value in the link. The default value is "alternate".
* `:type` specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically.
-* `:title` specifies the title of the link. The default value is the uppercased `:type` value, for example, "ATOM" or "RSS".
+* `:title` specifies the title of the link. The default value is the uppercase `:type` value, for example, "ATOM" or "RSS".
#### Linking to JavaScript Files with the `javascript_include_tag`
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index bd63970bea..550f8fdc3c 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -150,7 +150,25 @@ class AddPartNumberToProducts < ActiveRecord::Migration
end
```
-Similarly,
+If you'd like to add an index on the new column, you can do that as well:
+
+```bash
+$ rails generate migration AddPartNumberToProducts part_number:string:index
+```
+
+will generate
+
+```ruby
+class AddPartNumberToProducts < ActiveRecord::Migration
+ def change
+ add_column :products, :part_number, :string
+ add_index :products, :part_number
+ end
+end
+```
+
+
+Similarly, you can generate a migration to remove a column from the command line:
```bash
$ rails generate migration RemovePartNumberFromProducts part_number:string
@@ -831,10 +849,10 @@ end
```
```ruby
-# app/model/product.rb
+# app/models/product.rb
class Product < ActiveRecord::Base
- validates :flag, presence: true
+ validates :flag, inclusion: { in: [true, false] }
end
```
@@ -856,10 +874,11 @@ end
```
```ruby
-# app/model/product.rb
+# app/models/product.rb
class Product < ActiveRecord::Base
- validates :flag, :fuzz, presence: true
+ validates :flag, inclusion: { in: [true, false] }
+ validates :fuzz, presence: true
end
```
@@ -1046,8 +1065,8 @@ with foreign key constraints in the database.
Although Active Record does not provide any tools for working directly with
such features, the `execute` method can be used to execute arbitrary SQL. You
-could also use some gem like
-[foreigner](https://github.com/matthuhiggins/foreigner) which add foreign key
+can also use a gem like
+[foreigner](https://github.com/matthuhiggins/foreigner) which adds foreign key
support to Active Record (including support for dumping foreign keys in
`db/schema.rb`).
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 77138d8871..b548eaede8 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -13,7 +13,7 @@ After reading this guide, you will know:
Usage
-----
-To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option. This can either be path to a file or a URL.
+To apply a template, you need to provide the Rails generator with the location of the template you wish to apply using the -m option. This can either be a path to a file or a URL.
```bash
$ rails new blog -m ~/template.rb
@@ -30,7 +30,7 @@ $ rake rails:template LOCATION=http://example.com/template.rb
Template API
------------
-Rails templates API is very self explanatory and easy to understand. Here's an example of a typical Rails template:
+The Rails templates API is easy to understand. Here's an example of a typical Rails template:
```ruby
# template.rb
@@ -43,7 +43,7 @@ git add: "."
git commit: %Q{ -m 'Initial commit' }
```
-The following sections outlines the primary methods provided by the API:
+The following sections outline the primary methods provided by the API:
### gem(*args)
@@ -66,7 +66,7 @@ bundle install
Wraps gem entries inside a group.
-For example, if you want to load `rspec-rails` only in `development` and `test` group:
+For example, if you want to load `rspec-rails` only in the `development` and `test` groups:
```ruby
gem_group :development, :test do
@@ -100,7 +100,7 @@ A block can be used in place of the `data` argument.
Adds an initializer to the generated application’s `config/initializers` directory.
-Lets say you like using `Object#not_nil?` and `Object#not_blank?`:
+Let's say you like using `Object#not_nil?` and `Object#not_blank?`:
```ruby
initializer 'bloatlol.rb', <<-CODE
@@ -116,9 +116,9 @@ initializer 'bloatlol.rb', <<-CODE
CODE
```
-Similarly `lib()` creates a file in the `lib/` directory and `vendor()` creates a file in the `vendor/` directory.
+Similarly, `lib()` creates a file in the `lib/` directory and `vendor()` creates a file in the `vendor/` directory.
-There is even `file()`, which accepts a relative path from `Rails.root` and creates all the directories/file needed:
+There is even `file()`, which accepts a relative path from `Rails.root` and creates all the directories/files needed:
```ruby
file 'app/components/foo.rb', <<-CODE
@@ -127,7 +127,7 @@ file 'app/components/foo.rb', <<-CODE
CODE
```
-That’ll create `app/components` directory and put `foo.rb` in there.
+That’ll create the `app/components` directory and put `foo.rb` in there.
### rakefile(filename, data = nil, &block)
@@ -179,7 +179,7 @@ rake "db:migrate", env: 'production'
### route(routing_code)
-Adds a routing entry to the `config/routes.rb` file. In above steps, we generated a person scaffold and also removed `README.rdoc`. Now to make `PeopleController#index` as the default page for the application:
+Adds a routing entry to the `config/routes.rb` file. In the steps above, we generated a person scaffold and also removed `README.rdoc`. Now, to make `PeopleController#index` the default page for the application:
```ruby
route "root to: 'person#index'"
@@ -197,7 +197,7 @@ end
### ask(question)
-`ask()` gives you a chance to get some feedback from the user and use it in your templates. Lets say you want your user to name the new shiny library you’re adding:
+`ask()` gives you a chance to get some feedback from the user and use it in your templates. Let's say you want your user to name the new shiny library you’re adding:
```ruby
lib_name = ask("What do you want to call the shiny library ?")
@@ -211,7 +211,7 @@ CODE
### yes?(question) or no?(question)
-These methods let you ask questions from templates and decide the flow based on the user’s answer. Lets say you want to freeze rails only if the user want to:
+These methods let you ask questions from templates and decide the flow based on the user’s answer. Let's say you want to freeze rails only if the user wants to:
```ruby
rake("rails:freeze:gems") if yes?("Freeze rails gems?")
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index d8477d89e3..de8f3f483f 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -28,7 +28,10 @@ Rails on Rack
### Rails Application's Rack Object
-`ApplicationName::Application` is the primary Rack application object of a Rails application. Any Rack compliant web server should be using `ApplicationName::Application` object to serve a Rails application.
+`ApplicationName::Application` is the primary Rack application object of a Rails
+application. Any Rack compliant web server should be using
+`ApplicationName::Application` object to serve a Rails
+application. `Rails.application` refers to the same application object.
### `rails server`
@@ -79,11 +82,11 @@ To use `rackup` instead of Rails' `rails server`, you can put the following insi
```ruby
# Rails.root/config.ru
-require "config/environment"
+require ::File.expand_path('../config/environment', __FILE__)
use Rack::Debugger
use Rack::ContentLength
-run ApplicationName::Application
+run Rails.application
```
And start the server:
@@ -101,7 +104,7 @@ $ rackup --help
Action Dispatcher Middleware Stack
----------------------------------
-Many of Action Dispatchers's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application.
+Many of Action Dispatcher's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application.
NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements.
@@ -324,7 +327,7 @@ config.middleware.clear
```ruby
# config.ru
use MyOwnStackFromScratch
-run ApplicationName::Application
+run Rails.application
```
Resources
@@ -332,7 +335,7 @@ Resources
### Learning Rack
-* [Official Rack Website](http://rack.github.com)
+* [Official Rack Website](http://rack.github.io)
* [Introducing Rack](http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html)
* [Ruby on Rack #1 - Hello Rack!](http://m.onkey.org/ruby-on-rack-1-hello-rack)
* [Ruby on Rack #2 - The Builder](http://m.onkey.org/ruby-on-rack-2-the-builder)
diff --git a/guides/source/routing.md b/guides/source/routing.md
index d7a4a237ed..c26a827172 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -138,6 +138,12 @@ Sometimes, you have a resource that clients always look up without referencing a
get 'profile', to: 'users#show'
```
+Passing a `String` to `match` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action:
+
+```ruby
+get 'profile', to: :show
+```
+
This resourceful route:
```ruby
@@ -530,7 +536,7 @@ In particular, simple routing makes it very easy to map legacy URLs to new Rails
### 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:
+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 this route:
```ruby
get ':controller(/:action(/:id))'
@@ -850,8 +856,8 @@ resources :user_permissions, controller: 'admin/user_permissions'
This will route to the `Admin::UserPermissions` controller.
-NOTE: Only the directory notation is supported. specifying the
-controller with ruby constant notation (eg. `:controller =>
+NOTE: Only the directory notation is supported. Specifying the
+controller with Ruby constant notation (eg. `:controller =>
'Admin::UserPermissions'`) can lead to routing problems and results in
a warning.
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index a78711f4b2..d5d1ee0a38 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -63,9 +63,13 @@ Those guidelines apply also to guides.
HTML Guides
-----------
+Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device.
+
+To install the latest version of Bundler, simply run the `gem install bundler` command
+
### Generation
-To generate all the guides, just `cd` into the **`guides`** directory, run `bundle install` and execute:
+To generate all the guides, just `cd` into the `guides` directory, run `bundle install` and execute:
```
bundle exec rake guides:generate
diff --git a/guides/source/security.md b/guides/source/security.md
index 769bd130be..f04129acdb 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1,4 +1,4 @@
-Ruby On Rails Security Guide
+Ruby on Rails Security Guide
============================
This manual describes common security problems in web applications and how to avoid them with Rails.
@@ -58,7 +58,7 @@ WARNING: _Stealing a user's session id lets an attacker use the web application
Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session.
-Hence, the cookie serves as temporary authentication for the web application. Everyone who seizes a cookie from someone else, may use the web application as this user – with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
+Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user – with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
@@ -268,7 +268,7 @@ def legacy
end
```
-This will redirect the user to the main action if he tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can exploited by an attacker if he includes a host key in the URL:
+This will redirect the user to the main action if he tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can be exploited by an attacker if he includes a host key in the URL:
```
http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
@@ -432,7 +432,7 @@ Depending on your web application, there may be more ways to hijack the user's a
INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
-But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](http://ambethia.com/recaptcha/) is also a Rails plug-in with the same name as the API.
+But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails.
The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that he is human, but reveal that a spam robot is a bot.
@@ -942,7 +942,7 @@ Or you can remove them.
config.action_dispatch.default_headers.clear
```
-Here is the list of common headers:
+Here is a list of common headers:
* X-Frame-Options
_'SAMEORIGIN' in Rails by default_ - allow framing on same domain. Set it to 'DENY' to deny framing at all or 'ALLOWALL' if you want to allow framing for all website.
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 3b1c159aec..b02d0b663c 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -85,8 +85,8 @@ ERB allows you to embed Ruby code within templates. The YAML fixture format is p
```erb
<% 1000.times do |n| %>
user_<%= n %>:
- username: <%= "user%03d" % n %>
- email: <%= "user%03d@example.com" % n %>
+ username: <%= "user#{n}" %>
+ email: <%= "user#{n}@example.com" %>
<% end %>
```
@@ -159,9 +159,10 @@ class PostTest < ActiveSupport::TestCase
The `PostTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `PostTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide.
-Any method defined within a `Test::Unit` test case that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run.
+Any method defined within a class inherited from `MiniTest::Unit::TestCase`
+(which is the superclass of `ActiveSupport::TestCase`) that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run.
-Rails adds a `test` method that takes a test name and a block. It generates a normal `Test::Unit` test with method names prefixed with `test_`. So,
+Rails adds a `test` method that takes a test name and a block. It generates a normal `MiniTest::Unit` test with method names prefixed with `test_`. So,
```ruby
test "the truth" do
@@ -222,10 +223,10 @@ TIP: You can see all these rake tasks and their descriptions by running `rake --
### Running Tests
-Running a test is as simple as invoking the file containing the test cases through `rails test` command.
+Running a test is as simple as invoking the file containing the test cases through `rake test` command.
```bash
-$ rails test test/models/post_test.rb
+$ rake test test/models/post_test.rb
.
Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
@@ -233,10 +234,10 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
-You can also run a particular test method from the test case by running the test and using `-n` switch with the `test method name`.
+You can also run a particular test method from the test case by running the test and providing the `test method name`.
```bash
-$ rails test test/models/post_test.rb -n test_the_truth
+$ rake test test/models/post_test.rb test_the_truth
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
@@ -260,7 +261,7 @@ end
Let us run this newly added test.
```bash
-$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title
+$ rake test test/models/post_test.rb test_should_not_save_post_without_title
F
Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.
@@ -300,7 +301,7 @@ end
Now the test should pass. Let us verify by running the test again:
```bash
-$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title
+$ rake test test/models/post_test.rb test_should_not_save_post_without_title
.
Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
@@ -325,7 +326,7 @@ end
Now you can see even more output in the console from running the tests:
```bash
-$ rails test test/models/post_test.rb -n test_should_report_error
+$ rake test test/models/post_test.rb test_should_report_error
E
Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.
@@ -346,31 +347,38 @@ NOTE: The execution of each test method stops as soon as any error or an asserti
Ideally, you would like to include a test for everything which could possibly break. It's a good practice to have at least one test for each of your validations and at least one test for every method in your model.
-### Assertions Available
+### Available Assertions
By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned.
-There are a bunch of different types of assertions you can use. Here's the complete list of assertions that ship with `test/unit`, the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required.
+There are a bunch of different types of assertions you can use.
+Here's an extract of the assertions you can use with `minitest`, the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required.
| Assertion | Purpose |
| ---------------------------------------------------------------- | ------- |
-| `assert( boolean, [msg] )` | Ensures that the object/expression is true.|
+| `assert( test, [msg] )` | Ensures that `test` is true.|
+| `refute( test, [msg] )` | Ensures that `test` is false.|
| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.|
-| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
+| `refute_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.|
-| `assert_not_same( expected, actual, [msg] )` | Ensures that `!expected.equal?(actual)` is true.|
+| `refute_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.|
-| `assert_not_nil( obj, [msg] )` | Ensures that `!obj.nil?` is true.|
+| `refute_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.|
-| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
-| `assert_in_delta( expecting, actual, delta, [msg] )` | Ensures that the numbers `expecting` and `actual` are within `delta` of each other.|
+| `refute_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
+| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
+| `refute_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
-| `assert_raise( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
+| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.|
-| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is of the `class` type.|
+| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
+| `refute_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.|
-| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` has a method called `symbol`.|
-| `assert_operator( obj1, operator, obj2, [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
+| `refute_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.|
+| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.|
+| `refute_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
+| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
+| `refute_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?|
| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
@@ -475,7 +483,7 @@ NOTE: Functional tests do not verify whether the specified request type should b
### The Four Hashes of the Apocalypse
-After a request has been made by using one of the 5 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use:
+After a request has been made using one of the 6 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use:
* `assigns` - Any objects that are stored as instance variables in actions for use in views.
* `cookies` - Any cookies that are set.
@@ -690,9 +698,9 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
get "/login"
assert_response :success
- post_via_redirect "/login", username: users(:avs).username, password: users(:avs).password
+ post_via_redirect "/login", username: users(:david).username, password: users(:david).password
assert_equal '/welcome', path
- assert_equal 'Welcome avs!', flash[:notice]
+ assert_equal 'Welcome david!', flash[:notice]
https!(false)
get "/posts/all"
@@ -714,17 +722,17 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
test "login and browse site" do
- # User avs logs in
- avs = login(:avs)
+ # User david logs in
+ david = login(:david)
# User guest logs in
guest = login(:guest)
# Both are now available in different sessions
- assert_equal 'Welcome avs!', avs.flash[:notice]
+ assert_equal 'Welcome david!', david.flash[:notice]
assert_equal 'Welcome guest!', guest.flash[:notice]
- # User avs can browse site
- avs.browses_site
+ # User david can browse site
+ david.browses_site
# User guest can browse site as well
guest.browses_site
@@ -761,14 +769,14 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai
| Tasks | Description |
| ------------------------ | ----------- |
-| `rails test` | Runs all unit, functional and integration tests. You can also simply run `rails test` as Rails will run all the tests by default|
-| `rails test controllers` | Runs all the controller tests from `test/controllers`|
-| `rails test functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`|
-| `rails test helpers` | Runs all the helper tests from `test/helpers`|
-| `rails test integration` | Runs all the integration tests from `test/integration`|
-| `rails test mailers` | Runs all the mailer tests from `test/mailers`|
-| `rails test models` | Runs all the model tests from `test/models`|
-| `rails test units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`|
+| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake test` as Rails will run all the tests by default|
+| `rake test:controllers` | Runs all the controller tests from `test/controllers`|
+| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`|
+| `rake test:helpers` | Runs all the helper tests from `test/helpers`|
+| `rake test:integration` | Runs all the integration tests from `test/integration`|
+| `rake test:mailers` | Runs all the mailer tests from `test/mailers`|
+| `rake test:models` | Runs all the model tests from `test/models`|
+| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`|
There're also some test commands which you can initiate by running rake tasks:
@@ -997,5 +1005,6 @@ The built-in `test/unit` based testing is not the only way to test Rails applica
* [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use.
* [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures.
* [Machinist](https://github.com/notahat/machinist/tree/master), another replacement for fixtures.
+* [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests.
* [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions.
* [RSpec](http://relishapp.com/rspec), a behavior-driven development framework
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 8ad2e2bdb4..5a1594102a 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -22,6 +22,100 @@ Rails generally stays close to the latest released Ruby version when it's releas
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
+### HTTP PATCH
+
+Rails 4 now uses `PATCH` as the primary HTTP verb for updates when a RESTful
+resource is declared in `config/routes.rb`. The `update` action is still used,
+and `PUT` requests will continue to be routed to the `update` action as well.
+So, if you're using only the standard RESTful routes, no changes need to be made:
+
+```ruby
+resources :users
+```
+
+```erb
+<%= form_for @user do |f| %>
+```
+
+```ruby
+class UsersController < ApplicationController
+ def update
+ # No change needed; PATCH will be preferred, and PUT will still work.
+ end
+end
+```
+
+However, you will need to make a change if you are using `form_for` to update
+a resource in conjunction with a custom route using the `PUT` HTTP method:
+
+```ruby
+resources :users, do
+ put :update_name, on: :member
+end
+```
+
+```erb
+<%= form_for [ :update_name, @user ] do |f| %>
+```
+
+```ruby
+class UsersController < ApplicationController
+ def update_name
+ # Change needed; form_for will try to use a non-existant PATCH route.
+ end
+end
+```
+
+If the action is not being used in a public API and you are free to change the
+HTTP method, you can update your route to use `patch` instead of `put`:
+
+```ruby
+resources :users do
+ patch :update_name, on: :member
+end
+```
+
+If the action is being used in a public API and you can't change to HTTP method
+being used, you can update your form to use the `PUT` method instead:
+
+```erb
+<%= form_for [ :update_name, @user ], method: :put do |f| %>
+```
+
+For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/)
+on the Rails blog.
+
+#### A note about media types
+
+The errata for the `PATCH` verb [specifies that a 'diff' media type should be
+used with `PATCH`](http://www.rfc-editor.org/errata_search.php?rfc=5789). One
+such format is [JSON Patch](http://tools.ietf.org/html/rfc6902). While Rails
+does not support JSON Patch natively, it's easy enough to add support:
+
+```
+# in your controller
+def update
+ respond_to do |format|
+ format.json do
+ # perform a partial update
+ @post.update params[:post]
+ end
+
+ format.json_patch do
+ # perform sophisticated change
+ end
+ end
+end
+
+# In config/initializers/json_patch.rb:
+Mime::Type.register 'application/json-patch+json', :json_patch
+```
+
+As JSON Patch was only recently made into an RFC, there aren't a lot of great
+Ruby libraries yet. Aaron Patterson's
+[hana](https://github.com/tenderlove/hana) is one such gem, but doesn't have
+full support for the last few changes in the specification.
+
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------
@@ -31,6 +125,10 @@ If your application is currently on any version of Rails older than 3.2.x, you s
The following changes are meant for upgrading your application to Rails 4.0.
+### Gemfile
+
+Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that line from your Gemfile when upgrading.
+
### vendor/plugins
Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
@@ -88,17 +186,22 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
Please note that you should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. This is because cookies signed based on the new `secret_key_base` in Rails 4.x are not backwards compatible with Rails 3.x. You are free to leave your existing `secret_token` in place, not set the new `secret_key_base`, and ignore the deprecation warnings until you are reasonably sure that your upgrade is otherwise complete.
-* Rails 4.0 introduces a new `UpgradeSignatureToEncryptionCookieStore` cookie store. This is useful for upgrading apps using the old default `CookieStore` to the new default `EncryptedCookieStore` which leverages the new `ActiveSupport::KeyGenerator`. To use this transitional cookie store, you'll want to leave your existing `secret_token` in place, add a new `secret_key_base`, and change your `session_store` like so:
+If you are relying on the ability for external applications or Javascript to be able to read your Rails app's signed session cookies (or signed cookies in general) you should not set `secret_key_base` until you have decoupled these concerns.
-```ruby
- # config/initializers/session_store.rb
- Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: 'existing session key'
+* Rails 4.0 encrypts the contents of cookie-based sessions if `secret_key_base` has been set. Rails 3.x signed, but did not encrypt, the contents of cookie-based session. Signed cookies are "secure" in that they are verified to have been generated by your app and are tamper-proof. However, the contents can be viewed by end users, and encrypting the contents eliminates this caveat/concern without a significant performance penalty.
+
+As described above, existing signed cookies generated with Rails 3.x will be transparently upgraded if you leave your existing `secret_token` in place and add the new `secret_key_base`.
+```ruby
# config/initializers/secret_token.rb
Myapp::Application.config.secret_token = 'existing secret token'
Myapp::Application.config.secret_key_base = 'new secret key base'
```
+The same caveats apply here, too. You should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. You should also take care to make sure you are not relying on the ability to decode signed cookies generated by your app in external applications or Javascript before upgrading.
+
+Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for details on the move to encrypted session cookies.
+
* Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature.
* Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use `ActionController::Base.default_static_extension` instead.
@@ -109,7 +212,7 @@ Please note that you should wait to set `secret_key_base` until you have 100% of
* Rails 4.0 changes the default memcached client from `memcache-client` to `dalli`. To upgrade, simply add `gem 'dalli'` to your `Gemfile`.
-* Rails 4.0 deprecates the `dom_id` and `dom_class` methods. You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature.
+* Rails 4.0 deprecates the `dom_id` and `dom_class` methods in controllers (they are fine in views). You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature.
* Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 7c4192ee26..ddefaf6ff8 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -394,3 +394,4 @@ Here are some helpful links to help you learn even more:
* [jquery-ujs list of external articles](https://github.com/rails/jquery-ujs/wiki/External-articles)
* [Rails 3 Remote Links and Forms: A Definitive Guide](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/)
* [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript)
+* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks) \ No newline at end of file
diff --git a/rails.gemspec b/rails.gemspec
index 8a5d042361..4a17beac69 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -16,9 +16,7 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.bindir = 'bin'
- s.executables = []
- s.files = ['README.rdoc'] + Dir['guides/**/*']
+ s.files = ['README.md'] + Dir['guides/**/*']
s.add_dependency 'activesupport', version
s.add_dependency 'actionpack', version
@@ -27,5 +25,5 @@ Gem::Specification.new do |s|
s.add_dependency 'railties', version
s.add_dependency 'bundler', '>= 1.3.0', '< 2.0'
- s.add_dependency 'sprockets-rails', '~> 2.0.0.rc3'
+ s.add_dependency 'sprockets-rails', '~> 2.0.0'
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 2727f1a85d..23a7cf6ca3 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,351 +1,16 @@
-## Rails 4.0.0 (unreleased) ##
-
-* Allow vanilla apps to render CoffeeScript templates in production
-
- Vanilla apps already render CoffeeScript templates in development and test
- environments. With this change, the production behavior matches that of
- the other environments.
-
- Effectively, this meant moving coffee-rails (and the JavaScript runtime on
- which it is dependent) from the :assets group to the top-level of the
- generated Gemfile.
-
- *Gabe Kopley*
-
-* `Rails.version` now returns an instance of `Gem::Version`
-
- *Charlie Somerville*
-
-* Don't generate a scaffold.css when --no-assets is specified
-
- *Kevin Glowacz*
-
-* Add support for generate scaffold password:digest
-
- * adds password_digest attribute to the migration
- * adds has_secure_password to the model
- * adds password and password_confirmation password_fields to _form.html
- * omits password from index.html and show.html
- * adds password and password_confirmation to the controller
- * adds unencrypted password and password_confirmation to the controller test
- * adds encrypted password_digest to the fixture
-
- *Sam Ruby*
-
-* Rails now generates a `test/test_helper.rb` file with `fixtures :all` commented out by default,
- since we don't want to force loading all fixtures for user when a single test is run. However,
- fixtures are still going to be loaded automatically for test suites.
-
- To force all fixtures to be create in your database, use `rails test -f` to run your test.
-
- *Prem Sichanugrist*
-
-* Add `rails test` command for running tests
-
- To run all tests:
-
- $ rails test
-
- To run a test suite
-
- $ rails test [models,helpers,units,controllers,mailers,...]
-
- To run a selected test file(s):
-
- $ rails test test/unit/foo_test.rb [test/unit/bar_test.rb ...]
-
- To run a single test from a test file
-
- $ rails test test/unit/foo_test.rb -n test_the_truth
-
- For more information, see `rails test --help`.
-
- This command will eventually replace `rake test:*` and `rake test` tasks.
-
- *Prem Sichanugrist and Chris Toomey*
-
-* Improve service pages with new layout (404, etc). *Stanislav Sobolev*
-
-
-## Rails 4.0.0.beta1 (February 25, 2013) ##
-
-* Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments
- and calculates number of functions.
-
- *Hendy Tanata*
-
-* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed.
- Consider using application template instead. See this guide for more detail:
- http://guides.rubyonrails.org/rails_application_templates.html
-
- *Prem Sichanugrist*
-
-* Fix `rake db:*` tasks to work with `DATABASE_URL` and without `config/database.yml`.
-
- *Terence Lee*
-
-* Add notice message for destroy action in scaffold generator.
-
- *Rahul P. Chaudhari*
-
-* Add two new test rake tasks to speed up full test runs.
-
- * `test:all`: run tests quickly by merging all types and not resetting db.
- * `test:all:db`: run tests quickly, but also reset db.
-
- *Ryan Davis*
-
-* Add `--rc` option to support the load of a custom rc file during the generation of a new app.
-
- *Amparo Luna*
-
-* Add `--no-rc` option to skip the loading of railsrc file during the generation of a new app.
-
- *Amparo Luna*
-
-* Fixes database.yml when creating a new rails application with '.'
- Fixes #8304.
-
- *Jeremy W. Rowe*
-
-* Restore Rails::Engine::Railties#engines with deprecation to ensure
- compatibility with gems such as Thinking Sphinx
- Fixes #8551.
-
- *Tim Raymond*
-
-* Specify which logs to clear when using the `rake log:clear` task.
- (e.g. rake log:clear LOGS=test,staging)
-
- *Matt Bridges*
-
-* Allow a `:dirs` key in the `SourceAnnotationExtractor.enumerate` options
- to explicitly set the directories to be traversed so it's easier to define
- custom rake tasks.
-
- *Brian D. Burns*
-
-* Deprecate `Rails::Generators::ActiveModel#update_attributes` in favor of `#update`.
-
- ORMs that implement `Generators::ActiveModel#update_attributes` should change
- to `#update`. Scaffold controller generators should change calls like:
-
- @orm_instance.update_attributes(...)
-
- to:
-
- @orm_instance.update(...)
-
- This goes along with the addition of `ActiveRecord::Base#update`.
-
- *Carlos Antonio da Silva*
-
-* Include `jbuilder` by default and rely on its scaffold generator to show json API.
- Check https://github.com/rails/jbuilder for more info and examples.
-
- *DHH*
-
-* Scaffold now generates HTML-only controller by default.
-
- *DHH + Pavel Pravosud*
-
-* The generated `README.rdoc` for new applications invites the user to
- document the necessary steps to get the application up and running.
+* Clearing autoloaded constants triggers routes reloading [Fixes #10685].
*Xavier Noria*
-* Generated applications no longer get `doc/README_FOR_APP`. In consequence,
- the `doc` directory is created on demand by documentation tasks rather than
- generated by default.
-
- *Xavier Noria*
-
-* App executables now live in the `bin/` directory: `bin/bundle`,
- `bin/rails`, `bin/rake`. Run `rake rails:update:bin` to add these
- executables to your own app. `script/rails` is gone from new apps.
-
- Running executables within your app ensures they use your app's Ruby
- version and its bundled gems, and it ensures your production deployment
- tools only need to execute a single script. No more having to carefully
- `cd` to the app dir and run `bundle exec ...`.
-
- Rather than treating `bin/` as a junk drawer for generated "binstubs",
- bundler 1.3 adds support for generating stubs for just the executables
- you actually use: `bundle binstubs unicorn` generates `bin/unicorn`.
- Add that executable to git and version it just like any other app code.
-
- *Jeremy Kemper*
-
-* `config.assets.enabled` is now true by default. If you're upgrading from a Rails 3.x app
- that does not use the asset pipeline, you'll be required to add `config.assets.enabled = false`
- to your application.rb. If you don't want the asset pipeline on a new app use `--skip-sprockets`
-
- *DHH*
-
-* Environment name can be a start substring of the default environment names
- (production, development, test). For example: tes, pro, prod, dev, devel.
- Fixes #8628.
-
- *Mykola Kyryk*
-
-* Add `-B` alias for `--skip-bundle` option in the rails new generators.
-
- *Jiri Pospisil*
-
-* Quote column names in generates fixture files. This prevents
- conflicts with reserved YAML keywords such as 'yes' and 'no'
- Fixes #8612.
-
- *Yves Senn*
-
-* Explicit options have precedence over `~/.railsrc` on the `rails new` command.
-
- *Rafael Mendonça França*
-
-* Generated migrations now always use the `change` method.
-
- *Marc-André Lafortune*
-
-* Add `app/models/concerns` and `app/controllers/concerns` to the default directory structure and load path.
- See http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns for usage instructions.
-
- *DHH*
-
-* The `rails/info/routes` now correctly formats routing output as an html table.
-
- *Richard Schneeman*
-
-* The `public/index.html` is no longer generated for new projects.
- Page is replaced by internal `welcome_controller` inside of railties.
-
- *Richard Schneeman*
-
-* Add `ENV['RACK_ENV']` support to `rails runner/console/server`.
-
- *kennyj*
-
-* Add `db` to list of folders included by `rake notes` and `rake notes:custom`. *Antonio Cangiano*
-
-* Engines with a dummy app include the rake tasks of dependencies in the app namespace.
- Fixes #8229.
-
- *Yves Senn*
-
-* Add `sqlserver.yml` template file to satisfy `-d sqlserver` being passed to `rails new`.
- Fixes #6882.
-
- *Robert Nesius*
-
-* Rake test:uncommitted finds git directory in ancestors *Nicolas Despres*
-
-* Add dummy app Rake tasks when `--skip-test-unit` and `--dummy-path` is passed to the plugin generator.
- Fixes #8121.
-
- *Yves Senn*
-
-* Add `.rake` to list of file extensions included by `rake notes` and `rake notes:custom`. *Brent J. Nordquist*
-
-* New test locations `test/models`, `test/helpers`, `test/controllers`, and
- `test/mailers`. Corresponding rake tasks added as well. *Mike Moore*
-
-* Set a different cache per environment for assets pipeline
- through `config.assets.cache`.
-
- *Guillermo Iguaran*
-
-* `Rails.public_path` now returns a Pathname object. *Prem Sichanugrist*
-
-* Remove highly uncommon `config.assets.manifest` option for moving the manifest path.
- This option is now unsupported in sprockets-rails.
-
- *Guillermo Iguaran & Dmitry Vorotilin*
-
-* Add `config.action_controller.permit_all_parameters` to disable
- StrongParameters protection, it's false by default.
-
- *Guillermo Iguaran*
-
-* Remove `config.active_record.whitelist_attributes` and
- `config.active_record.mass_assignment_sanitizer` from new applications since
- MassAssignmentSecurity has been extracted from Rails.
-
- *Guillermo Iguaran*
-
-* Change `rails new` and `rails plugin new` generators to name the `.gitkeep` files
- as `.keep` in a more SCM-agnostic way.
-
- Change `--skip-git` option to only skip the `.gitignore` file and still generate
- the `.keep` files.
-
- Add `--skip-keeps` option to skip the `.keep` files.
-
- *Derek Prior & Francesco Rodriguez*
-
-* Fixed support for DATABASE_URL environment variable for rake db tasks. *Grace Liu*
-
-* rails dbconsole now can use SSL for MySQL. The database.yml options sslca, sslcert, sslcapath, sslcipher,
- and sslkey now affect rails dbconsole. *Jim Kingdon and Lars Petrus*
-
-* Correctly handle SCRIPT_NAME when generating routes to engine in application
- that's mounted at a sub-uri. With this behavior, you *should not* use
- default_url_options[:script_name] to set proper application's mount point by
- yourself. *Piotr Sarnacki*
-
-* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded *José Valim*
-
-* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
-
- rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
-
- will generate the migration with:
-
- add_reference :products, :user, index: true
- add_reference :products, :supplier, polymorphic: true, index: true
-
- *Aleksey Magusev*
-
-* Allow scaffold/model/migration generators to accept a `polymorphic` modifier
- for `references`/`belongs_to`, for instance
-
- rails g model Product supplier:references{polymorphic}
-
- will generate the model with `belongs_to :supplier, polymorphic: true`
- association and appropriate migration.
-
- *Aleksey Magusev*
-
-* Set `config.active_record.migration_error` to `:page_load` for development *Richard Schneeman*
-
-* Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj*
-
-* Add `/rails/info/routes` path, displays same information as `rake routes` *Richard Schneeman & Andrew White*
-
-* Improved `rake routes` output for redirects *Łukasz Strzałkowski & Andrew White*
-
-* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
-
-* Remove Rack::SSL in favour of ActionDispatch::SSL. *Rafael Mendonça França*
-
-* Remove Active Resource from Rails framework. *Prem Sichangrist*
-
-* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block. *Piotr Sarnacki*
-
- Example:
-
- # it can be added to config/application.rb
- console do
- # this block is called only when running console,
- # so we can safely require pry here
- require "pry"
- config.console = Pry
- end
+* Fixes bug with scaffold generator with `--assets=false --resource-route=false`.
+ Fixes #9525.
-* Add convenience `hide!` method to Rails generators to hide current generator
- namespace from showing when running `rails generate`. *Carlos Antonio da Silva*
+ *Arun Agrawal*
-* Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
+* Rails::Railtie no longer forces the Rails::Configurable module on everything
+ that subclasses it. Instead, the methods from Rails::Configurable have been
+ moved to class methods in Railtie and the Railtie has been made abstract.
-* Set config.action_mailer.async = true to turn on asynchronous
- message delivery *Brian Cardarella*
+ *John Wang*
-Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/railties/CHANGELOG.md) for previous changes.
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/README.rdoc b/railties/RDOC_MAIN.rdoc
index 55b5efc916..cadf0fb43e 100644
--- a/README.rdoc
+++ b/railties/RDOC_MAIN.rdoc
@@ -1,9 +1,9 @@
-== Welcome to Rails
+== Welcome to \Rails
-Rails is a web-application framework that includes everything needed to create
+\Rails is a web-application framework that includes everything needed to create
database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern.
-Understanding the MVC pattern is key to understanding Rails. MVC divides your application
+Understanding the MVC pattern is key to understanding \Rails. MVC divides your application
into three layers, each with a specific responsibility.
The View layer is composed of "templates" that are responsible for providing
@@ -12,32 +12,32 @@ can come in a variety of formats, but most view templates are \HTML with embedde
code (.erb files).
The Model layer represents your domain model (such as Account, Product, Person, Post)
-and encapsulates the business logic that is specific to your application. In Rails,
+and encapsulates the business logic that is specific to your application. In \Rails,
database-backed model classes are derived from ActiveRecord::Base. Active Record allows
you to present the data from database rows as objects and embellish these data objects
-with business logic methods. Although most Rails models are backed by a database, models
+with business logic methods. Although most \Rails models are backed by a database, models
can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as
provided by the ActiveModel module. You can read more about Active Record in its
{README}[link:/activerecord/README.rdoc].
The Controller layer is responsible for handling incoming HTTP requests and providing a
-suitable response. Usually this means returning \HTML, but Rails controllers can also
+suitable response. Usually this means returning \HTML, but \Rails controllers can also
generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models
and render view templates in order to generate the appropriate HTTP response.
-In Rails, the Controller and View layers are handled together by Action Pack.
+In \Rails, the Controller and View layers are handled together by Action Pack.
These two layers are bundled in a single package due to their heavy interdependence.
This is unlike the relationship between Active Record and Action Pack, which are
-independent. Each of these packages can be used independently outside of Rails. You
+independent. Each of these packages can be used independently outside of \Rails. You
can read more about Action Pack in its {README}[link:/actionpack/README.rdoc].
== Getting Started
-1. Install Rails at the command prompt if you haven't yet:
+1. Install \Rails at the command prompt if you haven't yet:
gem install rails
-2. At the command prompt, create a new Rails application:
+2. At the command prompt, create a new \Rails application:
rails new myapp
@@ -56,22 +56,18 @@ can read more about Action Pack in its {README}[link:/actionpack/README.rdoc].
5. Follow the guidelines to start developing your application. You may find the following resources handy:
* The README file created within your application.
-* {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html].
-* {Ruby on Rails Tutorial}[http://ruby.railstutorial.org/ruby-on-rails-tutorial-book].
-* {Ruby on Rails Guides}[http://guides.rubyonrails.org].
+* {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html].
+* {Ruby on \Rails Tutorial}[http://ruby.railstutorial.org/ruby-on-rails-tutorial-book].
+* {Ruby on \Rails Guides}[http://guides.rubyonrails.org].
* {The API Documentation}[http://api.rubyonrails.org].
== Contributing
-We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails
+We encourage you to contribute to Ruby on \Rails! Please check out the {Contributing to Rails
guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how
to proceed. {Join us}[http://contributors.rubyonrails.org]!
-== Code Status
-
-* {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[http://travis-ci.org/rails/rails]
-* {<img src="https://gemnasium.com/rails/rails.png?travis"/>}[https://gemnasium.com/rails/rails]
== License
-Ruby on Rails is released under the {MIT License}[http://www.opensource.org/licenses/MIT].
+Ruby on \Rails is released under the {MIT License}[http://www.opensource.org/licenses/MIT].
diff --git a/railties/Rakefile b/railties/Rakefile
index eb068fc526..4789f41ad4 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -12,8 +12,9 @@ task :test => 'test:isolated'
namespace :test do
task :isolated do
- dir = ENV["TEST_DIR"] || "**"
- Dir["test/#{dir}/*_test.rb"].each do |file|
+ dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",")
+ test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" }
+ Dir[*test_files].each do |file|
next true if file.include?("fixtures")
dash_i = [
'test',
@@ -22,7 +23,7 @@ namespace :test do
"#{File.dirname(__FILE__)}/../actionpack/lib",
"#{File.dirname(__FILE__)}/../activemodel/lib"
]
- ruby "-I#{dash_i.join ':'}", file
+ ruby "-w", "-I#{dash_i.join ':'}", file
end
end
end
diff --git a/railties/bin/rails b/railties/bin/rails
index a1c4faaa73..b3026e8a93 100755
--- a/railties/bin/rails
+++ b/railties/bin/rails
@@ -1,6 +1,8 @@
#!/usr/bin/env ruby
-if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git'))
+git_path = File.join(File.expand_path('../../..', __FILE__), '.git')
+
+if File.exists?(git_path)
railties_path = File.expand_path('../../lib', __FILE__)
$:.unshift(railties_path)
end
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 84f8b82ad5..3a3c11d8b3 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -3,7 +3,9 @@ require 'rails/ruby_version_check'
require 'pathname'
require 'active_support'
+require 'active_support/dependencies/autoload'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/array/extract_options'
require 'rails/application'
@@ -20,26 +22,22 @@ silence_warnings do
end
module Rails
- autoload :Info, 'rails/info'
- autoload :InfoController, 'rails/info_controller'
- autoload :WelcomeController, 'rails/welcome_controller'
+ extend ActiveSupport::Autoload
+
+ autoload :Info
+ autoload :InfoController
+ autoload :WelcomeController
class << self
attr_accessor :application, :cache, :logger
+ delegate :initialize!, :initialized?, to: :application
+
# The Configuration instance used to configure the Rails environment
def configuration
application.config
end
- def initialize!
- application.initialize!
- end
-
- def initialized?
- application.initialized?
- end
-
def backtrace_cleaner
@backtrace_cleaner ||= begin
# Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded
@@ -76,12 +74,16 @@ module Rails
env = Rails.env
groups.unshift(:default, env)
groups.concat ENV["RAILS_GROUPS"].to_s.split(",")
- groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) }
+ groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) }
groups.compact!
groups.uniq!
groups
end
+ def version
+ VERSION::STRING
+ end
+
def public_path
application && Pathname.new(application.paths["public"].first)
end
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 19c2226619..6c9c53fc69 100644
--- a/railties/lib/rails/all.rb
+++ b/railties/lib/rails/all.rb
@@ -1,9 +1,5 @@
require "rails"
-if defined?(Rake) && Rake.application.top_level_tasks.grep(/^test(?::|$)/).any?
- ENV['RAILS_ENV'] ||= 'test'
-end
-
%w(
active_record
action_controller
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
new file mode 100644
index 0000000000..c829873da4
--- /dev/null
+++ b/railties/lib/rails/api/task.rb
@@ -0,0 +1,158 @@
+require 'rdoc/task'
+
+module Rails
+ module API
+ class Task < RDoc::Task
+ RDOC_FILES = {
+ 'activesupport' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/active_support/**/*.rb
+ ),
+ :exclude => 'lib/active_support/vendor/*'
+ },
+
+ 'activerecord' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/active_record/**/*.rb
+ ),
+ :exclude => 'lib/active_record/vendor/*'
+ },
+
+ 'activemodel' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/active_model/**/*.rb
+ )
+ },
+
+ 'actionpack' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/abstract_controller/**/*.rb
+ lib/action_controller/**/*.rb
+ lib/action_dispatch/**/*.rb
+ lib/action_view/**/*.rb
+ ),
+ :exclude => 'lib/action_controller/vendor/*'
+ },
+
+ 'actionmailer' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/action_mailer/**/*.rb
+ ),
+ :exclude => 'lib/action_mailer/vendor/*'
+ },
+
+ 'railties' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ MIT-LICENSE
+ lib/**/*.rb
+ ),
+ :exclude => 'lib/rails/generators/rails/**/templates/**/*.rb'
+ }
+ }
+
+ def initialize(name)
+ super
+
+ # Every time rake runs this task is instantiated as all the rest.
+ # Be lazy computing stuff to have as light impact as possible to
+ # the rest of tasks.
+ before_running_rdoc do
+ load_and_configure_sdoc
+ configure_rdoc_files
+ setup_horo_variables
+ end
+ end
+
+ # Hack, ignore the desc calls performed by the original initializer.
+ def desc(description)
+ # no-op
+ end
+
+ def load_and_configure_sdoc
+ require 'sdoc'
+
+ self.title = 'Ruby on Rails API'
+ self.rdoc_dir = api_dir
+
+ options << '-m' << api_main
+ options << '-e' << 'UTF-8'
+
+ options << '-f' << 'sdoc'
+ options << '-T' << 'rails'
+ rescue LoadError
+ $stderr.puts %(Unable to load SDoc, please add\n\n gem 'sdoc', require: false\n\nto the Gemfile.)
+ exit 1
+ end
+
+ def configure_rdoc_files
+ rdoc_files.include(api_main)
+
+ RDOC_FILES.each do |component, cfg|
+ cdr = component_root_dir(component)
+
+ Array(cfg[:include]).each do |pattern|
+ rdoc_files.include("#{cdr}/#{pattern}")
+ end
+
+ Array(cfg[:exclude]).each do |pattern|
+ rdoc_files.exclude("#{cdr}/#{pattern}")
+ end
+ end
+ end
+
+ def setup_horo_variables
+ ENV['HORO_PROJECT_NAME'] = 'Ruby on Rails'
+ ENV['HORO_PROJECT_VERSION'] = rails_version
+ end
+
+ def api_main
+ component_root_dir('railties') + '/RDOC_MAIN.rdoc'
+ end
+ end
+
+ class RepoTask < Task
+ def load_and_configure_sdoc
+ super
+ options << '-g' # link to GitHub, SDoc flag
+ end
+
+ def component_root_dir(component)
+ component
+ end
+
+ def api_dir
+ 'doc/rdoc'
+ end
+
+ def rails_version
+ "master@#{`git rev-parse HEAD`[0, 7]}"
+ end
+ end
+
+ class AppTask < Task
+ def component_root_dir(gem_name)
+ $:.grep(%r{#{gem_name}[\w.-]*/lib\z}).first[0..-5]
+ end
+
+ def api_dir
+ 'doc/api'
+ end
+
+ def rails_version
+ Rails::VERSION::STRING
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb
index 44f4d3dabc..4a17803f1c 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_rails_loader.rb
@@ -4,34 +4,58 @@ module Rails
module AppRailsLoader
RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
EXECUTABLES = ['bin/rails', 'script/rails']
+ BUNDLER_WARNING = <<EOS
+Looks like your app's ./bin/rails is a stub that was generated by Bundler.
+
+In Rails 4, your app's bin/ directory contains executables that are versioned
+like any other source code, rather than stubs that are generated on demand.
+
+Here's how to upgrade:
+
+ bundle config --delete bin # Turn off Bundler's stub generator
+ rake rails:update:bin # Use the new Rails 4 executables
+ git add bin # Add bin/ to source control
+
+You may need to remove bin/ from your .gitignore as well.
+
+When you install a gem whose executable you want to use in your app,
+generate it and add it to source control:
+
+ bundle binstubs some-gem-name
+ git add bin/new-executable
+
+EOS
def self.exec_app_rails
- cwd = Dir.pwd
+ original_cwd = Dir.pwd
- exe = find_executable
- exe ||= find_executable_in_parent_path
- return unless exe
+ loop do
+ if exe = find_executable
+ contents = File.read(exe)
- exec RUBY, exe, *ARGV if find_executable
- Dir.chdir("..") do
- # Recurse in a chdir block: if the search fails we want to be sure
- # the application is generated in the original working directory.
- exec_app_rails unless cwd == Dir.pwd
- end
- rescue SystemCallError
- # could not chdir, no problem just return
- end
+ if contents =~ /(APP|ENGINE)_PATH/
+ exec RUBY, exe, *ARGV
+ break # non reachable, hack to be able to stub exec in the test suite
+ elsif exe.end_with?('bin/rails') && contents.include?('This file was generated by Bundler')
+ $stderr.puts(BUNDLER_WARNING)
+ Object.const_set(:APP_PATH, File.expand_path('config/application', Dir.pwd))
+ require File.expand_path('../boot', APP_PATH)
+ require 'rails/commands'
+ break
+ end
+ end
- def self.find_executable
- EXECUTABLES.find do |exe|
- File.exists?(exe) && File.read(exe) =~ /(APP|ENGINE)_PATH/
+ # If we exhaust the search there is no executable, this could be a
+ # call to generate a new application, so restore the original cwd.
+ Dir.chdir(original_cwd) and return if Pathname.new(Dir.pwd).root?
+
+ # Otherwise keep moving upwards in search of a executable.
+ Dir.chdir('..')
end
end
- def self.find_executable_in_parent_path(path = Pathname.new(Dir.pwd))
- EXECUTABLES.find do |exe|
- File.exists?(File.join(path, exe)) || !path.root? && find_executable_in_parent_path(path.parent)
- end
+ def self.find_executable
+ EXECUTABLES.find { |exe| File.exists?(exe) }
end
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 563905e8b3..914e4393c4 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,5 +1,5 @@
require 'fileutils'
-# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
require 'rails/engine'
@@ -93,6 +93,7 @@ module Rails
# dispatches the request to the underlying middleware stack.
def call(env)
env["ORIGINAL_FULLPATH"] = build_original_fullpath(env)
+ env["ORIGINAL_SCRIPT_NAME"] = env["SCRIPT_NAME"]
super(env)
end
@@ -111,7 +112,7 @@ module Rails
key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
ActiveSupport::CachingKeyGenerator.new(key_generator)
else
- ActiveSupport::DummyKeyGenerator.new(config.secret_token)
+ ActiveSupport::LegacyKeyGenerator.new(config.secret_token)
end
end
end
@@ -122,7 +123,8 @@ module Rails
#
# * "action_dispatch.parameter_filter" => config.filter_parameters
# * "action_dispatch.redirect_filter" => config.filter_redirect
- # * "action_dispatch.secret_token" => config.secret_token,
+ # * "action_dispatch.secret_token" => config.secret_token
+ # * "action_dispatch.secret_key_base" => config.secret_key_base
# * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
# * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local
# * "action_dispatch.logger" => Rails.logger
@@ -135,13 +137,12 @@ module Rails
#
def env_config
@app_env_config ||= begin
- if config.secret_key_base.nil?
- ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base in config/initializers/secret_token.rb file. " +
- "This should be used instead of the old deprecated config.secret_token in order to use the new EncryptedCookieStore. " +
- "To convert safely to the encrypted store (without losing existing cookies and sessions), see http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#action-pack"
+ if config.secret_key_base.blank?
+ ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " +
+ "Read the upgrade documentation to learn more about this new config option."
if config.secret_token.blank?
- raise "You must set config.secret_key_base in your app's config"
+ raise "You must set config.secret_key_base in your app's config."
end
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 3ae60312c5..7a1bb1e25c 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -62,17 +62,28 @@ module Rails
ActiveSupport.run_load_hooks(:after_initialize, self)
end
- # Set app reload just after the finisher hook to ensure
- # routes added in the hook are still loaded.
+ # Set routes reload after the finisher hook to ensure routes added in
+ # the hook are taken into account.
initializer :set_routes_reloader_hook do
reloader = routes_reloader
reloader.execute_if_updated
self.reloaders << reloader
- ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
+ ActionDispatch::Reloader.to_prepare do
+ # We configure #execute rather than #execute_if_updated because if
+ # autoloaded constants are cleared we need to reload routes also in
+ # case any was used there, as in
+ #
+ # mount MailPreview => 'mail_view'
+ #
+ # This means routes are also reloaded if i18n is updated, which
+ # might not be necessary, but in order to be more precise we need
+ # some sort of reloaders dependency support, to be added.
+ reloader.execute
+ end
end
- # Set app reload just after the finisher hook to ensure
- # paths added in the hook are still loaded.
+ # Set clearing dependencies after the finisher hook to ensure paths
+ # added in the hook are taken into account.
initializer :set_clear_dependencies_hook, group: :all do
callback = lambda do
ActiveSupport::DescendantsTracker.clear
@@ -82,9 +93,17 @@ module Rails
if config.reload_classes_only_on_change
reloader = config.file_watcher.new(*watchable_args, &callback)
self.reloaders << reloader
- # We need to set a to_prepare callback regardless of the reloader result, i.e.
- # models should be reloaded if any of the reloaders (i18n, routes) were updated.
- ActionDispatch::Reloader.to_prepare(prepend: true){ reloader.execute }
+
+ # Prepend this callback to have autoloaded constants cleared before
+ # any other possible reloading, in case they need to autoload fresh
+ # constants.
+ ActionDispatch::Reloader.to_prepare(prepend: true) do
+ # In addition to changes detected by the file watcher, if routes
+ # or i18n have been updated we also need to clear constants,
+ # that's why we run #execute rather than #execute_if_updated, this
+ # callback has to clear autoloaded constants after any update.
+ reloader.execute
+ end
else
ActionDispatch::Reloader.to_cleanup(&callback)
end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 41d3722c18..e8c42b149b 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -66,7 +66,7 @@ when 'console'
Rails::Console.start(Rails.application, options)
when 'server'
- # Change to the application's path if there is no config.ru file in current dir.
+ # Change to the application's path if there is no config.ru file in current directory.
# This allows us to run `rails server` from other directories, but still get
# the main config.ru and properly set the tmp directory.
Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))
@@ -80,15 +80,6 @@ when 'server'
server.start
end
-when 'test'
- $LOAD_PATH.unshift("./test")
- require 'rails/commands/test_runner'
- options = Rails::TestRunner.parse_arguments(ARGV)
- ENV['RAILS_ENV'] ||= options[:environment] || 'test'
-
- require APP_PATH
- Rails::TestRunner.start(ARGV, options)
-
when 'dbconsole'
require 'rails/commands/dbconsole'
Rails::DBConsole.start
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index d7b8ff4439..2ff29418c6 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -1,7 +1,7 @@
require 'rails/version'
if ['--version', '-v'].include?(ARGV.first)
- puts "Rails #{Rails.version}"
+ puts "Rails #{Rails::VERSION::STRING}"
exit(0)
end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 96229bb4f6..f6bdf129d6 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -94,8 +94,8 @@ module Rails
require 'debugger'
puts "=> Debugger enabled"
rescue LoadError
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
- exit
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
+ exit(1)
end
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index e3119ecf22..87d6505ed5 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -63,6 +63,9 @@ module Rails
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Run `rails server -h` for more startup options"
+ if options[:Host].to_s.match(/0\.0\.0\.0/)
+ puts "=> Notice: server is listening on all interfaces (#{options[:Host]}). Consider using 127.0.0.1 (--binding option)"
+ end
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/railties/lib/rails/commands/test_runner.rb b/railties/lib/rails/commands/test_runner.rb
deleted file mode 100644
index d8857bd183..0000000000
--- a/railties/lib/rails/commands/test_runner.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-require 'optparse'
-require 'minitest/unit'
-
-module Rails
- # Handles all logic behind +rails test+ command.
- class TestRunner
- class << self
- # Creates a new +TestRunner+ object with an array of test files to run
- # based on the arguments. When no arguments are provided, it runs all test
- # files. When a suite argument is provided, it runs only the test files in
- # that suite. Otherwise, it runs the specified test file(s).
- def start(files, options = {})
- original_fixtures_options = options.delete(:fixtures)
- options[:fixtures] = true
-
- case files.first
- when nil
- new(Dir['test/**/*_test.rb'], options).run
- when 'models'
- new(Dir['test/models/**/*_test.rb'], options).run
- when 'helpers'
- new(Dir['test/helpers/**/*_test.rb'], options).run
- when 'units'
- new(Dir['test/{models,helpers,unit}/**/*_test.rb'], options).run
- when 'controllers'
- new(Dir['test/controllers/**/*_test.rb'], options).run
- when 'mailers'
- new(Dir['test/mailers/**/*_test.rb'], options).run
- when 'functionals'
- new(Dir['test/{controllers,mailers,functional}/**/*_test.rb'], options).run
- when 'integration'
- new(Dir['test/integration/**/*_test.rb'], options).run
- else
- options[:fixtures] = original_fixtures_options
- new(files, options).run
- end
- end
-
- # Parses arguments and sets them as option flags
- def parse_arguments(arguments)
- options = {}
- orig_arguments = arguments.dup
-
- OptionParser.new do |opts|
- opts.banner = "Usage: rails test [path to test file(s) or test suite]"
-
- opts.separator ""
- opts.separator "Run a specific test file(s) or a test suite, under Rails'"
- opts.separator "environment. If the file name(s) or suit name is omitted,"
- opts.separator "Rails will run all tests."
- opts.separator ""
- opts.separator "Specific options:"
-
- opts.on '-h', '--help', 'Display this help.' do
- puts opts
- exit
- end
-
- opts.on '-e', '--environment NAME', String, 'Specifies the environment to run this test under' do |e|
- options[:environment] = e
- end
-
- opts.on '-f', '--fixtures', 'Load fixtures in test/fixtures/ before running the tests' do
- options[:fixtures] = true
- end
-
- opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m|
- options[:seed] = m.to_i
- end
-
- opts.on '-v', '--verbose', "Verbose. Show progress processing files." do
- options[:verbose] = true
- end
-
- opts.on '-n', '--name PATTERN', "Filter test names on pattern (e.g. /foo/)" do |n|
- options[:filter] = n
- end
-
- opts.separator ""
- opts.separator "Support types of test suites:"
- opts.separator "-------------------------------------------------------------"
- opts.separator "* models (test/models/**/*)"
- opts.separator "* helpers (test/helpers/**/*)"
- opts.separator "* units (test/{models,helpers,unit}/**/*"
- opts.separator "* controllers (test/controllers/**/*)"
- opts.separator "* mailers (test/mailers/**/*)"
- opts.separator "* functionals (test/{controllers,mailers,functional}/**/*)"
- opts.separator "* integration (test/integration/**/*)"
- opts.separator "-------------------------------------------------------------"
-
- opts.parse! arguments
- orig_arguments -= arguments
- end
- options
- end
- end
-
- # Creates a new +TestRunner+ object with a list of test file paths.
- def initialize(files, options)
- @files = files
-
- Rails.application.load_tasks
- Rake::Task['db:test:load'].invoke
-
- if options.delete(:fixtures)
- if defined?(ActiveRecord::Base)
- ActiveSupport::TestCase.send :include, ActiveRecord::TestFixtures
- ActiveSupport::TestCase.fixture_path = "#{Rails.root}/test/fixtures/"
- ActiveSupport::TestCase.fixtures :all
- end
- end
-
- MiniTest::Unit.runner.options = options
- MiniTest::Unit.output = SilentUntilSyncStream.new(MiniTest::Unit.output)
- end
-
- # Runs test files by evaluating each of them.
- def run
- @files.each { |filename| load(filename) }
- end
-
- # A null stream object which ignores everything until +sync+ has been set
- # to true. This is only used to silence unnecessary output from MiniTest,
- # as MiniTest calls +output.sync = true+ right before it outputs the first
- # test result.
- class SilentUntilSyncStream < File
- # Creates a +SilentUntilSyncStream+ object by giving it a target stream
- # object that will be assigned to +MiniTest::Unit.output+ after +sync+ is
- # set to true.
- def initialize(target_stream)
- @target_stream = target_stream
- super(File::NULL, 'w')
- end
-
- # Swaps +MiniTest::Unit.output+ to another stream when +sync+ is true.
- def sync=(sync)
- if sync
- @target_stream.sync = true
- MiniTest::Unit.output = @target_stream
- end
-
- super
- end
- end
- end
-end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 579af8c6a5..8000fc3b1e 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -107,7 +107,7 @@ module Rails
#
# The <tt>Application</tt> class adds a couple more paths to this set. And as in your
# <tt>Application</tt>, all folders under +app+ are automatically added to the load path.
- # If you have an <tt>app/services/tt> folder for example, it will be added by default.
+ # If you have an <tt>app/services</tt> folder for example, it will be added by default.
#
# == Endpoint
#
@@ -124,7 +124,7 @@ module Rails
#
# Now you can mount your engine in application's routes just like that:
#
- # MyRailsApp::Application.routes.draw do
+ # Rails.application.routes.draw do
# mount MyEngine::Engine => "/engine"
# end
#
@@ -154,7 +154,7 @@ module Rails
# Note that now there can be more than one router in your application, and it's better to avoid
# passing requests through many routers. Consider this situation:
#
- # MyRailsApp::Application.routes.draw do
+ # Rails.application.routes.draw do
# mount MyEngine::Engine => "/blog"
# get "/blog/omg" => "main#omg"
# end
@@ -164,7 +164,7 @@ module Rails
# and if there is no such route in +Engine+'s routes, it will be dispatched to <tt>main#omg</tt>.
# It's much better to swap that:
#
- # MyRailsApp::Application.routes.draw do
+ # Rails.application.routes.draw do
# get "/blog/omg" => "main#omg"
# mount MyEngine::Engine => "/blog"
# end
@@ -251,7 +251,7 @@ module Rails
# created to allow you to do that. Consider such a scenario:
#
# # config/routes.rb
- # MyApplication::Application.routes.draw do
+ # Rails.application.routes.draw do
# mount MyEngine::Engine => "/my_engine", as: "my_engine"
# get "/foo" => "foo#index"
# end
@@ -447,7 +447,7 @@ module Rails
self
end
- # Load rails generators and invoke the registered hooks.
+ # Load Rails generators and invoke the registered hooks.
# Check <tt>Rails::Railtie.generators</tt> for more info.
def load_generators(app=self)
require "rails/generators"
@@ -553,7 +553,7 @@ module Rails
#
# This needs to be an initializer, since it needs to run once
# per engine and get the engine as a block parameter
- initializer :set_autoload_paths, before: :bootstrap_hook do |app|
+ initializer :set_autoload_paths, before: :bootstrap_hook do
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
@@ -634,6 +634,10 @@ module Rails
end
end
+ def routes? #:nodoc:
+ @routes
+ end
+
protected
def run_tasks_blocks(*) #:nodoc:
@@ -641,10 +645,6 @@ module Rails
paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
- def routes? #:nodoc:
- @routes
- end
-
def has_migrations? #:nodoc:
paths["db/migrate"].existent.any?
end
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index 072d16291b..f39f926109 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -27,7 +27,7 @@ else
puts <<-EOT
Usage: rails COMMAND [ARGS]
-The common rails commands available for engines are:
+The common Rails commands available for engines are:
generate Generate new code (short-cut alias: "g")
destroy Undo code generated with "generate" (short-cut alias: "d")
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 28593c5907..366c72ebaa 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -86,7 +86,7 @@ module Rails
# end
def environment(data=nil, options={}, &block)
sentinel = /class [a-z_:]+ < Rails::Application/i
- env_file_sentinel = /::Application\.configure do/
+ env_file_sentinel = /Rails\.application\.configure do/
data = block.call if !data && block_given?
in_root do
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 25baa470d7..675ada7ed0 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,7 +1,7 @@
require 'digest/md5'
require 'securerandom'
require 'active_support/core_ext/string/strip'
-require 'rails/version' unless defined?(Rails.version)
+require 'rails/version' unless defined?(Rails::VERSION)
require 'rbconfig'
require 'open-uri'
require 'uri'
@@ -115,7 +115,11 @@ module Rails
end
def database_gemfile_entry
- options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
+ options[:skip_active_record] ? "" :
+ <<-GEMFILE.strip_heredoc
+ # Use #{options[:database]} as the database for Active Record
+ gem '#{gem_for_database}'
+ GEMFILE
end
def include_all_railties?
@@ -131,18 +135,16 @@ module Rails
<<-GEMFILE.strip_heredoc
gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}'
gem 'arel', github: 'rails/arel'
- gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
- gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders'
GEMFILE
else
<<-GEMFILE.strip_heredoc
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
- gem 'rails', '#{Rails.version}'
+ gem 'rails', '#{Rails::VERSION::STRING}'
GEMFILE
end
end
@@ -178,48 +180,57 @@ module Rails
return if options[:skip_sprockets]
gemfile = if options.dev? || options.edge?
- <<-GEMFILE.gsub(/^ {12}/, '')
- # Gems used for assets
+ <<-GEMFILE.strip_heredoc
+ # Use edge version of sprockets-rails
gem 'sprockets-rails', github: 'rails/sprockets-rails'
- gem 'sass-rails', github: 'rails/sass-rails'
- gem 'uglifier', '>= 1.0.3'
+
+ # Use SCSS for stylesheets
+ gem 'sass-rails', github: 'rails/sass-rails'
GEMFILE
else
- <<-GEMFILE.gsub(/^ {12}/, '')
- # Gems used for assets
- gem 'sass-rails', '~> 4.0.0.beta1'
- gem 'uglifier', '>= 1.0.3'
+ <<-GEMFILE.strip_heredoc
+ # Use SCSS for stylesheets
+ gem 'sass-rails', '~> 4.0.0.rc1'
GEMFILE
end
+ gemfile += <<-GEMFILE.strip_heredoc
+
+ # Use Uglifier as compressor for JavaScript assets
+ gem 'uglifier', '>= 1.3.0'
+ GEMFILE
+
if options[:skip_javascript]
- gemfile += <<-GEMFILE.gsub(/^ {12}/, '')
+ gemfile += <<-GEMFILE
#{coffee_gemfile_entry}
#{javascript_runtime_gemfile_entry}
GEMFILE
end
- gemfile.strip_heredoc.gsub(/^[ \t]*$/, '')
+ gemfile.gsub(/^[ \t]+/, '')
end
def coffee_gemfile_entry
if options.dev? || options.edge?
- "gem 'coffee-rails', github: 'rails/coffee-rails'"
+ <<-GEMFILE
+ # Use CoffeeScript for .js.coffee assets and views
+ gem 'coffee-rails', github: 'rails/coffee-rails'
+ GEMFILE
else
- "gem 'coffee-rails', '~> 4.0.0.beta1'"
+ <<-GEMFILE
+ # Use CoffeeScript for .js.coffee assets and views
+ gem 'coffee-rails', '~> 4.0.0'
+ GEMFILE
end
end
def javascript_gemfile_entry
- args = {'jquery' => ", github: 'rails/jquery-rails'"}
-
unless options[:skip_javascript]
- <<-GEMFILE.gsub(/^ {12}/, '').strip_heredoc
- #{javascript_runtime_gemfile_entry}
- # Use CoffeeScript for .js.coffee assets and views
+ <<-GEMFILE.gsub(/^[ \t]+/, '')
#{coffee_gemfile_entry}
-
- gem '#{options[:javascript]}-rails'#{args[options[:javascript]]}
+ #{javascript_runtime_gemfile_entry}
+ # Use #{options[:javascript]} as the JavaScript library
+ gem '#{options[:javascript]}-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
@@ -233,7 +244,7 @@ module Rails
else
"# gem 'therubyracer', platforms: :ruby"
end
- <<-GEMFILE.gsub(/^ {10}/, '')
+ <<-GEMFILE
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
#{runtime}
GEMFILE
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 85a1b01cc6..1799e823b6 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -14,14 +14,14 @@
<% attributes.each do |attribute| -%>
<div class="field">
<% if attribute.password_digest? -%>
- <%%= f.label :password %><br />
+ <%%= f.label :password %><br>
<%%= f.password_field :password %>
</div>
<div>
- <%%= f.label :password_confirmation %><br />
+ <%%= f.label :password_confirmation %><br>
<%%= f.password_field :password_confirmation %>
<% else -%>
- <%%= f.label :<%= attribute.name %> %><br />
+ <%%= f.label :<%= attribute.name %> %><br>
<%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
<% end -%>
</div>
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 d2fd99fdcb..9d778642f2 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -14,18 +14,18 @@
<tbody>
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
- <tr>
+ <tr>
<% attributes.reject(&:password_digest?).each do |attribute| -%>
- <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
+ <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 %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
+ <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 %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ </tr>
<%% end %>
</tbody>
</table>
-<br />
+<br>
<%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 8b4f52bb3b..e712c747b0 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -18,6 +18,8 @@ module Rails
parse_attributes! if respond_to?(:attributes)
end
+ # Defines the template that would be used for the migration file.
+ # The arguments include the source template file, the migration filename etc.
no_tasks do
def template(source, *args, &block)
inside_template do
@@ -40,7 +42,7 @@ module Rails
def indent(content, multiplier = 2)
spaces = " " * multiplier
- content = content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
+ content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
end
def wrap_with_namespace(content)
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index b2d1be9b51..d48dcf9ef3 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -56,6 +56,7 @@ module Rails
def app
directory 'app'
+ keep_file 'app/assets/images'
keep_file 'app/mailers'
keep_file 'app/models'
@@ -150,7 +151,15 @@ module Rails
desc: "Show Rails version number and quit"
def initialize(*args)
- raise Error, "Options should be given after the application name. For details run: rails --help" if args[0].blank?
+ if args[0].blank?
+ if args[1].blank?
+ # rails new
+ raise Error, "Application name should be provided in arguments. For details run: rails --help"
+ else
+ # rails new --skip-bundle my_new_application
+ raise Error, "Options should be given after the application name. For details run: rails --help"
+ end
+ end
super
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index f6bd107eba..577ff651e5 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -4,24 +4,27 @@ source 'https://rubygems.org'
<%= database_gemfile_entry -%>
-<%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%>
-
<%= assets_gemfile_entry %>
<%= javascript_gemfile_entry -%>
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 1.0.1'
+gem 'jbuilder', '~> 1.2'
+
+group :doc do
+ # bundle exec rake doc:rails generates the API under doc/api.
+ gem 'sdoc', require: false
+end
-# To use ActiveModel has_secure_password
+# Use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
# Use unicorn as the app server
# gem 'unicorn'
-# Deploy with Capistrano
+# Use Capistrano for deployment
# gem 'capistrano', group: :development
<% unless defined?(JRUBY_VERSION) -%>
-# To use debugger
+# Use debugger
# gem 'debugger', group: [:development, :test]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 6eb23f68a3..ba6b733dd2 100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -3,4 +3,4 @@
require File.expand_path('../config/application', __FILE__)
-<%= app_const %>.load_tasks
+Rails.application.load_tasks
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png b/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png
deleted file mode 100644
index d5edc04e65..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png
+++ /dev/null
Binary files differ
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index 7342bffd9d..8b91313e51 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -7,8 +7,8 @@
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
-// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-// GO AFTER THE REQUIRES BELOW.
+// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
+// about supported directives.
//
<% unless options[:skip_javascript] -%>
//= require <%= options[:javascript] %>
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 daf399a538..4d474d5267 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -4,6 +4,7 @@ require File.expand_path('../boot', __FILE__)
require 'rails/all'
<% else -%>
# Pick the frameworks you want:
+require "active_model/railtie"
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
@@ -11,8 +12,9 @@ require "action_mailer/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
-# Assets should be precompiled for production (so we don't need the gems loaded then)
-Bundler.require(*Rails.groups(assets: %w(development test)))
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env)
module <%= app_const_base %>
class Application < Rails::Application
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
index e080ebd74e..ee8d90dc65 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
@@ -1,5 +1,5 @@
-# Load the rails application.
+# Load the Rails application.
require File.expand_path('../application', __FILE__)
-# Initialize the rails application.
-<%= app_const %>.initialize!
+# Initialize the Rails application.
+Rails.application.initialize!
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 8b64881dbc..91253d1508 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -1,4 +1,4 @@
-<%= app_const %>.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
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 c40eef145f..1dfc9f136b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -1,4 +1,4 @@
-<%= app_const %>.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
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 3c9c787948..ba0742f97f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -1,4 +1,4 @@
-<%= app_const %>.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
@@ -13,7 +13,7 @@
config.eager_load = false
# Configure static asset server for tests with Cache-Control for performance.
- config.serve_static_assets = true
+ config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
# Show full error reports and disable caching.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
index efccf72d3d..f3cc6098a3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -9,4 +9,4 @@
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
-<%= app_const %>.config.secret_key_base = '<%= app_secret %>'
+Rails.application.config.secret_key_base = '<%= app_secret %>'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
index df07de9922..2bb9b82c61 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-<%= app_const %>.config.session_store :encrypted_cookie_store, key: <%= "'_#{app_name}_session'" %>
+Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index 4f1d56cd2f..f2110c2c70 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -7,8 +7,8 @@
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
end
-
<%- unless options.skip_active_record? -%>
+
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
# self.include_root_in_json = true
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 f877fa1f8a..3dfb724164 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,4 +1,4 @@
-<%= app_const %>.routes.draw do
+Rails.application.routes.draw do
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
@@ -39,6 +39,13 @@
# get 'recent', on: :collection
# end
# end
+
+ # Example resource route with concerns:
+ # concern :toggleable do
+ # post 'toggle'
+ # end
+ # resources :posts, concerns: :toggleable
+ # resources :photos, concerns: :toggleable
# Example resource route within a namespace:
# namespace :admin do
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 25a742dff0..6a502e997f 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -1,4 +1,4 @@
-# See http://help.github.com/ignore-files/ for more about ignoring files.
+# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index ca40914d3b..4fd060341e 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -6,12 +6,11 @@ class ActiveSupport::TestCase
<% unless options[:skip_active_record] -%>
ActiveRecord::Migration.check_pending!
- # Uncomment the `fixtures :all` line below to setup all fixtures in test/fixtures/*.yml
- # for all tests in alphabetical order.
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
- # fixtures :all
+ fixtures :all
<% end -%>
# Add more helper methods to be used by all tests here...
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 6574200fbf..145d9ee6e0 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -46,26 +46,32 @@ Available field types:
`rails generate model photo title:string album:references`
- It will generate an album_id column. You should generate this kind of fields when
- you will use a `belongs_to` association for instance. `references` also support
- the polymorphism, you could enable the polymorphism like this:
+ It will generate an `album_id` column. You should generate these kinds of fields when
+ you will use a `belongs_to` association, for instance. `references` also supports
+ polymorphism, you can enable polymorphism like this:
`rails generate model product supplier:references{polymorphic}`
- You can also specify some options just after the field type. You can use the
- following options:
+ For integer, string, text and binary fields, an integer in curly braces will
+ be set as the limit:
- limit Set the maximum size of the field giving a number between curly braces
- default Set a default value for the field
- precision Defines the precision for the decimal fields
- scale Defines the scale for the decimal fields
- uniq Defines the field values as unique
- index Will add an index on the field
+ `rails generate model user pseudo:string{30}`
- Examples:
+ For decimal, two integers separated by a comma in curly braces will be used
+ for precision and scale:
+
+ `rails generate model product price:decimal{10,2}`
+
+ You can add a `:uniq` or `:index` suffix for unique or standard indexes
+ respectively:
- `rails generate model user pseudo:string{30}`
`rails generate model user pseudo:string:uniq`
+ `rails generate model user pseudo:string:index`
+
+ You can combine any single curly brace option with the index options:
+
+ `rails generate model user username:string{30}:uniq`
+ `rails generate model product supplier:references{polymorphic}:index`
Examples:
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 5fe01d0961..850c9d5c0d 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
@@ -106,8 +106,6 @@ task default: :test
remove_file "doc"
remove_file "Gemfile"
remove_file "lib/tasks"
- remove_file "app/assets/images/rails.png"
- remove_file "public/index.html"
remove_file "public/robots.txt"
remove_file "README"
remove_file "test"
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 6373ca711e..5fdf0e1554 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -12,13 +12,14 @@ Gem::Specification.new do |s|
s.homepage = "TODO"
s.summary = "TODO: Summary of <%= camelized %>."
s.description = "TODO: Description of <%= camelized %>."
+ s.license = "MIT"
s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
<% unless options.skip_test_unit? -%>
s.test_files = Dir["test/**/*"]
<% end -%>
- <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails.version %>"
+ <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>"
<% unless options[:skip_active_record] -%>
s.add_development_dependency "<%= gem_for_database %>"
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index de00ab057d..3f2b78f2fd 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -1,7 +1,7 @@
source "https://rubygems.org"
<% if options[:skip_gemspec] -%>
-<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails.version %>"
+<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>"
<% else -%>
# Declare your gem's dependencies in <%= name %>.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt
index aa87d1b50c..c8de9f3e0f 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt
@@ -1,4 +1,4 @@
-# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
+# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__)
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js
index 084d5d1c49..5bc2e1c8b5 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js
@@ -7,7 +7,7 @@
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
-// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-// GO AFTER THE REQUIRES BELOW.
+// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
+// about supported directives.
//
//= require_tree .
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index 2a0522e81c..e89789e72b 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -8,6 +8,8 @@ module Rails
class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets"
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
+ class_option :assets, type: :boolean
+ class_option :resource_route, type: :boolean
def handle_skip
@options = @options.merge(stylesheets: false) unless options[:assets]
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 85a8914ccc..58592b4f8e 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -1,8 +1,7 @@
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/core_ext/kernel/reporting'
require 'rails/generators'
+require 'rails/generators/testing/behaviour'
+require 'rails/generators/testing/setup_and_teardown'
+require 'rails/generators/testing/assertions'
require 'fileutils'
module Rails
@@ -27,215 +26,11 @@ module Rails
# setup :prepare_destination
# end
class TestCase < ActiveSupport::TestCase
+ include Rails::Generators::Testing::Behaviour
+ include Rails::Generators::Testing::SetupAndTeardown
+ include Rails::Generators::Testing::Assertions
include FileUtils
- class_attribute :destination_root, :current_path, :generator_class, :default_arguments
-
- # 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.
- self.current_path = File.expand_path(Dir.pwd)
- self.default_arguments = []
-
- def setup # :nodoc:
- destination_root_is_set?
- ensure_current_path
- super
- end
-
- def teardown # :nodoc:
- ensure_current_path
- super
- end
-
- # Sets which generator should be tested:
- #
- # tests AppGenerator
- def self.tests(klass)
- self.generator_class = klass
- end
-
- # Sets default arguments on generator invocation. This can be overwritten when
- # invoking it.
- #
- # arguments %w(app_name --skip-active-record)
- def self.arguments(array)
- self.default_arguments = array
- end
-
- # Sets the destination of generator files:
- #
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
- def self.destination(path)
- self.destination_root = path
- end
-
- # Asserts a given file exists. You need to supply an absolute path or a path relative
- # to the configured destination:
- #
- # assert_file "config/environment.rb"
- #
- # You can also give extra arguments. If the argument is a regexp, it will check if the
- # regular expression matches the given file content. If it's a string, it compares the
- # file with the given string:
- #
- # assert_file "config/environment.rb", /initialize/
- #
- # Finally, when a block is given, it yields the file content:
- #
- # assert_file "app/controllers/products_controller.rb" do |controller|
- # assert_instance_method :index, controller do |index|
- # assert_match(/Product\.all/, index)
- # end
- # end
- def assert_file(relative, *contents)
- absolute = File.expand_path(relative, destination_root)
- assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not"
-
- read = File.read(absolute) if block_given? || !contents.empty?
- yield read if block_given?
-
- contents.each do |content|
- case content
- when String
- assert_equal content, read
- when Regexp
- assert_match content, read
- end
- end
- end
- alias :assert_directory :assert_file
-
- # Asserts a given file does not exist. You need to supply an absolute path or a
- # path relative to the configured destination:
- #
- # assert_no_file "config/random.rb"
- def assert_no_file(relative)
- absolute = File.expand_path(relative, destination_root)
- assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does"
- end
- alias :assert_no_directory :assert_no_file
-
- # Asserts a given migration exists. You need to supply an absolute path or a
- # path relative to the configured destination:
- #
- # assert_migration "db/migrate/create_products.rb"
- #
- # This method manipulates the given path and tries to find any migration which
- # matches the migration name. For example, the call above is converted to:
- #
- # assert_file "db/migrate/003_create_products.rb"
- #
- # Consequently, assert_migration accepts the same arguments has assert_file.
- def assert_migration(relative, *contents, &block)
- file_name = migration_file_name(relative)
- assert file_name, "Expected migration #{relative} to exist, but was not found"
- assert_file file_name, *contents, &block
- end
-
- # Asserts a given migration does not exist. You need to supply an absolute path or a
- # path relative to the configured destination:
- #
- # assert_no_migration "db/migrate/create_products.rb"
- def assert_no_migration(relative)
- file_name = migration_file_name(relative)
- assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}"
- end
-
- # Asserts the given class method exists in the given content. This method does not detect
- # class methods inside (class << self), only class methods which starts with "self.".
- # When a block is given, it yields the content of the method.
- #
- # assert_migration "db/migrate/create_products.rb" do |migration|
- # assert_class_method :up, migration do |up|
- # assert_match(/create_table/, up)
- # end
- # end
- def assert_class_method(method, content, &block)
- assert_instance_method "self.#{method}", content, &block
- end
-
- # Asserts the given method exists in the given content. When a block is given,
- # it yields the content of the method.
- #
- # assert_file "app/controllers/products_controller.rb" do |controller|
- # assert_instance_method :index, controller do |index|
- # assert_match(/Product\.all/, index)
- # end
- # end
- def assert_instance_method(method, content)
- assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}"
- yield $3.strip if block_given?
- end
- alias :assert_method :assert_instance_method
-
- # Asserts the given attribute type gets translated to a field type
- # properly:
- #
- # assert_field_type :date, :date_select
- def assert_field_type(attribute_type, field_type)
- assert_equal(field_type, create_generated_attribute(attribute_type).field_type)
- end
-
- # Asserts the given attribute type gets a proper default value:
- #
- # assert_field_default_value :string, "MyString"
- def assert_field_default_value(attribute_type, value)
- assert_equal(value, create_generated_attribute(attribute_type).default)
- end
-
- # Runs the generator configured for this class. The first argument is an array like
- # command line arguments:
- #
- # class AppGeneratorTest < Rails::Generators::TestCase
- # tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
- # teardown :cleanup_destination_root
- #
- # test "database.yml is not created when skipping Active Record" do
- # run_generator %w(myapp --skip-active-record)
- # assert_no_file "config/database.yml"
- # end
- # end
- #
- # You can provide a configuration hash as second argument. This method returns the output
- # printed by the generator.
- def run_generator(args=self.default_arguments, config={})
- capture(:stdout) { self.generator_class.start(args, config.reverse_merge(destination_root: destination_root)) }
- end
-
- # Instantiate the generator.
- def generator(args=self.default_arguments, options={}, config={})
- @generator ||= self.generator_class.new(args, options, config.reverse_merge(destination_root: destination_root))
- end
-
- # Create a Rails::Generators::GeneratedAttribute by supplying the
- # attribute type and, optionally, the attribute name:
- #
- # create_generated_attribute(:string, 'name')
- def create_generated_attribute(attribute_type, name = 'test', index = nil)
- Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':'))
- end
-
- protected
-
- def destination_root_is_set? # :nodoc:
- raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
- end
-
- def ensure_current_path # :nodoc:
- cd current_path
- end
-
- def prepare_destination # :nodoc:
- rm_rf(destination_root)
- mkdir_p(destination_root)
- end
-
- def migration_file_name(relative) # :nodoc:
- absolute = File.expand_path(relative, destination_root)
- dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
- Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
- end
end
end
end
diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb
new file mode 100644
index 0000000000..6267b2f2ee
--- /dev/null
+++ b/railties/lib/rails/generators/testing/assertions.rb
@@ -0,0 +1,121 @@
+module Rails
+ module Generators
+ module Testing
+ module Assertions
+ # Asserts a given file exists. You need to supply an absolute path or a path relative
+ # to the configured destination:
+ #
+ # assert_file "config/environment.rb"
+ #
+ # You can also give extra arguments. If the argument is a regexp, it will check if the
+ # regular expression matches the given file content. If it's a string, it compares the
+ # file with the given string:
+ #
+ # assert_file "config/environment.rb", /initialize/
+ #
+ # Finally, when a block is given, it yields the file content:
+ #
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
+ # assert_match(/Product\.all/, index)
+ # end
+ # end
+ def assert_file(relative, *contents)
+ absolute = File.expand_path(relative, destination_root)
+ assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not"
+
+ read = File.read(absolute) if block_given? || !contents.empty?
+ yield read if block_given?
+
+ contents.each do |content|
+ case content
+ when String
+ assert_equal content, read
+ when Regexp
+ assert_match content, read
+ end
+ end
+ end
+ alias :assert_directory :assert_file
+
+ # Asserts a given file does not exist. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_no_file "config/random.rb"
+ def assert_no_file(relative)
+ absolute = File.expand_path(relative, destination_root)
+ assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does"
+ end
+ alias :assert_no_directory :assert_no_file
+
+ # Asserts a given migration exists. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_migration "db/migrate/create_products.rb"
+ #
+ # This method manipulates the given path and tries to find any migration which
+ # matches the migration name. For example, the call above is converted to:
+ #
+ # assert_file "db/migrate/003_create_products.rb"
+ #
+ # Consequently, assert_migration accepts the same arguments has assert_file.
+ def assert_migration(relative, *contents, &block)
+ file_name = migration_file_name(relative)
+ assert file_name, "Expected migration #{relative} to exist, but was not found"
+ assert_file file_name, *contents, &block
+ end
+
+ # Asserts a given migration does not exist. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_no_migration "db/migrate/create_products.rb"
+ def assert_no_migration(relative)
+ file_name = migration_file_name(relative)
+ assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}"
+ end
+
+ # Asserts the given class method exists in the given content. This method does not detect
+ # class methods inside (class << self), only class methods which starts with "self.".
+ # When a block is given, it yields the content of the method.
+ #
+ # assert_migration "db/migrate/create_products.rb" do |migration|
+ # assert_class_method :up, migration do |up|
+ # assert_match(/create_table/, up)
+ # end
+ # end
+ def assert_class_method(method, content, &block)
+ assert_instance_method "self.#{method}", content, &block
+ end
+
+ # Asserts the given method exists in the given content. When a block is given,
+ # it yields the content of the method.
+ #
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
+ # assert_match(/Product\.all/, index)
+ # end
+ # end
+ def assert_instance_method(method, content)
+ assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}"
+ yield $3.strip if block_given?
+ end
+ alias :assert_method :assert_instance_method
+
+ # Asserts the given attribute type gets translated to a field type
+ # properly:
+ #
+ # assert_field_type :date, :date_select
+ def assert_field_type(attribute_type, field_type)
+ assert_equal(field_type, create_generated_attribute(attribute_type).field_type)
+ end
+
+ # Asserts the given attribute type gets a proper default value:
+ #
+ # assert_field_default_value :string, "MyString"
+ def assert_field_default_value(attribute_type, value)
+ assert_equal(value, create_generated_attribute(attribute_type).default)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
new file mode 100644
index 0000000000..7576eba6e0
--- /dev/null
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -0,0 +1,106 @@
+require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/hash/reverse_merge'
+require 'active_support/core_ext/kernel/reporting'
+require 'active_support/concern'
+require 'rails/generators'
+
+module Rails
+ module Generators
+ module Testing
+ module Behaviour
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :destination_root, :current_path, :generator_class, :default_arguments
+
+ # 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.
+ self.current_path = File.expand_path(Dir.pwd)
+ self.default_arguments = []
+ end
+
+ module ClassMethods
+ # Sets which generator should be tested:
+ #
+ # tests AppGenerator
+ def tests(klass)
+ self.generator_class = klass
+ end
+
+ # Sets default arguments on generator invocation. This can be overwritten when
+ # invoking it.
+ #
+ # arguments %w(app_name --skip-active-record)
+ def arguments(array)
+ self.default_arguments = array
+ end
+
+ # Sets the destination of generator files:
+ #
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ def destination(path)
+ self.destination_root = path
+ end
+ end
+
+ # Runs the generator configured for this class. The first argument is an array like
+ # command line arguments:
+ #
+ # class AppGeneratorTest < Rails::Generators::TestCase
+ # tests AppGenerator
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # teardown :cleanup_destination_root
+ #
+ # test "database.yml is not created when skipping Active Record" do
+ # run_generator %w(myapp --skip-active-record)
+ # assert_no_file "config/database.yml"
+ # end
+ # end
+ #
+ # You can provide a configuration hash as second argument. This method returns the output
+ # printed by the generator.
+ def run_generator(args=self.default_arguments, config={})
+ capture(:stdout) do
+ args += ['--skip-bundle'] unless args.include? '--dev'
+ self.generator_class.start(args, config.reverse_merge(destination_root: destination_root))
+ end
+ end
+
+ # Instantiate the generator.
+ def generator(args=self.default_arguments, options={}, config={})
+ @generator ||= self.generator_class.new(args, options, config.reverse_merge(destination_root: destination_root))
+ end
+
+ # Create a Rails::Generators::GeneratedAttribute by supplying the
+ # attribute type and, optionally, the attribute name:
+ #
+ # create_generated_attribute(:string, 'name')
+ def create_generated_attribute(attribute_type, name = 'test', index = nil)
+ Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':'))
+ end
+
+ protected
+
+ def destination_root_is_set? # :nodoc:
+ raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
+ end
+
+ def ensure_current_path # :nodoc:
+ cd current_path
+ end
+
+ def prepare_destination # :nodoc:
+ rm_rf(destination_root)
+ mkdir_p(destination_root)
+ end
+
+ def migration_file_name(relative) # :nodoc:
+ absolute = File.expand_path(relative, destination_root)
+ dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
+ Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/testing/setup_and_teardown.rb b/railties/lib/rails/generators/testing/setup_and_teardown.rb
new file mode 100644
index 0000000000..73102a283f
--- /dev/null
+++ b/railties/lib/rails/generators/testing/setup_and_teardown.rb
@@ -0,0 +1,18 @@
+module Rails
+ module Generators
+ module Testing
+ module SetupAndTeardown
+ def setup # :nodoc:
+ destination_root_is_set?
+ ensure_current_path
+ super
+ end
+
+ def teardown # :nodoc:
+ ensure_current_path
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb
index 902361ce77..f7b77bcb3b 100644
--- a/railties/lib/rails/rack/debugger.rb
+++ b/railties/lib/rails/rack/debugger.rb
@@ -12,8 +12,8 @@ module Rails
::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings)
puts "=> Debugger enabled"
rescue LoadError
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
- exit
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
+ exit(1)
end
def call(env)
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 9437e9c406..89ca8cbe11 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -112,7 +112,6 @@ module Rails
# Be sure to look at the documentation of those specific classes for more information.
#
class Railtie
- autoload :Configurable, "rails/railtie/configurable"
autoload :Configuration, "rails/railtie/configuration"
include Initializable
@@ -121,6 +120,7 @@ module Rails
class << self
private :new
+ delegate :config, to: :instance
def subclasses
@subclasses ||= []
@@ -128,7 +128,6 @@ module Rails
def inherited(base)
unless base.abstract_railtie?
- base.send(:include, Railtie::Configurable)
subclasses << base
end
end
@@ -166,14 +165,51 @@ module Rails
@railtie_name ||= generate_railtie_name(self.name)
end
+ # Since Rails::Railtie cannot be instantiated, any methods that call
+ # +instance+ are intended to be called only on subclasses of a Railtie.
+ def instance
+ @instance ||= new
+ end
+
+ def respond_to_missing?(*args)
+ instance.respond_to?(*args) || super
+ end
+
+ # Allows you to configure the railtie. This is the same method seen in
+ # Railtie::Configurable, but this module is no longer required for all
+ # subclasses of Railtie so we provide the class method here.
+ def configure(&block)
+ instance.configure(&block)
+ end
+
protected
def generate_railtie_name(class_or_module)
ActiveSupport::Inflector.underscore(class_or_module).tr("/", "_")
end
+
+ # If the class method does not have a method, then send the method call
+ # to the Railtie instance.
+ def method_missing(name, *args, &block)
+ if instance.respond_to?(name)
+ instance.public_send(name, *args, &block)
+ else
+ super
+ end
+ end
end
delegate :railtie_name, to: :class
+ def initialize
+ if self.class.abstract_railtie?
+ raise "#{self.class.name} is abstract, you cannot instantiate it directly."
+ end
+ end
+
+ def configure(&block)
+ instance_eval(&block)
+ end
+
def config
@config ||= Railtie::Configuration.new
end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index 9807000578..142af2d792 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -1,6 +1,6 @@
$VERBOSE = nil
-# Load Rails rakefile extensions
+# Load Rails Rakefile extensions
%w(
annotations
documentation
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 0057b0f887..8544890553 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -1,108 +1,70 @@
-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
-
- def define
- task rdoc_task_name
-
- task rerdoc_task_name => [clobber_task_name, rdoc_task_name]
-
- task clobber_task_name do
- rm_r rdoc_dir rescue nil
- end
-
- task :clobber => [clobber_task_name]
+begin
+ require 'rdoc/task'
+rescue LoadError
+ # Rubinius installs RDoc as a gem, and for this interpreter "rdoc/task" is
+ # available only if the application bundle includes "rdoc" (normally as a
+ # dependency of the "sdoc" gem.)
+ #
+ # If RDoc is not available it is fine that we do not generate the tasks that
+ # depend on it. Just be robust to this gotcha and go on.
+else
+ require 'rails/api/task'
+
+ # Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
+ class RDocTaskWithoutDescriptions < RDoc::Task
+ include ::Rake::DSL
+
+ def define
+ task rdoc_task_name
+
+ task rerdoc_task_name => [clobber_task_name, rdoc_task_name]
+
+ task clobber_task_name do
+ rm_r rdoc_dir rescue nil
+ end
- directory @rdoc_dir
- task rdoc_task_name => [rdoc_target]
- file rdoc_target => @rdoc_files + [Rake.application.rakefile] do
- rm_r @rdoc_dir rescue nil
- @before_running_rdoc.call if @before_running_rdoc
- args = option_list + @rdoc_files
- if @external
- argstring = args.join(' ')
- sh %{ruby -Ivendor vendor/rd #{argstring}}
- else
- require 'rdoc/rdoc'
- RDoc::RDoc.new.document(args)
+ task :clobber => [clobber_task_name]
+
+ directory @rdoc_dir
+ task rdoc_task_name => [rdoc_target]
+ file rdoc_target => @rdoc_files + [Rake.application.rakefile] do
+ rm_r @rdoc_dir rescue nil
+ @before_running_rdoc.call if @before_running_rdoc
+ args = option_list + @rdoc_files
+ if @external
+ argstring = args.join(' ')
+ sh %{ruby -Ivendor vendor/rd #{argstring}}
+ else
+ require 'rdoc/rdoc'
+ RDoc::RDoc.new.document(args)
+ end
end
+ self
end
- self
end
-end
-namespace :doc do
- def gem_path(gem_name)
- path = $LOAD_PATH.grep(/#{gem_name}[\w.-]*\/lib$/).first
- yield File.dirname(path) if path
+ namespace :doc do
+ RDocTaskWithoutDescriptions.new("app") { |rdoc|
+ rdoc.rdoc_dir = 'doc/app'
+ rdoc.template = ENV['template'] if ENV['template']
+ rdoc.title = ENV['title'] || "Rails Application Documentation"
+ rdoc.options << '--line-numbers'
+ rdoc.options << '--charset' << 'utf-8'
+ rdoc.rdoc_files.include('README.rdoc')
+ rdoc.rdoc_files.include('app/**/*.rb')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+ }
+ Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")"
+
+ # desc 'Generate documentation for the Rails framework.'
+ Rails::API::AppTask.new('rails')
end
+end
- RDocTaskWithoutDescriptions.new("app") { |rdoc|
- rdoc.rdoc_dir = 'doc/app'
- rdoc.template = ENV['template'] if ENV['template']
- rdoc.title = ENV['title'] || "Rails Application Documentation"
- rdoc.options << '--line-numbers'
- rdoc.options << '--charset' << 'utf-8'
- rdoc.rdoc_files.include('README.rdoc')
- rdoc.rdoc_files.include('app/**/*.rb')
- rdoc.rdoc_files.include('lib/**/*.rb')
- }
- Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")"
-
- # desc 'Generate documentation for the Rails framework.'
- RDocTaskWithoutDescriptions.new("rails") { |rdoc|
- rdoc.rdoc_dir = 'doc/api'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
- rdoc.title = "Rails Framework Documentation"
- rdoc.options << '--line-numbers'
-
- gem_path('rails') do |rails|
- rdoc.options << '-m' << "#{rails}/README.rdoc"
- end
-
- gem_path('actionmailer') do |actionmailer|
- %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/action_mailer/base.rb).each do |file|
- rdoc.rdoc_files.include("#{actionmailer}/#{file}")
- end
- end
-
- gem_path('actionpack') do |actionpack|
- %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{actionpack}/#{file}")
- end
- end
-
- gem_path('activemodel') do |activemodel|
- %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/active_model/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{activemodel}/#{file}")
- end
- end
-
- gem_path('activerecord') do |activerecord|
- %w(README.rdoc CHANGELOG.md lib/active_record/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{activerecord}/#{file}")
- end
- end
-
- gem_path('activesupport') do |activesupport|
- %w(README.rdoc CHANGELOG.md lib/active_support/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{activesupport}/#{file}")
- end
- end
-
- gem_path('railties') do |railties|
- %w(README.rdoc CHANGELOG.md lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
- rdoc.rdoc_files.include("#{railties}/#{file}")
- end
- end
- }
-
- # desc "Generate Rails Guides"
+namespace :doc do
task :guides do
- # FIXME: Reaching outside lib directory is a bad idea
- require File.expand_path('../../../../../guides/rails_guides', __FILE__)
+ rails_gem_dir = Gem::Specification.find_by_name("rails").gem_dir
+ require File.expand_path(File.join(rails_gem_dir, "/guides/rails_guides"))
RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate
end
end
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index e239e1695e..4c4c80ecda 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -59,7 +59,7 @@
#header {
- background-image: url("/assets/rails.png");
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAABACAYAAABY1SR7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAGZhJREFUeNqsWwmUXGWV/t5Sr9aurl6qO0l3Z9/DEoJh18gZQGAUxPHIyQHH7eioZ8bjnAFHZ0RndNxxRBhGcUbxoKIHBkTEcUYREIHIGpKQjUDS6U660/tSVV3Lq/fefPf/Xy2dBFGYx3npqvde/e/e/97v3u/e/8e4Lt2L8DCCAFcGwF8ZBjYbgM1rAZoO+WLwZhDMu9y4+YcOozbAqzwXNA3GdzX/5hV+KnKO2+GXFj/AvzmW8e72iG202CYiphbY403f9/k3QHZtJ9oWtyCQe7wGX79TKVb7rP9pXJPDVxf0Rz+oyxm4HNWrahFNixdk3EAJbERMWOm4ulctVODNVeEVK0DeRVDgb1wfJgcqUo6duaKnFOH7bm6JmH+5LOEgZprwRIHAV3JYfLjKM55Noz3bBqdcgt0Wg52Kq/cHHkXns0qIukKBlltk9rU2QaiouiefPQ+RdBuseAJeqYTK1CTH8mE4NsyIpRWu8nssCs+xULWpjGVwTvieKl/sV6mIXzOib/OftzuG8d6l8SiVMODyRb46oazg8YPP2Wnvy9ISNqplzsxYAW6hjGhHEmYiBoPC+hRMfFMrESgrBC5n0KS+lq1nPahZh2OXymg9bSNWX/u3FKyKI//7Exx96B4Y8RiCEseq8t0VznyxjMDidFIJ8QSf3hJEOFbZEAHVhIkFTX54fxtnIW5pJUQIeZ8ooZShkInuDOLpFIX1ldtCBix7KI/k4E7OwbTjcNIdiCQzsONp2LEk7GgUnZsuQN9lW2En45xlukrUghWzeZq8FsXsi8+gND6MSCqD9k3nwulIUShKZxt0LYPWortRSY0NXreC8J6pZNDChEDh53PT1NIPLaEnLbQKNTETEaR7sycA0jD1INXZAnzObjTbiWh7Vr1A3Knn4nciu+lCvstUig09cp96cVCtcELoFpEIFUjjyIM/osWIg+IMXS3DcfNwZ3NQHmmKU9OqroX2jWdgatduuPkpmA4ViZrK9RqKABEBtg9tDeW+oUIyTIYuFaX7eCG4aqbU+hhKocD3UBoZISBLiC9cpAQKyq5SQo6OjVswtec5VHLTiHUuIN4WonXlqUj2riS0DIUXwZlERFHSK+SQGzqI3MHdmNm7CzMvvowF527B8qvejZ3/+iXk9vVTao5tiTKN0OUHISZEGS/8W6UbRdoTSHe3E1f+CRaR3xhBLVJSIQ7qleZQGBigZYoYdR+ElUjBaW3H6JMPIrV0Hdo2bEayZ7my0KsdLctPBS64EuWZMYw/9wTGnvod0mtzWH71Vuz66o10bVpK8FIx6orUMejpCKYBTvfM9HXBJtA8z3/1BKDivaksVJmaYsgsYPDnd6LzzAuw8I1XUIGleC1HtDWLnguv5BiX4+jDD2D4sQeV1bQvNXBi6vAb1MGtrEEHjRPgqfZ0qMRJElYYSudfq12nmzAvtJ2yib69iRadRGnySD0Uv5bDtCPou/gqnPY3N6DnLRczgtHxCf4aVnUeUdgw6i6FqM1w292Ujo/TJdB5wHcJ2iDCaBTRmVfw4rkw4yksuvQyJJf0YvrgNiayvBLESS9AYuFqJLLLCPb4SQWulosojhxmeCeoDeaQSoVuy8lPtSKxYKnC2Bmf+DwtvBgv3/qfTI6uEtGuJV7PCBTIq5zNtt5uxBgyvap30pf55TISfX1Y/PatGPrVvcgPvEyAJ1GenaPZLSy//G2IL+qki43CNCMwk620iovy9FGUJgYwm8gwpK/guRJOS5dyD688h+n9z2L28F4Ujx2ia04jEl8Ad3oGVTePaGcnQ3sKLb1rkD3nIqx594dRIh733n6PmmrrvGj671sjVlxczRWAkxZ0r+rTrhfMJ0uEM8xKUYXONR+5nr57BdpP24TCsX6M/f5F5AYLWPauK9F11htUwjOIL8GNZH1qpKwiyVGELk0OoDj2EtziFOaODSN3aC/v24xmZzAU51TgcJKd/DktHo9jyRXvg0Or7PvejTj22KPKiyafew6zg8MYypVLNsLkJ2bxaZXM4i5EmCBPsEaoWJUUpfeSK7DgvEtQmh4ihTDQdf5FOHDHr7HqPVeh99KL4OVzpE50N18CtqnCdBCY6rsEcTsqIGUGD6rY9e3bMPzIHmTWLsbqa7ai84wL6YrTqEyOqEmwonEExSoO//R7dLcJWiWCueF+7P7mjZAUY8YdJZqySMo24j5zQSybQdeyhdrX5imho4NhEEnkRbkDQyjSRVJLeziCgef/6avIrFuOtR95P2lJNSSshg4l6rdm+Ht9inWsqIOX7voN+u/eRoEM5PvHMbbjGcwcfg7jO3YxbCcRiaaYQOXnpEaFGeahGQaMCidJRidt8RghS6Q344XQIowmFq2QXdLNdwsx8zUFqCOQNIECVqdp8pESB53Fvhdux9T2FxBb1AWX4XbjDX/HFzjEmgedB4XYKT5D4T0VTLRCtIiTwOBvfovpvS8T+Bm4MyW6jw13tIIDt/9G/TTWk8HKvzgbmd4+YldYQIdixgHJYkC82Ul6UDnQSbEGdsFGZlEWyUyLyiEyYwajRVAoAXNlEjR+pjUCUmiDQcKOORwwgpFfP4cg5mPzTZ9FoqePdGVWuZRPYQNPcgrd0/dCpqpdy3DIsQ4fxtiTu7Hxkx8iRXkcB+94iM86/K0Jx4opi5aOzGJs14toWeLAdYXWxFQCtJlkA+LUq+bI7QR3mj3YoqVNgGcXd5NWUOiZAk9GH86S4jK25jWBLVREl1uK5Voywz6WXf1WLHjTm0lPigSyxoUpnEqU8c26Wyk/Y24RMjhw/yMoj+cQbWvH0isuwuijL6BwaJwcyq7XUTaBP7N3HOU3ke7HSONJb8RTBGoGKZPFyTE8saTZyCPtrC2coxOoTuY5+x4UTzHNsNjR6d6Qa8JJ5BIV8ksVtKzpwcr3v5dyOrzHKMWXizsZAnK6k1ImPDmAqjOmdr9AwXcodzr4kwfQfuY6VKbzyhpGU96S75WxIqb2DaPnvNWKklQD4WSuzB+sVILjOYjm/VARSWKTBQQzlZCFmErYeubzVJJR14SlQtVQMjO0xrXvoulXkq3OKnxAXqSsoSmNUbOM/BV35RjDDz9JrBXpnnEM3vsYjj38LLyZihI8QNAgQhITOCmTO46i+6w1MPm86RVIiC09/RJUGcECCe2UU0G6QIyUjEC5hGaCNd4RqHKU6VuDylQlI2N8hfXDWibEdyhCKXREuZUVUX8lyhh2+Jl5Q/6akSgT4izGn3wBFu+JwYOKj8qwtsbJaYmJuYEZ5AYmFOWXPCN1jTodzeuqM0WtSI1rzXrV0LSNKRFuZLYQ2EYVPjEQVuQUMsCya65GvL1HWUwJS+FNUcBsUiZUQv7aLGlndr+I8ug4XUMVAJw4U7FmI8SFETTmUaGK2gas1SeeP8znoizIEso9DaUIy2FWkNU5V0VYs/azWXKncuCHqgQq1CHiY831H8TGr34erRvXKdD6LD3b+HnRn12qGgdqlmxHZe2aRcy6NbQScl8y8dSOfWQE1yK9YYmqXYww3xhNObemUI2IWraF2d1HMTeeh83MbkUiylKiiMdy2wjzXBjxWYdRiSkhfDVVKGSstxM9l16JxZe/E2+848c49bPXK9D2vPUyEsBOVZMINmpCW6HgEOuIQjXF6FYuAV2aHsWyrVfj9C9er5SR5Kms0PTf8QoZtIo7WSJW+mmRJLGSpDK2ipzV2bK6X6fxtWOCicYVqyhGXkXn+WeTcfape5ZDsPGM91C5iy8LI0s445bd9FkrAFHICt1N8DE+gdyeQczs34+uzeei68LNLGfdea50st6VbiyYmHq+nxTFRSSRVsD3ii7xyeQbdt/M5h/MERMT4i6GjlAWeUxh6HCN8+LIz+5H5zlUbtHSOnVp4MCa51JaIQ16i0kwP9CP0uExPP+JL2DggfuYN8jTJClYxnH4aNimdpp0r7nDkyx9h5gE0+RqSVTyZXXTsMz5FaJyMJrrGLNopyWUIImj//1LjPzuUZLCC5gzVqMwPIglV7/rxCaihFaCPCDOxDUl1EoylFP4mUlFCgPDStLKWB47PnUjrSSsNqrJsa/zR02ZwGjYRoVkEZh0ZHzbfmTPXE85SWrnKip6GeFE2I1iKVBCzNK9pmiVhS1x+Axx7myRJesvgHvvR3rNKmQ3n/OKPVGND1MVXTqHiFK6qVFiwlXgTVDhkq+ChhnyJCW9GeaoIGQOdV0M9YhYZWbvUXrIJJ+rKL6lJ9CYj5Fai0iKqyPkx0HcUsJYrBbtREIJ2H72GxTI/2CL1zAbLkZ8WIxYgUvsKebq6Zl3rEZvymx6echo1N+au9XcS3oHsxWMPrGTFH+CLhsmbhMNRWrNB4SZVSwyJ5WDFRb3DAAmaXf2rPP+6BpbkmStkBLAWwkHmdNWKfYqFaZRp2GGdo+mhpv6bBkNhepRzERpdASeW1aKSZ5RidpoUsRAvQ+NJCnJHHl+bcZ80vjkij661vo/rWMQSitWskgnNv7LP+MNN38NadYuCPtYCItIFTjMRgfeqClkhkFZ+FXCQmpFuyKXii7xNI93LT9szdrUMsNZnJkuwZX6zlKdaqRXrESiq/e19kBC3NisLt+Gc/7jW0gtZ51Bl1MCmUaoM//aRv0aapnF0l362KIUnI6EyuhCUOuWrIVfAZcRAj5NJWJ0C5epP19y1awJLWhdt/a1t3KcGF8Yxb5bbsLItoeYmxZRkRWq46IrR9StX/tcw4oKsYH+nlrZpmbcZQ7R1tDPBvMbdIwofLpVKIfcJy5nCa5WRhnDFkVOx+s5kr29GPzpfUxsuxg0zlQUxSZudG/CqNOSIJxYCclGCA7fDRDpiCK6gIVfidVmWXrHRh0fmBd+eSYIIEcWdRhdJJsWp+aQT1vI9nYjnl3wuhSJLuhAJJ1WQWDisadUELCi0bD1WlscMpq6lrV1Ft0riC9tVcFD8odfDVS9bod5pNGgC3+XFnxsXA2rsw25/gHMTcwiRxdbvLgPsY7s61IktWSZinw6l8SbupNGvUlphB1yZY3aIhfZtRmz4XS3oMoA5JP6BywdvBIr24ytMdzsWjHaMcnI0nXRG5FkdCrnS6gy6QzccxeMZDsJW+r1KbJ4pbKAVy6huXoyauVUaAUjRK5WjN9cH05PCiZl84VfsXaSVTKf191C6F61qCXjtjAORtvTSPb0sgYoEi/UmEmnMj6JkpXA6z2cTAbxxV26GdEEZB12DVVV63BrIuwYaWpCGZyuJBWSFSxPLTB5PH1+rhDDKlQbuvajNUzE+UVyRTTdQt+zWIrGWIJOozo8hjmashq8PkXsZAoty1Yqi/gVnq6ru+p1pUKFTM3dENJzu421TiqKKq3hhUp45apSyM1VGMH0xOi+liz0yOxUyijs2w2DlRjI+8tHB3XUIP+fGBxA9+LFr1kRgwV769p1fPkEQ+9KRq+dKE9MsGKc1BmxltEC7W6CEdW0aUtocIvw0tcSt5JGu3R4OA+zIxW1uKoUOUZzFxmxRp/ai+iz+xi9CK5EVJGdqBNBlG4xdvBlRq9eTQteawhm0MgPLsSGj92gVqjKk8ew/TOfxPjjz8BKxhvLFGHjWUBuJh0Cu6pqD7WCTGz4BDqKpE30rIlj05rw6sKFxuCXPP9O8MEjxQqOTuQwNjJLa1mItaRRGB3GLHnO6znaNmxC/nA/cocPKNoS61iEZVdfEy5LBHVKUieCLY5eeKIiXp6RapJuNVJFMCamYGnOUFyslBo0Xronai0dIfXmnZIqtKhgNIaj/F3ULSLx4j60dnXXy8s/OZe0dyGW7cLOL34arevXI9rayWgYhZPtoJtNqsTbyPKUgwzamyCw867MtG5NBUF9bSBXLCkeKOzDroUutaZODax52yUk5sfgsyrL897+PXtQHTmK7vWnomPpCkSTf3pI7j7/Qmz/5HWY3r5LNziYeC3WPlYsovOJJ7VKVbuPENcgXEyvuV3IbKXpPlcqqh0acqGe2S1oq1jzqmZ+b0mGDJNaM2bnjrHuPnYUifZOtDMKda9ah1RnZ30F99WO9jM2MzouZw0vLdJIuCsiUInOz0vbiVNa9DSBtITyWo3VAV/XG/KmPEuBKrmard7rNxKiyCoN7EBnpXlLCiYTmfibuEHSSSkLV4uzGNr5NEYP7EZb31J0rd6AzMIevtf+g4oIg+7e8iYM3H03J5muw9n3ZquqfwU3aGDdMBqdztr+lXBbhyg+R2xYTb5jN7YG6SKnyh870r8Ki6Py0CiO3fcTNWaCBU3E8FVDr7ZPRjbcDLHO30N/TmazdLk+JFMxVoZh6errUrcmnDQp5o4MocrI4o3N6dmXhp1hoHkOFV2R5CXtVwm3Qc0aBip8Z6lY0HtRpJ8GYz5pVFgxgkaHiaCuDE1gfOAhFdNbJIKxplCKNJqqyoqi0CT9tp9/IyyPE2SryYyDKD9LVKxKUqXbuFOM+yVDN/Rq+0ia1mLmtYNqK8rhTiSpLLNbLkDLuZvQ0X8QBoG+//5fIMjP4AQ/kJkuM+vW+sS1wkgiVSTi0Fq2XqoLFfFYMMkyHSFL2mOpHQmy+aU4xXHoLk6rrIkYiE1JNpZOJjO1ivduOLSkZeuk6/YBwR54jaVv6chXpmZQmJnEssveQjwVcPCXv1IWt4//sUVB7K4WpGTREqhvJCrO5MhtGLMTKWU5pUSpDKs1glhbB4W3VCSpTM6gOl2GQzxJt+RQUMFcOoENrXG0FEhESSvMmIVIZ6uaHL9QZn6Y067VNJueV4bdmYDdktJ7pAJNKKfG+pG/cz8GH/gfGLIARF4o9fs8RWSrUmZxN7Z+9za0sooTPiRuI22bsUMHsevWW1B+iFnYdOgqFWTPPxWnXPdxtK5eV8fB9IH92Pn1m1hz7MQh00Xm/C34+K23MiOXsPvLX8bgbXej5bz1OPs7tzIhduHgnXfghX/8OplEsr6U4ZtV9G69HMvf8wEkKUfgaUeWbs4zX/8Sxm/+AbzxCRVF1VpFM9hrvS2ZmZbuRUh2LpxPw7t60EWK8vgHPoCZ5w4i1pvBps99Bu2nbJ73XLyzB4kvLcAPt27F2LFR9MTjSKbb1L1h4mIq4iNL14u2ZRFJysazZCNHqA0DZXRcuBGnf+bz6v4JLDqVgk3r247DnMdJDkOzffJtDfoY2b0dg08/gbZlq7BiyyWk+MuQ2bAGU9v2snTtQnxBj3pu9OnfYXr/Hiq1EZ0bz0ZsUS+sFUvgDB+DFfh1v3X9Kg4xknfLRNZ21h2/RYTX29avU0pUSwUcuP07KLw0oBZrA5bGozt2MlA5updgzGuJnYyp6rt7778HP37fX+OJW77ZaKzKoo5eOdfRhMehO3+EbXzu8H/dXW/SOTwj0gZqeoVck+h3xES9LDjpVp3QXeRdqSVLkDllrepy5oeHMPH0brq2qdteRmNJwj7pYKFVlr75YrztKw6ya9aFTzF8Tk+pBZrmXRGRdCsSLMiQbKlfE7PLrjarCcSSA0QZvQQevGKncnrfXpVwZTde3+XvqN9b8d4PYfuNn8O+b9zO56K6oGpOiMYreNfSc7eoE+FO00P334XJx3fQzM685zd8/Hqs/uCHGGEy9QEslaT0Cm9t7rVyYqnGWogEGIl+nqUTmyxwTj62HTs/91ks3XqN2u8VBLKZoVt14pe/42oc/O6dzB2+qnEMNGHEPHHbSfiSqloakGP7D7+Dpz79BfT6cRXu5rHatk51Nh9aEaOJu2mOZIf36uDu6EDi3PVoIQGV5efiwSG1Rjny8COY3P4sI1WM2HKx4bpPYdEFlzA9RMOlhCAsLJssYqGxRbcZI8//9MfIrliDvjPOwqqL/xwD996P6rY9zGHWPNMNPf8UJl9+Cdm169G9YWOdapjB8auShsJMc85YdekVWL7lQgroKHd68qMfRcAEu+lrX1GdSdmBKjQn0aOrU9lso5bK53uSLiyscNu10tAy66FganAQD9zwD6jM5ZBe2IeLbvoGWs5YofZQyfKXxbpejl133omfXfth7P/Fz8NRLbXgb0nGNe26GhGST5MzFmEYll2oCl+sd2IZCcWtTKxd6rokwdYVpyK9fB1z1KnIrD0NDt1WiNGB738X3kxJVapiWVmR5pCurc2iSaIkmNJ0Hr+9+WYkMu0YfHI7Dv9+J+766Eew8vSNiFP4WGsGBanhh6bw1K3fRjSdwfSel5FikTT67At4+t9vgVssojA0Rp6VwOyhfjx9262qABrfw1KaJW15YprXvsVcEG1sT5eCji40fXSURVyAvTd9TSmv6nTVifQx/uwzmHiU7kb3Clu+GC27MsY247p07+SihN0m/Kgc6EXRIjmMgDvCF9mcsXJxDgniZSnN3xFLIcc6Yormd1mhCX2QpWc7SteolNUpNUQkIUvJpDkUrsrfqy1L8ZjaFSTrJKLsCbvz6BqxaBwdBReWbJmF3kTa2NYRVYFGHEYKqqFKFXtzMg6uUhaJyzZyQ/d/FdUm8LwmAuYwO/vhQBU+m+ddmy+NpBKNWpIzF7EdRSxrOygMMl6LruUw2tQXOTy1akNFk/XtU/V70H3g6YyNNk5GtOIp/DYvlKp9LoJLWuIl2fADfJ/X71PQ8Jo2Vzbv620OAFI9jtIqCQ7tnfC/JxhNT4dShds4UKvB66s1ftPnRqOh/l13hDDqWGhxqUgTsIV1Fzg5Y7TEpKsK+B/w+sdqUWuqv1CxUN8K/MqHLMnhj/g/J/4/juDky9VSg0kh/zQj322897Pao/8nwAC+AZicLeuzngAAAABJRU5ErkJggg==);
background-repeat: no-repeat;
background-position: top left;
height: 64px;
diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb
index f52c4c44b7..75180ff978 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -1,3 +1,7 @@
+if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
+ ENV['RAILS_ENV'] ||= 'test'
+end
+
module Rails
class TestUnitRailtie < Rails::Railtie
config.app_generators do |c|
diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb
index 87b6f9b5a4..a463380e2d 100644
--- a/railties/lib/rails/test_unit/sub_test_task.rb
+++ b/railties/lib/rails/test_unit/sub_test_task.rb
@@ -1,6 +1,83 @@
+require 'rake/testtask'
+
module Rails
+ class TestTask < Rake::TestTask # :nodoc: all
+ class TestInfo
+ def initialize(tasks)
+ @tasks = tasks
+ end
+
+ def files
+ @tasks.map { |task|
+ [task, translate(task)].find { |file| test_file?(file) }
+ }.compact
+ end
+
+ def translate(file)
+ if file =~ /^app\/(.*)$/
+ "test/#{$1.sub(/\.rb$/, '')}_test.rb"
+ else
+ "test/#{file}_test.rb"
+ end
+ end
+
+ def tasks
+ @tasks - test_file_tasks - opt_names
+ end
+
+ def opts
+ opts = opt_names
+ if opts.any?
+ "-n #{opts.join ' '}"
+ end
+ end
+
+ private
+
+ def test_file_tasks
+ @tasks.find_all { |task|
+ [task, translate(task)].any? { |file| test_file?(file) }
+ }
+ end
+
+ def test_file?(file)
+ file =~ /^test/ && File.file?(file) && !File.directory?(file)
+ end
+
+ def opt_names
+ (@tasks - test_file_tasks).reject { |t| task_defined? t }
+ end
+
+ def task_defined?(task)
+ Rake::Task.task_defined? task
+ end
+ end
+
+ def self.test_info(tasks)
+ TestInfo.new tasks
+ end
+
+ def initialize(name = :test)
+ super
+ @libs << "test" # lib *and* test seem like a better default
+ end
+
+ def define
+ task @name do
+ if ENV['TESTOPTS']
+ ARGV.replace Shellwords.split ENV['TESTOPTS']
+ end
+ libs = @libs - $LOAD_PATH
+ $LOAD_PATH.unshift(*libs)
+ file_list.each { |fl|
+ FileList[fl].to_a.each { |f| require File.expand_path f }
+ }
+ end
+ end
+ end
+
# Silence the default description to cut down on `rake -T` noise.
- class SubTestTask < Rake::TestTask
+ class SubTestTask < Rake::TestTask # :nodoc:
def desc(string)
# Ignore the description.
end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 3c247f32c0..9263b9b81d 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -48,10 +48,17 @@ task default: :test
desc 'Runs test:units, test:functionals, test:integration together'
task :test do
- if ENV['TEST']
- exec "bundle exec rails test #{ENV['TEST'].inspect}"
+ info = Rails::TestTask.test_info Rake.application.top_level_tasks
+ if info.files.any?
+ Rails::TestTask.new('test:single') { |t|
+ t.test_files = info.files
+ }
+ ENV['TESTOPTS'] ||= info.opts
+ Rake.application.top_level_tasks.replace info.tasks
+
+ Rake::Task['test:single'].invoke
else
- exec 'bundle exec rails test'
+ Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke
end
end
@@ -60,15 +67,11 @@ namespace :test do
# Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
end
- task :run do
- ActiveSupport::Deprecation.warn "`rake test:run` is deprecated. Please use `rails test`."
- exec 'bundle exec rails test'
- end
+ task :run => ['test:units', 'test:functionals', 'test:integration']
# Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html
desc "Run tests quickly by merging all types and not resetting db"
- Rake::TestTask.new(:all) do |t|
- t.libs << "test"
+ Rails::TestTask.new(:all) do |t|
t.pattern = "test/**/*_test.rb"
end
@@ -90,7 +93,6 @@ namespace :test do
recent_tests('app/controllers/**/*.rb', 'test/controllers', since) +
recent_tests('app/controllers/**/*.rb', 'test/functional', since)
- t.libs << 'test'
t.test_files = touched.uniq
end
Rake::Task['test:recent'].comment = "Deprecated; Test recent changes"
@@ -114,23 +116,22 @@ namespace :test do
controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" }
(unit_tests + functional_tests).uniq.select { |file| File.exist?(file) }
end
-
- t.libs << 'test'
end
Rake::Task['test:uncommitted'].comment = "Deprecated; Test changes since last checkin (only Subversion and Git)"
- desc "Deprecated; Please use `rails test \"#{ENV['TEST']}\"`"
- task :single do
- ActiveSupport::Deprecation.warn "`rake test:single` is deprecated. Please use `rails test \"#{ENV['TEST']}\"`."
- exec "bundle exec rails test #{test_suit_name}"
+ Rails::TestTask.new(single: "test:prepare")
+
+ ["models", "helpers", "controllers", "mailers", "integration"].each do |name|
+ Rails::TestTask.new(name => "test:prepare") do |t|
+ t.pattern = "test/#{name}/**/*_test.rb"
+ end
end
- [:models, :helpers, :units, :controllers, :functionals, :integration].each do |test_suit_name|
- desc "Deprecated; Please use `rails test #{test_suit_name}`"
- task test_suit_name do
- ActiveSupport::Deprecation.warn "`rake test:#{test_suit_name}` is deprecated. Please use `rails test #{test_suit_name}`."
+ Rails::TestTask.new(units: "test:prepare") do |t|
+ t.pattern = 'test/{models,helpers,unit}/**/*_test.rb'
+ end
- exec "bundle exec rails test #{test_suit_name}"
- end
+ Rails::TestTask.new(functionals: "test:prepare") do |t|
+ t.pattern = 'test/{controllers,mailers,functional}/**/*_test.rb'
end
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index d1d02e086d..5a6d8d0983 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -1,11 +1,10 @@
module Rails
- # Returns the version of the currently loaded Rails as a Gem::Version
- def self.version
- Gem::Version.new "4.0.0.beta1"
- end
+ module VERSION
+ MAJOR = 4
+ MINOR = 1
+ TINY = 0
+ PRE = "beta"
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = Rails.version.segments
- STRING = Rails.version.to_s
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index a55bf012da..45968052a8 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG.md', 'README.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
+ s.files = Dir['CHANGELOG.md', 'README.rdoc', 'RDOC_MAIN.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
s.require_path = 'lib'
s.bindir = 'bin'
@@ -27,6 +27,5 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
s.add_dependency 'rake', '>= 0.8.7'
- s.add_dependency 'thor', '>= 0.17.0', '< 2.0'
- s.add_dependency 'rdoc', '~> 3.4'
+ s.add_dependency 'thor', '>= 0.18.1', '< 2.0'
end
diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb
index 63ed9eaef0..ceae78ae80 100644
--- a/railties/test/app_rails_loader_test.rb
+++ b/railties/test/app_rails_loader_test.rb
@@ -1,48 +1,68 @@
+require 'tmpdir'
require 'abstract_unit'
require 'rails/app_rails_loader'
class AppRailsLoaderTest < ActiveSupport::TestCase
+ def write(filename, contents=nil)
+ FileUtils.mkdir_p(File.dirname(filename))
+ File.write(filename, contents)
+ end
+
+ def expects_exec(exe)
+ Rails::AppRailsLoader.expects(:exec).with(Rails::AppRailsLoader::RUBY, exe)
+ end
setup do
- File.stubs(:exists?).returns(false)
+ @tmp = Dir.mktmpdir('railties-rails-loader-test-suite')
+ @cwd = Dir.pwd
+ Dir.chdir(@tmp)
end
- ['bin/rails', 'script/rails'].each do |exe|
- test "is in a rails application if #{exe} exists and contains APP_PATH" do
- File.stubs(:exists?).with(exe).returns(true)
- File.stubs(:read).with(exe).returns('APP_PATH')
- assert Rails::AppRailsLoader.find_executable
- end
+ ['bin', 'script'].each do |script_dir|
+ exe = "#{script_dir}/rails"
- test "is not in a rails application if #{exe} exists but doesn't contain APP_PATH" do
- File.stubs(:exists?).with(exe).returns(true)
- File.stubs(:read).with(exe).returns("railties #{exe}")
- assert !Rails::AppRailsLoader.find_executable
- end
+ test "is not in a Rails application if #{exe} is not found in the current or parent directories" do
+ File.stubs(:exists?).with('bin/rails').returns(false)
+ File.stubs(:exists?).with('script/rails').returns(false)
- test "is in a rails application if parent directory has #{exe} containing APP_PATH" do
- File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false)
- File.stubs(:exists?).with("/foo/#{exe}").returns(true)
- File.stubs(:read).with("/foo/#{exe}").returns('APP_PATH')
- assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar"))
+ assert !Rails::AppRailsLoader.exec_app_rails
end
- test "is not in a rails application if at the root directory and doesn't have #{exe}" do
- Pathname.any_instance.stubs(:root?).returns true
- assert !Rails::AppRailsLoader.find_executable
- end
+ ['APP_PATH', 'ENGINE_PATH'].each do |keyword|
+ test "is in a Rails application if #{exe} exists and contains #{keyword}" do
+ write exe, keyword
- test "is in a rails engine if parent directory has #{exe} containing ENGINE_PATH" do
- File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false)
- File.stubs(:exists?).with("/foo/#{exe}").returns(true)
- File.stubs(:read).with("/foo/#{exe}").returns('ENGINE_PATH')
- assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar"))
- end
+ expects_exec exe
+ Rails::AppRailsLoader.exec_app_rails
+ end
- test "is in a rails engine if #{exe} exists containing ENGINE_PATH" do
- File.stubs(:exists?).with(exe).returns(true)
- File.stubs(:read).with(exe).returns('ENGINE_PATH')
- assert Rails::AppRailsLoader.find_executable
+ test "is not in a Rails application if #{exe} exists but doesn't contain #{keyword}" do
+ write exe
+
+ assert !Rails::AppRailsLoader.exec_app_rails
+ end
+
+ test "is in a Rails application if parent directory has #{exe} containing #{keyword} and chdirs to the root directory" do
+ write "foo/bar/#{exe}"
+ write "foo/#{exe}", keyword
+
+ Dir.chdir('foo/bar')
+
+ expects_exec exe
+ Rails::AppRailsLoader.exec_app_rails
+
+ # Compare the realpath in case either of them has symlinks.
+ #
+ # This happens in particular in Mac OS X, where @tmp starts
+ # with "/var", and Dir.pwd with "/private/var", due to a
+ # default system symlink var -> private/var.
+ assert_equal File.realpath("#@tmp/foo"), File.realpath(Dir.pwd)
+ end
end
end
+
+ teardown do
+ Dir.chdir(@cwd)
+ FileUtils.rm_rf(@tmp)
+ end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index e5503b1eff..34432eac3a 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -41,6 +41,7 @@ module ApplicationTests
end
test "assets routes have higher priority" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/javascripts/demo.js.erb", "a = <%= image_path('rails.png').inspect %>;"
app_file 'config/routes.rb', <<-RUBY
@@ -222,6 +223,7 @@ module ApplicationTests
end
test "precompile properly refers files referenced with asset_path and runs in the provided RAILS_ENV" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
# digest is default in false, we must enable it for test environment
add_to_env_config "test", "config.assets.digest = true"
@@ -233,6 +235,8 @@ module ApplicationTests
end
test "precompile shouldn't use the digests present in manifest.json" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
+
app_file "app/assets/stylesheets/application.css.erb", "//= depend_on rails.png\np { url: <%= asset_path('rails.png') %> }"
ENV["RAILS_ENV"] = "production"
@@ -251,6 +255,7 @@ module ApplicationTests
end
test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
add_to_config "config.assets.compile = true"
add_to_config "config.assets.digest = true"
@@ -272,7 +277,7 @@ module ApplicationTests
manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first
assets = ActiveSupport::JSON.decode(File.read(manifest))
- assert asset_path = assets["assets"].find { |(k, _)| k !~ /rails.png/ && k =~ /.png/ }[1]
+ assert asset_path = assets["assets"].find { |(k, _)| k && k =~ /.png/ }[1]
require "#{app_path}/config/environment"
@@ -431,6 +436,7 @@ module ApplicationTests
end
test "asset urls should be protocol-relative if no request is in scope" do
+ app_file "app/assets/images/rails.png", "notreallyapng"
app_file "app/assets/javascripts/image_loader.js.erb", 'var src="<%= image_path("rails.png") %>";'
add_to_config "config.assets.precompile = %w{image_loader.js}"
add_to_config "config.asset_host = 'example.com'"
@@ -441,6 +447,7 @@ module ApplicationTests
test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do
ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri"
+ app_file "app/assets/images/rails.png", "notreallyapng"
app_file "app/assets/javascripts/app.js.erb", 'var src="<%= image_path("rails.png") %>";'
add_to_config "config.assets.precompile = %w{app.js}"
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 1acf03f35a..28839a9c4b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -62,6 +62,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
ActiveRecord::Migrator.stubs(:needs_migration?).returns(true)
+ ActiveRecord::NullMigration.any_instance.stubs(:mtime).returns(1)
get "/foo"
assert_equal 500, last_response.status
@@ -158,12 +159,12 @@ module ApplicationTests
RUBY
require "#{app_path}/config/application"
- assert AppTemplate::Application.initialize!
+ assert Rails.application.initialize!
end
test "application is always added to eager_load namespaces" do
require "#{app_path}/config/application"
- assert AppTemplate::Application, AppTemplate::Application.config.eager_load_namespaces
+ assert Rails.application, Rails.application.config.eager_load_namespaces
end
test "the application can be eager loaded even when there are no frameworks" do
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 80700a1d64..31bc003dcb 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -127,24 +127,23 @@ class FullStackConsoleTest < ActiveSupport::TestCase
end
def spawn_console
- pid = Process.spawn(
+ Process.spawn(
"#{app_path}/bin/rails console --sandbox",
in: @slave, out: @slave, err: @slave
)
assert_output "> ", 30
- pid
end
def test_sandbox
- pid = spawn_console
+ spawn_console
write_prompt "Post.count", "=> 0"
write_prompt "Post.create"
write_prompt "Post.count", "=> 1"
@master.puts "quit"
- pid = spawn_console
+ spawn_console
write_prompt "Post.count", "=> 0"
write_prompt "Post.transaction { Post.create; raise }"
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index 9b18c329ec..0c66213caa 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -72,6 +72,7 @@ module ApplicationTests
end
test "load environment with global" do
+ $initialize_test_set_from_env = nil
app_file "config/environments/development.rb", <<-RUBY
$initialize_test_set_from_env = 'success'
AppTemplate::Application.configure do
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index ad7172c514..17926ac444 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -48,7 +48,7 @@ class LoadingTest < ActiveSupport::TestCase
test "load config/environments/environment before Bootstrap initializers" do
app_file "config/environments/development.rb", <<-RUBY
- AppTemplate::Application.configure do
+ Rails.application.configure do
config.development_environment_loaded = true
end
RUBY
@@ -60,7 +60,7 @@ class LoadingTest < ActiveSupport::TestCase
RUBY
require "#{app_path}/config/environment"
- assert ::AppTemplate::Application.config.loaded
+ assert ::Rails.application.config.loaded
end
test "descendants loaded after framework initialization are cleaned on each request without cache classes" do
@@ -75,7 +75,7 @@ class LoadingTest < ActiveSupport::TestCase
MODEL
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get '/load', to: lambda { |env| [200, {}, Post.all] }
get '/unload', to: lambda { |env| [200, {}, []] }
end
@@ -96,7 +96,7 @@ class LoadingTest < ActiveSupport::TestCase
test "initialize cant be called twice" do
require "#{app_path}/config/environment"
- assert_raise(RuntimeError) { ::AppTemplate::Application.initialize! }
+ assert_raise(RuntimeError) { Rails.application.initialize! }
end
test "reload constants on development" do
@@ -105,7 +105,7 @@ class LoadingTest < ActiveSupport::TestCase
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
end
RUBY
@@ -144,7 +144,7 @@ class LoadingTest < ActiveSupport::TestCase
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
end
RUBY
@@ -179,8 +179,8 @@ class LoadingTest < ActiveSupport::TestCase
RUBY
app_file 'config/routes.rb', <<-RUBY
- $counter = 0
- AppTemplate::Application.routes.draw do
+ $counter ||= 0
+ Rails.application.routes.draw do
get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
@@ -205,13 +205,46 @@ class LoadingTest < ActiveSupport::TestCase
assert_equal "2", last_response.body
end
- test "columns migrations also trigger reloading" do
+ test "dependencies reloading is followed by routes reloading" do
add_to_config <<-RUBY
config.cache_classes = false
RUBY
app_file 'config/routes.rb', <<-RUBY
+ $counter ||= 1
+ $counter *= 2
AppTemplate::Application.routes.draw do
+ get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ end
+ RUBY
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User
+ $counter += 1
+ end
+ MODEL
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ require "#{rails_root}/config/environment"
+
+ get "/c"
+ assert_equal "3", last_response.body
+
+ app_file "db/schema.rb", ""
+
+ get "/c"
+ assert_equal "7", last_response.body
+ end
+
+ test "columns migrations also trigger reloading" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
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
@@ -270,7 +303,7 @@ class LoadingTest < ActiveSupport::TestCase
RUBY
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get "/:controller(/:action)"
end
RUBY
@@ -288,10 +321,10 @@ class LoadingTest < ActiveSupport::TestCase
require "#{app_path}/config/application"
assert !Rails.initialized?
- assert !AppTemplate::Application.initialized?
+ assert !Rails.application.initialized?
Rails.initialize!
assert Rails.initialized?
- assert AppTemplate::Application.initialized?
+ assert Rails.application.initialized?
end
protected
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index f0d3438aa4..91c5807379 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -1,5 +1,4 @@
require 'isolation/abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
module ApplicationTests
@@ -10,7 +9,7 @@ module ApplicationTests
remote_ip = nil
env = Rack::MockRequest.env_for("/").merge(env).merge!(
'action_dispatch.show_exceptions' => false,
- 'action_dispatch.key_generator' => ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
+ 'action_dispatch.key_generator' => ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
)
endpoint = Proc.new do |e|
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index a5fdfbf887..14a56176f5 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -49,7 +49,7 @@ module ApplicationTests
test "session is empty and isn't saved on unverified request when using :null_session protect method" do
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller(/:action)'
post ':controller(/:action)'
end
@@ -90,7 +90,7 @@ module ApplicationTests
test "cookie jar is empty and isn't saved on unverified request when using :null_session protect method" do
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller(/:action)'
post ':controller(/:action)'
end
@@ -131,7 +131,7 @@ module ApplicationTests
test "session using encrypted cookie store" do
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller(/:action)'
end
RUBY
@@ -157,10 +157,6 @@ module ApplicationTests
end
RUBY
- add_to_config <<-RUBY
- config.session_store :encrypted_cookie_store, key: '_myapp_session'
- RUBY
-
require "#{app_path}/config/environment"
get '/foo/write_session'
@@ -178,9 +174,9 @@ module ApplicationTests
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
end
- test "session using upgrade signature to encryption cookie store works the same way as encrypted cookie store" do
+ test "session upgrading signature to encryption cookie store works the same way as encrypted cookie store" do
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller(/:action)'
end
RUBY
@@ -208,7 +204,6 @@ module ApplicationTests
add_to_config <<-RUBY
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
- config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
RUBY
require "#{app_path}/config/environment"
@@ -228,9 +223,9 @@ module ApplicationTests
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
end
- test "session using upgrade signature to encryption cookie store upgrades session to encrypted mode" do
+ test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller(/:action)'
end
RUBY
@@ -264,7 +259,6 @@ module ApplicationTests
add_to_config <<-RUBY
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
- config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
RUBY
require "#{app_path}/config/environment"
@@ -287,5 +281,63 @@ module ApplicationTests
get '/foo/read_raw_cookie'
assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo']
end
+
+ test "session upgrading legacy signed cookies to new signed cookies" do
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_raw_session
+ # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
+ cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749"
+ render nothing: true
+ end
+
+ def write_session
+ session[:foo] = session[:foo] + 1
+ render nothing: true
+ end
+
+ def read_session
+ render text: session[:foo]
+ end
+
+ def read_signed_cookie
+ render text: cookies.signed[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render text: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.secret_key_base = nil
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get '/foo/write_raw_session'
+ get '/foo/read_session'
+ assert_equal '1', last_response.body
+
+ get '/foo/write_session'
+ get '/foo/read_session'
+ assert_equal '2', last_response.body
+
+ get '/foo/read_signed_cookie'
+ assert_equal '2', last_response.body
+
+ verifier = ActiveSupport::MessageVerifier.new(app.config.secret_token)
+
+ get '/foo/read_raw_cookie'
+ assert_equal 2, verifier.verify(last_response.body)['foo']
+ end
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index d8076c7151..833114a34c 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -69,6 +69,14 @@ module ApplicationTests
assert_equal "Rack::Cache", middleware.first
end
+ test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do
+ add_to_config "config.active_record.migration_error = :page_load"
+
+ boot!
+
+ assert middleware.include?("ActiveRecord::Migration::CheckPending")
+ end
+
test "ActionDispatch::SSL is present when force_ssl is set" do
add_to_config "config.force_ssl = true"
boot!
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index a9e0e1bcb7..dc2684ae0d 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -34,7 +34,7 @@ module ApplicationTests
config.middleware.use SuperMiddleware
end
- AppTemplate::Application.initialize!
+ Rails.application.initialize!
RUBY
assert_match("SuperMiddleware", Dir.chdir(app_path){ `rake middleware` })
@@ -79,25 +79,30 @@ module ApplicationTests
assert_match "Hello world", output
end
- def test_code_statistics_sanity
- assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0",
- Dir.chdir(app_path){ `rake stats` }
- end
+ def test_should_not_eager_load_model_path_for_rake
+ add_to_config <<-RUBY
+ config.eager_load = true
- def test_rake_test_error_output
- Dir.chdir(app_path){ `rake db:migrate` }
+ rake_tasks do
+ task do_nothing: :environment do
+ end
+ end
+ RUBY
- app_file "test/models/one_model_test.rb", <<-RUBY
- raise 'models'
+ app_file "app/models/hello.rb", <<-RUBY
+ raise 'should not be pre-required for rake even `eager_load=true`'
RUBY
- silence_stderr do
- output = Dir.chdir(app_path) { `rake test 2>&1` }
- assert_match 'models', output
- end
+ Dir.chdir(app_path){ `rake do_nothing` }
+ end
+
+ def test_code_statistics_sanity
+ assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0",
+ Dir.chdir(app_path){ `rake stats` }
end
def test_rake_test_uncommitted_always_find_git_in_parent_dir
+ return "FIXME :'("
app_name = File.basename(app_path)
app_dir = File.dirname(app_path)
moved_app_name = app_name + '_moved'
@@ -129,13 +134,10 @@ module ApplicationTests
Dir.chdir(app_path){ `rails generate scaffold user name:string` }
Dir.chdir(app_path){ `rake db:migrate` }
- %w(run recent uncommitted models helpers units controllers functionals integration).each do |test_suit_name|
+ %w(recent uncommitted).each do |test_suit_name|
output = Dir.chdir(app_path) { `rake test:#{test_suit_name} 2>&1` }
- assert_match /DEPRECATION WARNING: `rake test:#{test_suit_name}` is deprecated/, output
+ assert_match(/DEPRECATION WARNING: `rake test:#{test_suit_name}` is deprecated/, output)
end
-
- assert_match /DEPRECATION WARNING: `rake test:single` is deprecated/,
- Dir.chdir(app_path) { `rake test:single TEST=test/models/user_test.rb 2>&1` }
end
def test_rake_routes_calls_the_route_inspector
@@ -149,6 +151,19 @@ module ApplicationTests
assert_equal "Prefix Verb URI Pattern Controller#Action\ncart GET /cart(.:format) cart#show\n", output
end
+ def test_rake_routes_with_controller_environment
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/cart', to: 'cart#show'
+ get '/basketball', to: 'basketball#index'
+ end
+ RUBY
+
+ ENV['CONTROLLER'] = 'cart'
+ output = Dir.chdir(app_path){ `rake routes` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\ncart GET /cart(.:format) cart#show\n", output
+ end
+
def test_rake_routes_displays_message_when_no_routes_are_defined
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
@@ -212,7 +227,7 @@ module ApplicationTests
bundle exec rake db:migrate db:test:clone test`
end
- assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
@@ -222,7 +237,7 @@ module ApplicationTests
bundle exec rake db:migrate db:test:clone test`
end
- assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
diff --git a/railties/test/application/rendering_test.rb b/railties/test/application/rendering_test.rb
new file mode 100644
index 0000000000..588d64dde9
--- /dev/null
+++ b/railties/test/application/rendering_test.rb
@@ -0,0 +1,45 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class RoutingTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "Unknown format falls back to HTML template" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'pages/:id', to: 'pages#show'
+ end
+ RUBY
+
+ app_file 'app/controllers/pages_controller.rb', <<-RUBY
+ class PagesController < ApplicationController
+ layout false
+
+ def show
+ end
+ end
+ RUBY
+
+ app_file 'app/views/pages/show.html.erb', <<-RUBY
+ <%= params[:id] %>
+ RUBY
+
+ get '/pages/foo'
+ assert_equal 200, last_response.status
+
+ get '/pages/foo.bar'
+ assert_equal 200, last_response.status
+ end
+ end
+end
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 22de640236..1a4e2d4123 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -48,7 +48,7 @@ module ApplicationTests
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
root to: "foo#index"
end
RUBY
@@ -100,7 +100,7 @@ module ApplicationTests
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller(/:action)'
end
RUBY
@@ -111,7 +111,7 @@ module ApplicationTests
test "mount rack app" do
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog"
# The line below is required because mount sometimes
# fails when a resource route is added.
@@ -141,7 +141,7 @@ module ApplicationTests
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller(/:action)'
end
RUBY
@@ -173,7 +173,7 @@ module ApplicationTests
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'admin/foo', to: 'admin/foo#index'
get 'foo', to: 'foo#index'
end
@@ -188,7 +188,7 @@ module ApplicationTests
test "routes appending blocks" do
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get ':controller/:action'
end
RUBY
@@ -205,7 +205,7 @@ module ApplicationTests
assert_equal 'WIN', last_response.body
app_file 'config/routes.rb', <<-R
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'lol' => 'hello#index'
end
R
@@ -229,7 +229,7 @@ module ApplicationTests
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'foo', to: 'foo#bar'
end
RUBY
@@ -240,7 +240,7 @@ module ApplicationTests
assert_equal 'bar', last_response.body
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'foo', to: 'foo#baz'
end
RUBY
@@ -261,7 +261,7 @@ module ApplicationTests
end
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'foo', to: ::InitializeRackApp
end
RUBY
@@ -277,6 +277,30 @@ module ApplicationTests
end
end
+ def test_root_path
+ app('development')
+
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def index
+ render :text => "foo"
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get 'foo', :to => 'foo#index'
+ root :to => 'foo#index'
+ end
+ RUBY
+
+ remove_file 'public/index.html'
+
+ get '/'
+ assert_equal 'foo', last_response.body
+ end
+
test 'routes are added and removed when reloading' do
app('development')
@@ -297,7 +321,7 @@ module ApplicationTests
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'foo', to: 'foo#index'
end
RUBY
@@ -313,7 +337,7 @@ module ApplicationTests
end
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'foo', to: 'foo#index'
get 'bar', to: 'bar#index'
end
@@ -330,7 +354,7 @@ module ApplicationTests
assert_equal '/bar', Rails.application.routes.url_helpers.bar_path
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'foo', to: 'foo#index'
end
RUBY
@@ -356,7 +380,7 @@ module ApplicationTests
RUBY
app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
resources :yazilar
end
RUBY
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 56ca3bc1a9..118f22995e 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -15,14 +15,6 @@ module ApplicationTests
teardown_app
end
- def test_should_not_display_heading
- create_test_file
- run_test_command.tap do |output|
- assert_no_match "Run options:", output
- assert_no_match "Running tests:", output
- end
- end
-
def test_run_in_test_environment
app_file 'test/unit/env_test.rb', <<-RUBY
require 'test_helper'
@@ -37,21 +29,16 @@ module ApplicationTests
assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb')
end
- def test_run_shortcut
- create_test_file :models, 'foo'
- output = Dir.chdir(app_path) { `bundle exec rails t test/models/foo_test.rb` }
- assert_match "1 tests, 1 assertions, 0 failures", output
- end
-
def test_run_single_file
create_test_file :models, 'foo'
- assert_match "1 tests, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb")
+ create_test_file :models, 'bar'
+ assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb")
end
def test_run_multiple_files
create_test_file :models, 'foo'
create_test_file :models, 'bar'
- assert_match "2 tests, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb")
+ assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb")
end
def test_run_file_with_syntax_error
@@ -62,27 +49,17 @@ module ApplicationTests
error_stream = Tempfile.new('error')
redirect_stderr(error_stream) { run_test_command('test/models/error_test.rb') }
- assert_match "SyntaxError", error_stream.read
- end
-
- def test_invoke_rake_db_test_load
- app_file "lib/tasks/test.rake", <<-RUBY
- task 'db:test:load' do
- puts "Hello World"
- end
- RUBY
- create_test_file
- assert_match "Hello World", run_test_command
+ assert_match "syntax error", error_stream.read
end
def test_run_models
create_test_file :models, 'foo'
create_test_file :models, 'bar'
create_test_file :controllers, 'foobar_controller'
- run_test_command("models").tap do |output|
+ run_test_models_command.tap do |output|
assert_match "FooTest", output
assert_match "BarTest", output
- assert_match "2 tests, 2 assertions, 0 failures", output
+ assert_match "2 runs, 2 assertions, 0 failures", output
end
end
@@ -90,10 +67,10 @@ module ApplicationTests
create_test_file :helpers, 'foo_helper'
create_test_file :helpers, 'bar_helper'
create_test_file :controllers, 'foobar_controller'
- run_test_command('helpers').tap do |output|
+ run_test_helpers_command.tap do |output|
assert_match "FooHelperTest", output
assert_match "BarHelperTest", output
- assert_match "2 tests, 2 assertions, 0 failures", output
+ assert_match "2 runs, 2 assertions, 0 failures", output
end
end
@@ -102,11 +79,11 @@ module ApplicationTests
create_test_file :helpers, 'bar_helper'
create_test_file :unit, 'baz_unit'
create_test_file :controllers, 'foobar_controller'
- run_test_command('units').tap do |output|
+ run_test_units_command.tap do |output|
assert_match "FooTest", output
assert_match "BarHelperTest", output
assert_match "BazUnitTest", output
- assert_match "3 tests, 3 assertions, 0 failures", output
+ assert_match "3 runs, 3 assertions, 0 failures", output
end
end
@@ -114,10 +91,10 @@ module ApplicationTests
create_test_file :controllers, 'foo_controller'
create_test_file :controllers, 'bar_controller'
create_test_file :models, 'foo'
- run_test_command('controllers').tap do |output|
+ run_test_controllers_command.tap do |output|
assert_match "FooControllerTest", output
assert_match "BarControllerTest", output
- assert_match "2 tests, 2 assertions, 0 failures", output
+ assert_match "2 runs, 2 assertions, 0 failures", output
end
end
@@ -125,10 +102,10 @@ module ApplicationTests
create_test_file :mailers, 'foo_mailer'
create_test_file :mailers, 'bar_mailer'
create_test_file :models, 'foo'
- run_test_command('mailers').tap do |output|
+ run_test_mailers_command.tap do |output|
assert_match "FooMailerTest", output
assert_match "BarMailerTest", output
- assert_match "2 tests, 2 assertions, 0 failures", output
+ assert_match "2 runs, 2 assertions, 0 failures", output
end
end
@@ -137,20 +114,20 @@ module ApplicationTests
create_test_file :controllers, 'bar_controller'
create_test_file :functional, 'baz_functional'
create_test_file :models, 'foo'
- run_test_command('functionals').tap do |output|
+ run_test_functionals_command.tap do |output|
assert_match "FooMailerTest", output
assert_match "BarControllerTest", output
assert_match "BazFunctionalTest", output
- assert_match "3 tests, 3 assertions, 0 failures", output
+ assert_match "3 runs, 3 assertions, 0 failures", output
end
end
def test_run_integration
create_test_file :integration, 'foo_integration'
create_test_file :models, 'foo'
- run_test_command('integration').tap do |output|
+ run_test_integration_command.tap do |output|
assert_match "FooIntegration", output
- assert_match "1 tests, 1 assertions, 0 failures", output
+ assert_match "1 runs, 1 assertions, 0 failures", output
end
end
@@ -159,7 +136,7 @@ module ApplicationTests
suites.each { |suite| create_test_file suite, "foo_#{suite}" }
run_test_command('') .tap do |output|
suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output }
- assert_match "7 tests, 7 assertions, 0 failures", output
+ assert_match "7 runs, 7 assertions, 0 failures", output
end
end
@@ -178,17 +155,31 @@ module ApplicationTests
end
RUBY
- run_test_command('test/unit/chu_2_koi_test.rb -n test_rikka').tap do |output|
+ run_test_command('test/unit/chu_2_koi_test.rb test_rikka').tap do |output|
assert_match "Rikka", output
assert_no_match "Sanae", output
end
end
- def test_not_load_fixtures_when_running_single_test
- create_model_with_fixture
- create_fixture_test :models, 'user'
- assert_match "0 users", run_test_command('test/models/user_test.rb')
- assert_match "3 users", run_test_command('test/models/user_test.rb -f')
+ def test_run_matched_test
+ app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class Chu2KoiTest < ActiveSupport::TestCase
+ def test_rikka
+ puts 'Rikka'
+ end
+
+ def test_sanae
+ puts 'Sanae'
+ end
+ end
+ RUBY
+
+ run_test_command('test/unit/chu_2_koi_test.rb /rikka/').tap do |output|
+ assert_match "Rikka", output
+ assert_no_match "Sanae", output
+ end
end
def test_load_fixtures_when_running_test_suites
@@ -199,11 +190,18 @@ module ApplicationTests
suites.each do |suite, directory|
directory ||= suite
create_fixture_test directory
- assert_match "3 users", run_test_command(suite)
+ assert_match "3 users", run_task(["test:#{suite}"])
Dir.chdir(app_path) { FileUtils.rm_f "test/#{directory}" }
end
end
+ def test_run_with_model
+ create_model_with_fixture
+ create_fixture_test 'models', 'user'
+ assert_match "3 users", run_task(["test models/user"])
+ assert_match "3 users", run_task(["test app/models/user.rb"])
+ end
+
def test_run_different_environment_using_env_var
app_file 'test/unit/env_test.rb', <<-RUBY
require 'test_helper'
@@ -220,6 +218,7 @@ module ApplicationTests
end
def test_run_different_environment_using_e_tag
+ env = "development"
app_file 'test/unit/env_test.rb', <<-RUBY
require 'test_helper'
@@ -230,7 +229,7 @@ module ApplicationTests
end
RUBY
- assert_match "development", run_test_command('-e development test/unit/env_test.rb')
+ assert_match env, run_test_command("test/unit/env_test.rb RAILS_ENV=#{env}")
end
def test_generated_scaffold_works_with_rails_test
@@ -239,8 +238,17 @@ module ApplicationTests
end
private
+ def run_task(tasks)
+ Dir.chdir(app_path) { `bundle exec rake #{tasks.join ' '}` }
+ end
+
def run_test_command(arguments = 'test/unit/test_test.rb')
- Dir.chdir(app_path) { `bundle exec rails test #{arguments}` }
+ run_task ['test', arguments]
+ end
+ %w{ mailers models helpers units controllers functionals integration }.each do |type|
+ define_method("run_test_#{type}_command") do
+ run_task ["test:#{type}"]
+ end
end
def create_model_with_fixture
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index 0905757442..2767779719 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -20,7 +20,7 @@ module ApplicationTests
config.eager_load = false
end
- MyApp.initialize!
+ Rails.application.initialize!
class ::ApplicationController < ActionController::Base
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 3c784b43be..a34beaedb3 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -114,9 +114,10 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match('dev', options[:environment])
end
- private
-
attr_reader :output
+ private :output
+
+ private
def start(argv = [])
rails_console = Rails::Console.new(app, parse_arguments(argv))
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index 38fe8ca544..edb92b3aa2 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -172,8 +172,10 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
assert_match(/Usage:.*dbconsole/, stdout)
end
- private
attr_reader :aborted, :output
+ private :aborted, :output
+
+ private
def dbconsole
@dbconsole ||= Rails::DBConsole.new(nil)
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index f8fa8ee153..0db40c1d32 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -103,7 +103,7 @@ class ActionsTest < Rails::Generators::TestCase
run_generator
autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]'
action :environment, autoload_paths, env: 'development'
- assert_file "config/environments/development.rb", /Application\.configure do\n #{Regexp.escape(autoload_paths)}/
+ assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}/
end
def test_environment_with_block_should_include_block_contents_in_environment_initializer_block
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index b813a7f6bb..e992534938 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -65,7 +65,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_invalid_application_name_is_fixed
run_generator [File.join(destination_root, "things-43")]
- assert_file "things-43/config/environment.rb", /Things43::Application\.initialize!/
+ assert_file "things-43/config/environment.rb", /Rails\.application\.initialize!/
assert_file "things-43/config/application.rb", /^module Things43$/
end
@@ -111,7 +111,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
destination_root: app_moved_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:create_config_files) }
- assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/
+ assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/
assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/
end
@@ -131,7 +131,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_application_names_are_not_singularized
run_generator [File.join(destination_root, "hats")]
- assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/
+ assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/
end
def test_gemfile_has_no_whitespace_errors
@@ -183,9 +183,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator([destination_root, "-d", "jdbcmysql"])
assert_file "config/database.yml", /mysql/
assert_gem "activerecord-jdbcmysql-adapter"
- # TODO: When the JRuby guys merge jruby-openssl in
- # jruby this will be removed
- assert_gem "jruby-openssl" if defined?(JRUBY_VERSION)
end
def test_config_jdbcsqlite3_database
@@ -260,6 +257,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file 'test'
end
+ def test_creation_of_app_assets_images_directory
+ run_generator
+ assert_file "app/assets/images"
+ end
+
def test_creation_of_vendor_assets_javascripts_directory
run_generator
assert_file "vendor/assets/javascripts"
@@ -303,6 +305,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /# gem 'debugger'/
end
+ def test_inclusion_of_lazy_loaded_sdoc
+ run_generator
+ assert_file 'Gemfile', /gem 'sdoc', require: false/
+ end
+
def test_template_from_dir_pwd
FileUtils.cd(Rails.root)
assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]))
@@ -341,7 +348,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_new_hash_style
run_generator [destination_root]
assert_file "config/initializers/session_store.rb" do |file|
- assert_match(/config.session_store :encrypted_cookie_store, key: '_.+_session'/, file)
+ assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file)
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 48425cbf81..32c7612a8f 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -124,7 +124,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip_active_record"]
assert_no_file "test/dummy/config/database.yml"
assert_file "test/test_helper.rb" do |contents|
- assert_no_match /ActiveRecord/, contents
+ assert_no_match(/ActiveRecord/, contents)
end
end
@@ -168,14 +168,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
run_generator
FileUtils.cd destination_root
quietly { system 'bundle install' }
- assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
+ assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
end
def test_ensure_that_tests_works_in_full_mode
run_generator [destination_root, "--full", "--skip_active_record"]
FileUtils.cd destination_root
quietly { system 'bundle install' }
- assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
+ assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
end
def test_ensure_that_migration_tasks_work_with_mountable_option
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 25f299118c..d5ad978986 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -239,13 +239,27 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/
end
- def test_scaffold_generator_no_assets
+ def test_scaffold_generator_no_assets_with_switch_no_assets
run_generator [ "posts", "--no-assets" ]
assert_no_file "app/assets/stylesheets/scaffold.css"
assert_no_file "app/assets/javascripts/posts.js"
assert_no_file "app/assets/stylesheets/posts.css"
end
+ def test_scaffold_generator_no_assets_with_switch_assets_false
+ run_generator [ "posts", "--assets=false" ]
+ assert_no_file "app/assets/stylesheets/scaffold.css"
+ assert_no_file "app/assets/javascripts/posts.js"
+ assert_no_file "app/assets/stylesheets/posts.css"
+ end
+
+ def test_scaffold_generator_no_assets_with_switch_resource_route_false
+ run_generator [ "posts", "--resource-route=false" ]
+ assert_file "config/routes.rb" do |route|
+ assert_no_match(/resources :posts$/, route)
+ end
+ end
+
def test_scaffold_generator_no_stylesheets
run_generator [ "posts", "--no-stylesheets" ]
assert_no_file "app/assets/stylesheets/scaffold.css"
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 2724882a23..369a0ee46c 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -77,9 +77,9 @@ module SharedGeneratorTests
end
def test_template_raises_an_error_with_invalid_path
- content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) }
+ content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) }
assert_match(/The template \[.*\] could not be loaded/, content)
- assert_match(/non\/existant\/path/, content)
+ assert_match(/non\/existent\/path/, content)
end
def test_template_is_executed_when_supplied
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 9953aa929b..361784f509 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -164,7 +164,7 @@ class GeneratorsTest < Rails::Generators::TestCase
Rails::Generators.invoke "super_shoulda:model", ["Account"]
end
- def test_developer_options_are_overwriten_by_user_options
+ def test_developer_options_are_overwritten_by_user_options
Rails::Generators.options[:with_options] = { generate: false }
self.class.class_eval(<<-end_eval, __FILE__, __LINE__ + 1)
diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb
index 16e259be5d..ed9573453b 100644
--- a/railties/test/initializable_test.rb
+++ b/railties/test/initializable_test.rb
@@ -20,14 +20,6 @@ module InitializableTests
end
end
- module Word
- include Rails::Initializable
-
- initializer :word do
- $word = "bird"
- end
- end
-
class Parent
include Rails::Initializable
@@ -235,4 +227,4 @@ module InitializableTests
assert_equal [1, 2, 3, 4], $arr
end
end
-end \ No newline at end of file
+end
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 5b9088cb64..44a5fd1904 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -37,6 +37,11 @@ class InfoTest < ActiveSupport::TestCase
assert_property 'Goodbye', 'World'
end
+ def test_rails_version
+ assert_property 'Rails version',
+ File.read(File.realpath('../../../RAILS_VERSION', __FILE__)).chomp
+ end
+
def test_framework_version
assert_property 'Active Support version', ActiveSupport.version.to_s
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 26b388b6f9..05e9c4ab6e 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -187,7 +187,7 @@ module RailtiesTest
assert_equal "Hello bukkits\n", response[2].body
end
- test "adds its views to view paths with lower proriority than app ones" do
+ test "adds its views to view paths with lower priority than app ones" do
@plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
class BukkitController < ActionController::Base
def index
@@ -270,7 +270,7 @@ module RailtiesTest
RUBY
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get 'foo', :to => 'foo#index'
end
RUBY
@@ -416,11 +416,6 @@ YAML
boot_rails
end
- test "Rails::Engine itself does not respond to config" do
- boot_rails
- assert !Rails::Engine.respond_to?(:config)
- end
-
test "initializers are executed after application configuration initializers" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
@@ -472,7 +467,7 @@ YAML
RUBY
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
mount(Bukkits::Engine => "/bukkits")
end
RUBY
@@ -607,7 +602,7 @@ YAML
RUBY
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get "/bar" => "bar#index", as: "bar"
mount Bukkits::Engine => "/bukkits", as: "bukkits"
end
@@ -725,7 +720,7 @@ YAML
RUBY
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
mount Bukkits::Engine => "/bukkits", as: "bukkits"
end
RUBY
@@ -769,7 +764,7 @@ YAML
RUBY
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
mount Bukkits::Awesome::Engine => "/bukkits", :as => "bukkits"
end
RUBY
@@ -833,7 +828,7 @@ YAML
add_to_config "isolate_namespace AppTemplate"
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do end
+ Rails.application.routes.draw do end
RUBY
boot_rails
@@ -1211,7 +1206,7 @@ YAML
RUBY
app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
+ Rails.application.routes.draw do
get '/bar' => 'bar#index', :as => 'bar'
mount Bukkits::Engine => "/bukkits", :as => "bukkits"
end
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index 0abb2b7578..7348d70c56 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -44,7 +44,7 @@ module RailtiesTests
Dir.chdir(engine_path) do
File.open("Gemfile", "w") do |f|
f.write <<-GEMFILE.gsub(/^ {12}/, '')
- source "http://rubygems.org"
+ source "https://rubygems.org"
gem 'rails', path: '#{RAILS_FRAMEWORK_ROOT}'
gem 'sqlite3'
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 80559a6e36..c94937f964 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -13,6 +13,7 @@ module ApplicationTests
@simple_plugin = engine "weblog"
@plugin = engine "blog"
+ @metrics_plugin = engine "metrics"
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
@@ -28,6 +29,7 @@ module ApplicationTests
scope "/:user", :user => "anonymous" do
mount Blog::Engine => "/blog"
end
+ mount Metrics::Engine => "/metrics"
root :to => 'main#index'
end
RUBY
@@ -54,6 +56,34 @@ module ApplicationTests
end
RUBY
+ @metrics_plugin.write "lib/metrics.rb", <<-RUBY
+ module Metrics
+ class Engine < ::Rails::Engine
+ isolate_namespace(Metrics)
+ end
+ end
+ RUBY
+
+ @metrics_plugin.write "config/routes.rb", <<-RUBY
+ Metrics::Engine.routes.draw do
+ get '/generate_blog_route', to: 'generating#generate_blog_route'
+ get '/generate_blog_route_in_view', to: 'generating#generate_blog_route_in_view'
+ end
+ RUBY
+
+ @metrics_plugin.write "app/controllers/metrics/generating_controller.rb", <<-RUBY
+ module Metrics
+ class GeneratingController < ActionController::Base
+ def generate_blog_route
+ render text: blog.post_path(1)
+ end
+
+ def generate_blog_route_in_view
+ render inline: "<%= blog.post_path(1) -%>"
+ end
+ end
+ end
+ RUBY
@plugin.write "app/models/blog/post.rb", <<-RUBY
module Blog
@@ -201,6 +231,21 @@ module ApplicationTests
get "/somone/blog/application_route_in_view"
assert_equal "/", last_response.body
+ # test generating engine's route from other engine
+ get "/metrics/generate_blog_route"
+ assert_equal '/anonymous/blog/posts/1', last_response.body
+
+ get "/metrics/generate_blog_route_in_view"
+ assert_equal '/anonymous/blog/posts/1', last_response.body
+
+ # test generating engine's route from other engine with default_url_options
+ get "/metrics/generate_blog_route", {}, 'SCRIPT_NAME' => '/foo'
+ assert_equal '/foo/anonymous/blog/posts/1', last_response.body
+
+ get "/metrics/generate_blog_route_in_view", {}, 'SCRIPT_NAME' => '/foo'
+ assert_equal '/foo/anonymous/blog/posts/1', last_response.body
+
+
# test generating application's route from engine with default_url_options
get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo'
assert_equal "/foo/", last_response.body
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index c80b0f63af..4cbd4822be 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -19,33 +19,26 @@ module RailtiesTest
@app ||= Rails.application
end
- test "Rails::Railtie itself does not respond to config" do
- assert !Rails::Railtie.respond_to?(:config)
+ test "cannot instantiate a Railtie object" do
+ assert_raise(RuntimeError) { Rails::Railtie.new }
end
test "Railtie provides railtie_name" do
begin
class ::FooBarBaz < Rails::Railtie ; end
- assert_equal "foo_bar_baz", ::FooBarBaz.railtie_name
+ assert_equal "foo_bar_baz", FooBarBaz.railtie_name
ensure
Object.send(:remove_const, :"FooBarBaz")
end
end
- test "railtie_name can be set manualy" do
+ test "railtie_name can be set manually" do
class Foo < Rails::Railtie
railtie_name "bar"
end
assert_equal "bar", Foo.railtie_name
end
- test "cannot inherit from a railtie" do
- class Foo < Rails::Railtie ; end
- assert_raise RuntimeError do
- class Bar < Foo; end
- end
- end
-
test "config is available to railtie" do
class Foo < Rails::Railtie ; end
assert_nil Foo.config.action_controller.foo
@@ -65,7 +58,7 @@ module RailtiesTest
config.foo.greetings = "hello"
end
require "#{app_path}/config/application"
- assert_equal "hello", AppTemplate::Application.config.foo.greetings
+ assert_equal "hello", Rails.application.config.foo.greetings
end
test "railtie can add to_prepare callbacks" do
@@ -103,7 +96,7 @@ module RailtiesTest
require 'rake/testtask'
require 'rdoc/task'
- AppTemplate::Application.load_tasks
+ Rails.application.load_tasks
assert $ran_block
end
@@ -127,7 +120,7 @@ module RailtiesTest
require 'rake/testtask'
require 'rdoc/task'
- AppTemplate::Application.load_tasks
+ Rails.application.load_tasks
assert $ran_block.include?("my_tie")
end
@@ -143,7 +136,7 @@ module RailtiesTest
require "#{app_path}/config/environment"
assert !$ran_block
- AppTemplate::Application.load_generators
+ Rails.application.load_generators
assert $ran_block
end
@@ -159,7 +152,7 @@ module RailtiesTest
require "#{app_path}/config/environment"
assert !$ran_block
- AppTemplate::Application.load_console
+ Rails.application.load_console
assert $ran_block
end
@@ -175,7 +168,7 @@ module RailtiesTest
require "#{app_path}/config/environment"
assert !$ran_block
- AppTemplate::Application.load_runner
+ Rails.application.load_runner
assert $ran_block
end
diff --git a/railties/test/test_info_test.rb b/railties/test/test_info_test.rb
new file mode 100644
index 0000000000..d5463c11de
--- /dev/null
+++ b/railties/test/test_info_test.rb
@@ -0,0 +1,59 @@
+require 'abstract_unit'
+require 'rails/test_unit/sub_test_task'
+
+module Rails
+ class TestInfoTest < ActiveSupport::TestCase
+ def test_test_files
+ info = new_test_info ['test']
+ assert_predicate info.files, :empty?
+ assert_nil info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_file
+ info = new_test_info ['test', __FILE__]
+ assert_equal [__FILE__], info.files
+ assert_nil info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_opts
+ info = new_test_info ['test', __FILE__, '/foo/']
+ assert_equal [__FILE__], info.files
+ assert_equal '-n /foo/', info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_model_shorthand
+ info = new_test_info ['test', 'models/foo', '/foo/']
+
+ def info.test_file?(file)
+ file == "test/models/foo_test.rb" || super
+ end
+
+ assert_equal ['test/models/foo_test.rb'], info.files
+ assert_equal '-n /foo/', info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_model_path
+ info = new_test_info ['test', 'app/models/foo.rb', '/foo/']
+
+ def info.test_file?(file)
+ file == "test/models/foo_test.rb" || super
+ end
+
+ assert_equal ['test/models/foo_test.rb'], info.files
+ assert_equal '-n /foo/', info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def new_test_info(tasks)
+ Class.new(TestTask::TestInfo) {
+ def task_defined?(task)
+ task == "test"
+ end
+ }.new tasks
+ end
+ end
+end
diff --git a/tasks/release.rb b/tasks/release.rb
index cf5b6d6843..0c22f812fc 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -23,8 +23,25 @@ directory "pkg"
file = Dir[glob].first
ruby = File.read(file)
- ruby.gsub!(/^(\s*)Gem::Version\.new .*?$/, "\\1Gem::Version.new \"#{version}\"")
- raise "Could not insert Gem::Version in #{file}" unless $1
+ if framework == "rails" || framework == "railties"
+ major, minor, tiny, pre = version.split('.')
+ pre = pre ? pre.inspect : "nil"
+
+ ruby.gsub!(/^(\s*)MAJOR(\s*)= .*?$/, "\\1MAJOR = #{major}")
+ raise "Could not insert MAJOR in #{file}" unless $1
+
+ ruby.gsub!(/^(\s*)MINOR(\s*)= .*?$/, "\\1MINOR = #{minor}")
+ raise "Could not insert MINOR in #{file}" unless $1
+
+ ruby.gsub!(/^(\s*)TINY(\s*)= .*?$/, "\\1TINY = #{tiny}")
+ raise "Could not insert TINY in #{file}" unless $1
+
+ ruby.gsub!(/^(\s*)PRE(\s*)= .*?$/, "\\1PRE = #{pre}")
+ raise "Could not insert PRE in #{file}" unless $1
+ else
+ ruby.gsub!(/^(\s*)Gem::Version\.new .*?$/, "\\1Gem::Version.new \"#{version}\"")
+ raise "Could not insert Gem::Version in #{file}" unless $1
+ end
File.open(file, 'w') { |f| f.write ruby }
end
diff --git a/version.rb b/version.rb
index 367d0f6546..5a6d8d0983 100644
--- a/version.rb
+++ b/version.rb
@@ -1,10 +1,10 @@
module Rails
- def self.version
- Gem::Version.new "4.0.0.beta1"
- end
+ module VERSION
+ MAJOR = 4
+ MINOR = 1
+ TINY = 0
+ PRE = "beta"
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = Rails.version.segments
- STRING = Rails.version.to_s
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
end