aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--CONTRIBUTING.md40
-rw-r--r--Gemfile36
-rw-r--r--Gemfile.lock264
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md12
-rw-r--r--Rakefile9
-rw-r--r--actioncable/CHANGELOG.md5
-rw-r--r--actioncable/MIT-LICENSE20
-rw-r--r--actioncable/README.md476
-rw-r--r--actioncable/Rakefile13
-rw-r--r--actioncable/actioncable.gemspec31
-rw-r--r--actioncable/lib/action_cable.rb50
-rw-r--r--actioncable/lib/action_cable/channel.rb14
-rw-r--r--actioncable/lib/action_cable/channel/base.rb277
-rw-r--r--actioncable/lib/action_cable/channel/broadcasting.rb29
-rw-r--r--actioncable/lib/action_cable/channel/callbacks.rb35
-rw-r--r--actioncable/lib/action_cable/channel/naming.rb22
-rw-r--r--actioncable/lib/action_cable/channel/periodic_timers.rb41
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb114
-rw-r--r--actioncable/lib/action_cable/connection.rb16
-rw-r--r--actioncable/lib/action_cable/connection/authorization.rb13
-rw-r--r--actioncable/lib/action_cable/connection/base.rb214
-rw-r--r--actioncable/lib/action_cable/connection/identification.rb46
-rw-r--r--actioncable/lib/action_cable/connection/internal_channel.rb45
-rw-r--r--actioncable/lib/action_cable/connection/message_buffer.rb54
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb75
-rw-r--r--actioncable/lib/action_cable/connection/tagged_logger_proxy.rb40
-rw-r--r--actioncable/lib/action_cable/connection/web_socket.rb29
-rw-r--r--actioncable/lib/action_cable/engine.rb38
-rw-r--r--actioncable/lib/action_cable/gem_version.rb15
-rw-r--r--actioncable/lib/action_cable/helpers/action_cable_helper.rb29
-rw-r--r--actioncable/lib/action_cable/process/logging.rb7
-rw-r--r--actioncable/lib/action_cable/remote_connections.rb64
-rw-r--r--actioncable/lib/action_cable/server.rb19
-rw-r--r--actioncable/lib/action_cable/server/base.rb74
-rw-r--r--actioncable/lib/action_cable/server/broadcasting.rb54
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb35
-rw-r--r--actioncable/lib/action_cable/server/connections.rb37
-rw-r--r--actioncable/lib/action_cable/server/worker.rb71
-rw-r--r--actioncable/lib/action_cable/server/worker/active_record_connection_management.rb22
-rw-r--r--actioncable/lib/action_cable/version.rb8
-rw-r--r--actioncable/lib/assets/javascripts/action_cable.coffee.erb23
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/connection.coffee81
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee79
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/consumer.coffee25
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/subscription.coffee68
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee64
-rw-r--r--actioncable/lib/rails/generators/channel/USAGE14
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb26
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/channel.coffee14
-rw-r--r--actioncable/lib/rails/generators/channel/templates/channel.rb17
-rw-r--r--actioncable/test/channel/base_test.rb184
-rw-r--r--actioncable/test/channel/broadcasting_test.rb29
-rw-r--r--actioncable/test/channel/naming_test.rb10
-rw-r--r--actioncable/test/channel/periodic_timers_test.rb40
-rw-r--r--actioncable/test/channel/rejection_test.rb25
-rw-r--r--actioncable/test/channel/stream_test.rb80
-rw-r--r--actioncable/test/connection/authorization_test.rb31
-rw-r--r--actioncable/test/connection/base_test.rb117
-rw-r--r--actioncable/test/connection/cross_site_forgery_test.rb81
-rw-r--r--actioncable/test/connection/identifier_test.rb77
-rw-r--r--actioncable/test/connection/multiple_identifiers_test.rb41
-rw-r--r--actioncable/test/connection/string_identifier_test.rb43
-rw-r--r--actioncable/test/connection/subscriptions_test.rb115
-rw-r--r--actioncable/test/stubs/global_id.rb8
-rw-r--r--actioncable/test/stubs/room.rb16
-rw-r--r--actioncable/test/stubs/test_connection.rb21
-rw-r--r--actioncable/test/stubs/test_server.rb15
-rw-r--r--actioncable/test/stubs/user.rb15
-rw-r--r--actioncable/test/test_helper.rb37
-rw-r--r--actioncable/test/worker_test.rb47
-rw-r--r--actionmailer/CHANGELOG.md6
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/README.rdoc3
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb22
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb2
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb3
-rw-r--r--actionmailer/test/fixtures/async_mailer/welcome.erb1
-rw-r--r--actionpack/CHANGELOG.md154
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/README.rdoc3
-rw-r--r--actionpack/lib/abstract_controller/base.rb4
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb3
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb51
-rw-r--r--actionpack/lib/action_controller/metal.rb2
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb13
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb40
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb107
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb109
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb36
-rw-r--r--actionpack/lib/action_controller/test_case.rb20
-rw-r--r--actionpack/lib/action_dispatch.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb26
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb13
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb3
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb105
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb25
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb61
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb19
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb49
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb37
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb2
-rw-r--r--actionpack/test/abstract_unit.rb12
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb56
-rw-r--r--actionpack/test/controller/base_test.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb25
-rw-r--r--actionpack/test/controller/helper_test.rb10
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb21
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb8
-rw-r--r--actionpack/test/controller/live_stream_test.rb39
-rw-r--r--actionpack/test/controller/metal/renderers_test.rb42
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb10
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb2
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb6
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb52
-rw-r--r--actionpack/test/controller/redirect_test.rb42
-rw-r--r--actionpack/test/controller/render_other_test.rb24
-rw-r--r--actionpack/test/controller/renderers_test.rb90
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb221
-rw-r--r--actionpack/test/controller/routing_test.rb12
-rw-r--r--actionpack/test/controller/test_case_test.rb2
-rw-r--r--actionpack/test/controller/url_for_test.rb7
-rw-r--r--actionpack/test/dispatch/cookies_test.rb2
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb69
-rw-r--r--actionpack/test/dispatch/live_response_test.rb2
-rw-r--r--actionpack/test/dispatch/mapper_test.rb14
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb16
-rw-r--r--actionpack/test/dispatch/request/session_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb77
-rw-r--r--actionpack/test/dispatch/response_test.rb26
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb38
-rw-r--r--actionpack/test/dispatch/routing_test.rb62
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb16
-rw-r--r--actionpack/test/dispatch/ssl_test.rb45
-rw-r--r--actionpack/test/dispatch/static_test.rb4
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb7
-rw-r--r--actionview/CHANGELOG.md59
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/README.rdoc3
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/flows.rb2
-rw-r--r--actionview/lib/action_view/gem_version.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb8
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb21
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb10
-rw-r--r--actionview/lib/action_view/lookup_context.rb2
-rw-r--r--actionview/lib/action_view/railtie.rb1
-rw-r--r--actionview/lib/action_view/record_identifier.rb17
-rw-r--r--actionview/lib/action_view/rendering.rb2
-rw-r--r--actionview/lib/action_view/template/handlers.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/html.rb9
-rw-r--r--actionview/lib/action_view/template/handlers/raw.rb4
-rw-r--r--actionview/lib/action_view/template/resolver.rb2
-rw-r--r--actionview/lib/action_view/template/types.rb29
-rw-r--r--actionview/lib/action_view/test_case.rb2
-rw-r--r--actionview/test/fixtures/layouts/render_partial_html.erb2
-rw-r--r--actionview/test/fixtures/test/_partialhtml.html1
-rw-r--r--actionview/test/lib/controller/fake_models.rb21
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb2
-rw-r--r--actionview/test/template/capture_helper_test.rb13
-rw-r--r--actionview/test/template/date_helper_test.rb22
-rw-r--r--actionview/test/template/form_collections_helper_test.rb12
-rw-r--r--actionview/test/template/form_helper_test.rb60
-rw-r--r--actionview/test/template/form_tag_helper_test.rb7
-rw-r--r--actionview/test/template/render_test.rb11
-rw-r--r--actionview/test/template/test_case_test.rb4
-rw-r--r--actionview/test/template/text_helper_test.rb32
-rw-r--r--actionview/test/template/translation_helper_test.rb10
-rw-r--r--actionview/test/template/url_helper_test.rb15
-rw-r--r--activejob/CHANGELOG.md4
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/README.md2
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/arguments.rb4
-rw-r--r--activejob/lib/active_job/gem_version.rb2
-rw-r--r--activejob/test/cases/argument_serialization_test.rb2
-rw-r--r--activemodel/CHANGELOG.md3
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/README.rdoc3
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb12
-rw-r--r--activemodel/lib/active_model/gem_version.rb2
-rw-r--r--activemodel/lib/active_model/type/time.rb8
-rw-r--r--activemodel/lib/active_model/type/value.rb9
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb11
-rw-r--r--activemodel/test/cases/callbacks_test.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb10
-rw-r--r--activemodel/test/cases/translation_test.rb5
-rw-r--r--activemodel/test/cases/types_test.rb3
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb42
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb1
-rw-r--r--activerecord/CHANGELOG.md203
-rw-r--r--activerecord/MIT-LICENSE4
-rw-r--r--activerecord/README.rdoc7
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc1
-rw-r--r--activerecord/Rakefile14
-rw-r--r--activerecord/activerecord.gemspec2
-rwxr-xr-xactiverecord/bin/test2
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/associations.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb3
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb9
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb22
-rw-r--r--activerecord/lib/active_record/autosave_association.rb21
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb174
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb480
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb4
-rw-r--r--activerecord/lib/active_record/errors.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/gem_version.rb2
-rw-r--r--activerecord/lib/active_record/inheritance.rb20
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb46
-rw-r--r--activerecord/lib/active_record/migration.rb175
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb91
-rw-r--r--activerecord/lib/active_record/model_schema.rb17
-rw-r--r--activerecord/lib/active_record/persistence.rb7
-rw-r--r--activerecord/lib/active_record/railtie.rb11
-rw-r--r--activerecord/lib/active_record/railties/databases.rake26
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb13
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb66
-rw-r--r--activerecord/lib/active_record/relation/from_clause.rb2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb3
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb101
-rw-r--r--activerecord/lib/active_record/relation/record_fetch_warning.rb10
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb1
-rw-r--r--activerecord/lib/active_record/sanitization.rb11
-rw-r--r--activerecord/lib/active_record/schema.rb5
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/scoping.rb28
-rw-r--r--activerecord/lib/active_record/scoping/default.rb4
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb20
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb4
-rw-r--r--activerecord/lib/active_record/touch_later.rb16
-rw-r--r--activerecord/lib/active_record/transactions.rb26
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb10
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb31
-rw-r--r--activerecord/test/cases/adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb190
-rw-r--r--activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb54
-rw-r--r--activerecord/test/cases/adapters/mysql/charset_collation_test.rb54
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb210
-rw-r--r--activerecord/test/cases/adapters/mysql/consistency_test.rb49
-rw-r--r--activerecord/test/cases/adapters/mysql/enum_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql/explain_test.rb21
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb119
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb29
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb153
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb94
-rw-r--r--activerecord/test/cases/adapters/mysql/sp_test.rb30
-rw-r--r--activerecord/test/cases/adapters/mysql/sql_types_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql/statement_pool_test.rb19
-rw-r--r--activerecord/test/cases/adapters/mysql/table_options_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql/unsigned_type_test.rb65
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb44
-rw-r--r--activerecord/test/cases/adapters/mysql2/sp_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb19
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb5
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb16
-rw-r--r--activerecord/test/cases/ar_schema_test.rb4
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb47
-rw-r--r--activerecord/test/cases/base_test.rb57
-rw-r--r--activerecord/test/cases/binary_test.rb4
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb30
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/custom_locking_test.rb2
-rw-r--r--activerecord/test/cases/database_statements_test.rb15
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb37
-rw-r--r--activerecord/test/cases/defaults_test.rb39
-rw-r--r--activerecord/test/cases/finder_test.rb181
-rw-r--r--activerecord/test/cases/fixtures_test.rb5
-rw-r--r--activerecord/test/cases/helper.rb4
-rw-r--r--activerecord/test/cases/inheritance_test.rb45
-rw-r--r--activerecord/test/cases/invalid_connection_test.rb4
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb6
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb8
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb8
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb2
-rw-r--r--activerecord/test/cases/migration/columns_test.rb2
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb58
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb6
-rw-r--r--activerecord/test/cases/migration/index_test.rb12
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb97
-rw-r--r--activerecord/test/cases/migrator_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb21
-rw-r--r--activerecord/test/cases/primary_keys_test.rb6
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb2
-rw-r--r--activerecord/test/cases/relation/merging_test.rb7
-rw-r--r--activerecord/test/cases/relation/or_test.rb4
-rw-r--r--activerecord/test/cases/relation_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb36
-rw-r--r--activerecord/test/cases/sanitize_test.rb104
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb18
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb14
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb29
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb165
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb4
-rw-r--r--activerecord/test/cases/test_case.rb6
-rw-r--r--activerecord/test/cases/time_precision_test.rb34
-rw-r--r--activerecord/test/cases/touch_later_test.rb8
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb6
-rw-r--r--activerecord/test/cases/transactions_test.rb5
-rw-r--r--activerecord/test/cases/view_test.rb4
-rw-r--r--activerecord/test/config.example.yml9
-rw-r--r--activerecord/test/fixtures/author_addresses.yml6
-rw-r--r--activerecord/test/fixtures/authors.yml2
-rw-r--r--activerecord/test/fixtures/naked/yml/trees.yml3
-rw-r--r--activerecord/test/migrations/10_urban/9_add_expressions.rb2
-rw-r--r--activerecord/test/migrations/decimal/1_give_me_big_numbers.rb2
-rw-r--r--activerecord/test/migrations/magic/1_currencies_have_symbols.rb2
-rw-r--r--activerecord/test/migrations/missing/1000_people_have_middle_names.rb2
-rw-r--r--activerecord/test/migrations/missing/1_people_have_last_names.rb2
-rw-r--r--activerecord/test/migrations/missing/3_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/missing/4_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/rename/1_we_need_things.rb2
-rw-r--r--activerecord/test/migrations/rename/2_rename_things.rb2
-rw-r--r--activerecord/test/migrations/to_copy/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy/2_people_have_descriptions.rb2
-rw-r--r--activerecord/test/migrations/to_copy2/1_create_articles.rb2
-rw-r--r--activerecord/test/migrations/to_copy2/2_create_comments.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps2/20090101010101_create_articles.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps2/20090101010202_create_comments.rb2
-rw-r--r--activerecord/test/migrations/valid/1_valid_people_have_last_names.rb2
-rw-r--r--activerecord/test/migrations/valid/2_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/valid/3_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb2
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/valid_with_timestamps/20100101010101_valid_with_timestamps_people_have_last_names.rb2
-rw-r--r--activerecord/test/migrations/valid_with_timestamps/20100201010101_valid_with_timestamps_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb2
-rw-r--r--activerecord/test/models/bulb.rb6
-rw-r--r--activerecord/test/models/post.rb7
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb29
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb62
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb37
-rw-r--r--activerecord/test/schema/schema.rb2
-rw-r--r--activesupport/CHANGELOG.md115
-rw-r--r--activesupport/MIT-LICENSE4
-rw-r--r--activesupport/README.rdoc3
-rwxr-xr-xactivesupport/bin/generate_tables5
-rw-r--r--activesupport/lib/active_support.rb4
-rw-r--r--activesupport/lib/active_support/cache.rb25
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb12
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb53
-rw-r--r--activesupport/lib/active_support/callbacks.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb141
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb1
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb6
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb (renamed from activesupport/lib/active_support/file_evented_update_checker.rb)7
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb24
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb2
-rw-r--r--activesupport/lib/active_support/locale/en.yml2
-rw-r--r--activesupport/lib/active_support/logger.rb26
-rw-r--r--activesupport/lib/active_support/logger_silence.rb27
-rw-r--r--activesupport/lib/active_support/message_verifier.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb51
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb4
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb10
-rw-r--r--activesupport/lib/active_support/number_helper.rb10
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb2
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb2
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb3
-rw-r--r--activesupport/lib/active_support/test_case.rb10
-rw-r--r--activesupport/lib/active_support/testing/composite_filter.rb54
-rw-r--r--activesupport/test/caching_test.rb36
-rw-r--r--activesupport/test/callbacks_test.rb18
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb109
-rw-r--r--activesupport/test/core_ext/module/qualified_const_test.rb130
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb40
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb6
-rw-r--r--activesupport/test/deprecation_test.rb6
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb (renamed from activesupport/test/file_evented_update_checker_test.rb)13
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb1
-rw-r--r--activesupport/test/logger_test.rb97
-rw-r--r--activesupport/test/multibyte_chars_test.rb32
-rw-r--r--activesupport/test/multibyte_conformance_test.rb5
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance.rb76
-rw-r--r--activesupport/test/multibyte_normalization_conformance.rb129
-rw-r--r--activesupport/test/notifications_test.rb17
-rw-r--r--activesupport/test/number_helper_test.rb14
-rwxr-xr-xci/travis.rb28
-rw-r--r--guides/CHANGELOG.md2
-rw-r--r--guides/rails_guides/markdown/renderer.rb2
-rw-r--r--guides/source/5_0_release_notes.md768
-rw-r--r--guides/source/action_controller_overview.md12
-rw-r--r--guides/source/action_mailer_basics.md2
-rw-r--r--guides/source/action_view_overview.md18
-rw-r--r--guides/source/active_job_basics.md4
-rw-r--r--guides/source/active_model_basics.md2
-rw-r--r--guides/source/active_record_basics.md21
-rw-r--r--guides/source/active_record_callbacks.md51
-rw-r--r--guides/source/active_record_migrations.md68
-rw-r--r--guides/source/active_record_postgresql.md34
-rw-r--r--guides/source/active_record_querying.md89
-rw-r--r--guides/source/active_record_validations.md137
-rw-r--r--guides/source/active_support_core_extensions.md42
-rw-r--r--guides/source/api_app.md10
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/asset_pipeline.md8
-rw-r--r--guides/source/association_basics.md260
-rw-r--r--guides/source/autoloading_and_reloading_constants.md10
-rw-r--r--guides/source/caching_with_rails.md6
-rw-r--r--guides/source/command_line.md8
-rw-r--r--guides/source/configuring.md67
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md3
-rw-r--r--guides/source/debugging_rails_applications.md2
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/engines.md18
-rw-r--r--guides/source/form_helpers.md8
-rw-r--r--guides/source/getting_started.md28
-rw-r--r--guides/source/i18n.md4
-rw-r--r--guides/source/initialization.md31
-rw-r--r--guides/source/layouts_and_rendering.md19
-rw-r--r--guides/source/maintenance_policy.md6
-rw-r--r--guides/source/nested_model_forms.md6
-rw-r--r--guides/source/plugins.md134
-rw-r--r--guides/source/routing.md12
-rw-r--r--guides/source/security.md6
-rw-r--r--guides/source/testing.md48
-rw-r--r--guides/source/upgrading_ruby_on_rails.md31
-rw-r--r--guides/source/working_with_javascript_in_rails.md4
-rw-r--r--rails.gemspec1
-rw-r--r--railties/CHANGELOG.md77
-rw-r--r--railties/MIT-LICENSE4
-rw-r--r--railties/RDOC_MAIN.rdoc2
-rw-r--r--railties/Rakefile29
-rw-r--r--railties/lib/rails/all.rb19
-rw-r--r--railties/lib/rails/api/task.rb7
-rw-r--r--railties/lib/rails/application.rb6
-rw-r--r--railties/lib/rails/application/configuration.rb100
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb4
-rw-r--r--railties/lib/rails/command.rb70
-rw-r--r--railties/lib/rails/commands.rb7
-rw-r--r--railties/lib/rails/commands/commands_tasks.rb43
-rw-r--r--railties/lib/rails/commands/dbconsole.rb2
-rw-r--r--railties/lib/rails/commands/dev_cache.rb21
-rw-r--r--railties/lib/rails/commands/rake_proxy.rb34
-rw-r--r--railties/lib/rails/commands/runner.rb8
-rw-r--r--railties/lib/rails/commands/server.rb10
-rw-r--r--railties/lib/rails/gem_version.rb2
-rw-r--r--railties/lib/rails/generators.rb1
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/actions/create_migration.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb60
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb7
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/layout.html.erb5
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb6
-rw-r--r--railties/lib/rails/generators/named_base.rb16
-rw-r--r--railties/lib/rails/generators/rails/app/USAGE1
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb33
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb (renamed from actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb)2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.html.erb.tt13
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.text.erb.tt (renamed from railties/lib/rails/generators/erb/mailer/templates/layout.text.erb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru.tt11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore3
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE8
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/README.md28
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/README.rdoc3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/test.tt8
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb6
-rw-r--r--railties/lib/rails/generators/rails/scaffold/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css4
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb8
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml4
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb19
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb23
-rw-r--r--railties/lib/rails/tasks.rb1
-rw-r--r--railties/lib/rails/tasks/dev.rake14
-rw-r--r--railties/lib/rails/tasks/log.rake25
-rw-r--r--railties/lib/rails/test_unit/line_filtering.rb70
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb32
-rw-r--r--railties/lib/rails/test_unit/railtie.rb6
-rw-r--r--railties/lib/rails/test_unit/reporter.rb44
-rw-r--r--railties/lib/rails/test_unit/test_requirer.rb2
-rw-r--r--railties/lib/rails/test_unit/testing.rake7
-rw-r--r--railties/test/application/asset_debugging_test.rb5
-rw-r--r--railties/test/application/assets_test.rb8
-rw-r--r--railties/test/application/bin_setup_test.rb8
-rw-r--r--railties/test/application/configuration_test.rb79
-rw-r--r--railties/test/application/loading_test.rb12
-rw-r--r--railties/test/application/middleware/session_test.rb9
-rw-r--r--railties/test/application/rake/dbs_test.rb14
-rw-r--r--railties/test/application/rake/dev_test.rb35
-rw-r--r--railties/test/application/rake/migrations_test.rb6
-rw-r--r--railties/test/application/rake_test.rb28
-rw-r--r--railties/test/application/runner_test.rb10
-rw-r--r--railties/test/application/test_runner_test.rb101
-rw-r--r--railties/test/commands/dbconsole_test.rb6
-rw-r--r--railties/test/commands/dev_cache_test.rb32
-rw-r--r--railties/test/commands/server_test.rb9
-rw-r--r--railties/test/generators/actions_test.rb18
-rw-r--r--railties/test/generators/api_app_generator_test.rb4
-rw-r--r--railties/test/generators/app_generator_test.rb28
-rw-r--r--railties/test/generators/channel_generator_test.rb29
-rw-r--r--railties/test/generators/generator_test.rb15
-rw-r--r--railties/test/generators/mailer_generator_test.rb23
-rw-r--r--railties/test/generators/migration_generator_test.rb4
-rw-r--r--railties/test/generators/model_generator_test.rb27
-rw-r--r--railties/test/generators/namespaced_generators_test.rb16
-rw-r--r--railties/test/generators/plugin_generator_test.rb40
-rw-r--r--railties/test/generators/plugin_test_helper.rb24
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb105
-rw-r--r--railties/test/generators/resource_generator_test.rb2
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb20
-rw-r--r--railties/test/generators/scaffold_generator_test.rb20
-rw-r--r--railties/test/generators/shared_generator_tests.rb3
-rw-r--r--railties/test/generators/test_runner_in_engine_test.rb32
-rw-r--r--railties/test/railties/engine_test.rb18
-rw-r--r--railties/test/test_unit/reporter_test.rb41
-rw-r--r--tasks/release.rb6
-rw-r--r--version.rb2
642 files changed, 12281 insertions, 4790 deletions
diff --git a/.travis.yml b/.travis.yml
index 46be91d18e..ae38617b99 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,19 +16,19 @@ env:
matrix:
- "GEM=railties"
- "GEM=ap"
+ - "GEM=ac"
- "GEM=am,amo,as,av,aj"
- - "GEM=ar:mysql"
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
- "GEM=ar:postgresql"
- "GEM=aj:integration"
- "GEM=guides"
rvm:
- - 2.2.3
+ - 2.2.4
+ - 2.3.0
- ruby-head
matrix:
allow_failures:
- - env: "GEM=ar:mysql"
- rvm: ruby-head
fast_finish: true
notifications:
@@ -49,4 +49,4 @@ services:
- redis
- rabbitmq
addons:
- postgresql: "9.3"
+ postgresql: "9.4"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 699b6fd2d1..961b48733c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,16 +1,42 @@
-Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the team](http://contributors.rubyonrails.org)!
+## How to contribute to Ruby on Rails
-* If you want to submit a bug report please make sure to follow our [reporting guidelines](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#reporting-an-issue).
+#### **Did you find a bug?**
-* 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.
+* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/rails/rails/issues).
-* 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.
+* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/rails/rails/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
-*We only accept bug reports and pull requests on GitHub*.
+* If possible, use the relevant bug report templates to create the issue. Simply copy the content of the appropriate template into a .rb file, make the necessary changes to demonstrate the issue, and **paste the content into the issue description**:
+ * [**Active Record** (models, database) issues](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb)
+ * [**Action Pack** (controllers, routing) issues](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb)
+ * [**Generic template** for other issues](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_master.rb)
-* If you have a question about how to use Ruby on Rails, please [ask it on the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
+* For more detailed information on submitting a bug report and creating an issue, visit our [reporting guidelines](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#reporting-an-issue).
-* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
+#### **Did you write a patch that fixes a bug?**
+
+* Open a new GitHub pull request with the patch.
+
+* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
+
+* Before submitting, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide to know more about coding conventions and benchmarks.
+
+#### **Do you intend to add a new feature or change an existing one?**
+
+* Suggest your change in the [rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
+
+* Do not open an issue on GitHub until you have collected positive feedback about the change. GitHub issues are primarily intended for bug reports and fixes.
+
+#### **Do you have questions about the source code?**
+
+* Ask any question about how to use Ruby on Rails in the [rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
+
+#### **Do you want to contribute to the Rails documentation?**
+
+* Please read [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation).
+
+</br>
+Ruby on Rails is a volunteer effort. We encourage you to pitch in and [join the team](http://contributors.rubyonrails.org)!
Thanks! :heart: :heart: :heart:
diff --git a/Gemfile b/Gemfile
index b9fc37cba2..1fbf1dd52e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,29 +5,25 @@ gemspec
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem 'rake', '>= 10.3'
-# Active Job depends on URI::GID::MissingModelIDError, which isn't released yet.
-gem 'globalid', github: 'rails/globalid', branch: 'master'
-gem 'rack', github: 'rack/rack', branch: 'master'
-
# This needs to be with require false to ensure correct loading order, as has to
# be loaded after loading the test library.
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
+gem 'jquery-rails'
gem 'coffee-rails', '~> 4.1.0'
-gem 'turbolinks', github: 'rails/turbolinks', branch: 'master'
-gem 'arel', github: 'rails/arel', branch: 'master'
-gem 'mail', github: 'mikel/mail', branch: 'master'
-
-gem 'sprockets', '~> 4.0', github: 'rails/sprockets', branch: 'master'
-gem 'sprockets-rails', '~> 3.0.0.beta3', github: 'rails/sprockets-rails', branch: 'master'
-gem 'sass-rails', github: 'rails/sass-rails', branch: 'master'
+gem 'turbolinks'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid Active Model (and by extension the entire framework)
# being dependent on a binary library.
-gem 'bcrypt', '~> 3.1.10', require: false
+platforms :mingw, :x64_mingw, :mswin, :mswin64 do
+ gem 'bcrypt-ruby', '~> 3.0.0', require: false
+end
+
+platforms :ruby, :jruby, :rbx do
+ gem 'bcrypt', '~> 3.1.10', require: false
+end
# This needs to be with require false to avoid it being automatically loaded by
# sprockets.
@@ -45,14 +41,14 @@ end
# Active Support.
gem 'dalli', '>= 2.2.1'
-gem 'listen', '~> 3.0.4'
+gem 'listen', '~> 3.0.5', require: false
# Active Job.
group :job do
gem 'resque', require: false
gem 'resque-scheduler', require: false
gem 'sidekiq', require: false
- gem 'sucker_punch', require: false
+ gem 'sucker_punch', '< 2.0', require: false
gem 'delayed_job', require: false
gem 'queue_classic', github: "QueueClassic/queue_classic", branch: 'master', require: false, platforms: :ruby
gem 'sneakers', require: false
@@ -64,6 +60,11 @@ group :job do
gem 'sequel', require: false
end
+# Action Cable
+group :cable do
+ gem 'puma', require: false
+end
+
# Add your own local bundler stuff.
local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
instance_eval File.read local_gemfile if File.exist? local_gemfile
@@ -80,8 +81,8 @@ group :test do
gem 'benchmark-ips'
end
-platforms :ruby do
- gem 'nokogiri', '>= 1.6.7.rc3'
+platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
+ gem 'nokogiri', '>= 1.6.7.1'
# Needed for compiling the ActionDispatch::Journey parser.
gem 'racc', '>=1.4.6', require: false
@@ -91,7 +92,6 @@ platforms :ruby do
group :db do
gem 'pg', '>= 0.18.0'
- gem 'mysql', '>= 2.9.0'
gem 'mysql2', '>= 0.4.0'
end
end
diff --git a/Gemfile.lock b/Gemfile.lock
index f8d6d5d24b..4f17b7a81a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -20,143 +20,73 @@ GIT
redis-namespace
GIT
- remote: git://github.com/mikel/mail.git
- revision: 9c313a401729b9aa9177878836829a61adf67b54
- branch: master
- specs:
- mail (2.6.3.edge)
- mime-types (>= 1.16, < 3)
-
-GIT
- remote: git://github.com/rack/rack.git
- revision: 35599cfc2751e0ee611c0ff799924b8e7fe0c0b4
- branch: master
- specs:
- rack (2.0.0.alpha)
- json
-
-GIT
- remote: git://github.com/rails/arel.git
- revision: 3c429c5d86e9e2201c2a35d934ca6a8911c18e69
- branch: master
- specs:
- arel (7.0.0.alpha)
-
-GIT
- remote: git://github.com/rails/globalid.git
- revision: 1d8fca667740570d204fd955a0bd39ac539bac7f
- branch: master
- specs:
- globalid (0.3.6)
- activesupport (>= 4.1.0)
-
-GIT
- remote: git://github.com/rails/jquery-rails.git
- revision: 04fcfa29b859eef9479f89b6a799d00212902385
- branch: master
- specs:
- jquery-rails (4.0.5)
- rails-dom-testing (~> 1.0)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
-
-GIT
- remote: git://github.com/rails/sass-rails.git
- revision: 6e4eee736bcbfa5b2962467673c7a51abf434c67
- branch: master
- specs:
- sass-rails (6.0.0)
- railties (>= 4.0.0, < 5.0)
- sass (~> 3.4)
- sprockets (>= 4.0)
- sprockets-rails (< 4.0)
-
-GIT
- remote: git://github.com/rails/sprockets-rails.git
- revision: 93a45b1c463a063ec7cf4d160107b67aa3db7a1a
- branch: master
- specs:
- sprockets-rails (3.0.0.beta3)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
- sprockets (>= 3.0.0)
-
-GIT
- remote: git://github.com/rails/sprockets.git
- revision: 5a77f8b007b8ec61edd783c48baf9d971f1c684d
- branch: master
- specs:
- sprockets (4.0.0)
- rack (>= 1, < 3)
-
-GIT
- remote: git://github.com/rails/turbolinks.git
- revision: 83d4b3d2c52a681f07900c28adb28bc8da604733
- branch: master
- specs:
- turbolinks (3.0.0)
- coffee-rails
-
-GIT
remote: git://github.com/sass/sass.git
- revision: 4e3e1d5684cc29073a507578fc977434ff488c93
+ revision: bce9509f396225d721501ea1070a6871b708abb1
branch: stable
specs:
- sass (3.4.19)
+ sass (3.4.20)
PATH
remote: .
specs:
- actionmailer (5.0.0.alpha)
- actionpack (= 5.0.0.alpha)
- actionview (= 5.0.0.alpha)
- activejob (= 5.0.0.alpha)
+ actioncable (5.0.0.beta1)
+ actionpack (= 5.0.0.beta1)
+ coffee-rails (~> 4.1.0)
+ concurrent-ruby (~> 1.0.0)
+ em-hiredis (~> 0.3.0)
+ faye-websocket (~> 0.10.0)
+ redis (~> 3.0)
+ websocket-driver (~> 0.6.1)
+ actionmailer (5.0.0.beta1)
+ actionpack (= 5.0.0.beta1)
+ actionview (= 5.0.0.beta1)
+ activejob (= 5.0.0.beta1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (5.0.0.alpha)
- actionview (= 5.0.0.alpha)
- activesupport (= 5.0.0.alpha)
+ actionpack (5.0.0.beta1)
+ actionview (= 5.0.0.beta1)
+ activesupport (= 5.0.0.beta1)
rack (~> 2.x)
rack-test (~> 0.6.3)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.0.0.alpha)
- activesupport (= 5.0.0.alpha)
+ actionview (5.0.0.beta1)
+ activesupport (= 5.0.0.beta1)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (5.0.0.alpha)
- activesupport (= 5.0.0.alpha)
- globalid (>= 0.3.0)
- activemodel (5.0.0.alpha)
- activesupport (= 5.0.0.alpha)
- builder (~> 3.1)
- activerecord (5.0.0.alpha)
- activemodel (= 5.0.0.alpha)
- activesupport (= 5.0.0.alpha)
- arel (= 7.0.0.alpha)
- activesupport (5.0.0.alpha)
+ activejob (5.0.0.beta1)
+ activesupport (= 5.0.0.beta1)
+ globalid (>= 0.3.6)
+ activemodel (5.0.0.beta1)
+ activesupport (= 5.0.0.beta1)
+ activerecord (5.0.0.beta1)
+ activemodel (= 5.0.0.beta1)
+ activesupport (= 5.0.0.beta1)
+ arel (~> 7.0)
+ activesupport (5.0.0.beta1)
concurrent-ruby (~> 1.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
method_source
minitest (~> 5.1)
tzinfo (~> 1.1)
- rails (5.0.0.alpha)
- actionmailer (= 5.0.0.alpha)
- actionpack (= 5.0.0.alpha)
- actionview (= 5.0.0.alpha)
- activejob (= 5.0.0.alpha)
- activemodel (= 5.0.0.alpha)
- activerecord (= 5.0.0.alpha)
- activesupport (= 5.0.0.alpha)
+ rails (5.0.0.beta1)
+ actioncable (= 5.0.0.beta1)
+ actionmailer (= 5.0.0.beta1)
+ actionpack (= 5.0.0.beta1)
+ actionview (= 5.0.0.beta1)
+ activejob (= 5.0.0.beta1)
+ activemodel (= 5.0.0.beta1)
+ activerecord (= 5.0.0.beta1)
+ activesupport (= 5.0.0.beta1)
bundler (>= 1.3.0, < 2.0)
- railties (= 5.0.0.alpha)
+ railties (= 5.0.0.beta1)
sprockets-rails (>= 2.0.0)
- railties (5.0.0.alpha)
- actionpack (= 5.0.0.alpha)
- activesupport (= 5.0.0.alpha)
+ railties (5.0.0.beta1)
+ actionpack (= 5.0.0.beta1)
+ activesupport (= 5.0.0.beta1)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -165,18 +95,19 @@ GEM
remote: https://rubygems.org/
specs:
amq-protocol (2.0.0)
- backburner (1.1.0)
+ arel (7.0.0)
+ backburner (1.2.0)
beaneater (~> 1.0)
dante (> 0.1.5)
bcrypt (3.1.10)
- bcrypt (3.1.10-x64-mingw32)
- bcrypt (3.1.10-x86-mingw32)
+ bcrypt-ruby (3.0.1)
+ bcrypt-ruby (3.0.1-x86-mingw32)
beaneater (1.0.0)
benchmark-ips (2.3.0)
builder (3.2.2)
- bunny (2.2.0)
+ bunny (2.2.1)
amq-protocol (>= 2.0.0)
- byebug (8.2.0)
+ byebug (8.2.1)
celluloid (0.17.2)
celluloid-essentials
celluloid-extras
@@ -200,52 +131,78 @@ GEM
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.9.1.1)
+ coffee-script-source (1.10.0)
concurrent-ruby (1.0.0)
connection_pool (2.2.0)
- dalli (2.7.4)
+ dalli (2.7.5)
dante (0.2.0)
delayed_job (4.1.1)
activesupport (>= 3.0, < 5.0)
delayed_job_active_record (4.1.0)
activerecord (>= 3.0, < 5)
delayed_job (>= 3.0, < 5)
+ em-hiredis (0.3.0)
+ eventmachine (~> 1.0)
+ hiredis (~> 0.5.0)
erubis (2.7.0)
+ eventmachine (1.0.8)
execjs (2.6.0)
+ faye-websocket (0.10.2)
+ eventmachine (>= 0.12.0)
+ websocket-driver (>= 0.5.1)
ffi (1.9.10)
ffi (1.9.10-x64-mingw32)
ffi (1.9.10-x86-mingw32)
+ globalid (0.3.6)
+ activesupport (>= 4.1.0)
+ hiredis (0.5.2)
hitimes (1.2.3)
hitimes (1.2.3-x86-mingw32)
i18n (0.7.0)
+ jquery-rails (4.0.5)
+ rails-dom-testing (~> 1.0)
+ railties (>= 4.2.0)
+ thor (>= 0.14, < 2.0)
json (1.8.3)
kindlerb (0.1.1)
mustache
nokogiri
- listen (3.0.4)
+ listen (3.0.5)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
loofah (2.0.3)
nokogiri (>= 1.5.9)
+ mail (2.6.3)
+ mime-types (>= 1.16, < 3)
metaclass (0.0.4)
method_source (0.8.2)
- mime-types (2.6.2)
- mini_portile (0.7.0.rc4)
+ mime-types (2.99)
+ mini_portile2 (2.0.0)
minitest (5.3.3)
mocha (0.14.0)
metaclass (~> 0.0.1)
mono_logger (1.1.0)
multi_json (1.11.2)
mustache (1.0.2)
- mysql (2.9.1)
- mysql2 (0.4.1)
- nokogiri (1.6.7.rc3)
- mini_portile (~> 0.7.0.rc4)
- pg (0.18.3)
- psych (2.0.15)
+ mysql2 (0.4.2)
+ mysql2 (0.4.2-x64-mingw32)
+ mysql2 (0.4.2-x86-mingw32)
+ nokogiri (1.6.7.1)
+ mini_portile2 (~> 2.0.0.rc2)
+ nokogiri (1.6.7.1-x64-mingw32)
+ mini_portile2 (~> 2.0.0.rc2)
+ nokogiri (1.6.7.1-x86-mingw32)
+ mini_portile2 (~> 2.0.0.rc2)
+ pg (0.18.4)
+ pg (0.18.4-x64-mingw32)
+ pg (0.18.4-x86-mingw32)
+ psych (2.0.16)
+ puma (2.15.3)
que (0.11.2)
- racc (1.4.13)
- rack-cache (1.5.0)
+ racc (1.4.14)
+ rack (2.0.0.alpha)
+ json
+ rack-cache (1.5.1)
rack (>= 0.4)
rack-test (0.6.3)
rack (>= 1.0)
@@ -263,7 +220,7 @@ GEM
ffi (>= 0.5.0)
rdoc (4.2.0)
redcarpet (3.2.3)
- redis (3.2.1)
+ redis (3.2.2)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
resque (1.25.2)
@@ -277,19 +234,18 @@ GEM
redis (~> 3.0)
resque (~> 1.25)
rufus-scheduler (~> 3.0)
- rufus-scheduler (3.1.7)
+ rufus-scheduler (3.1.10)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
- sequel (4.27.0)
+ sequel (4.29.0)
serverengine (1.5.11)
sigdump (~> 0.2.2)
- sidekiq (3.5.1)
- celluloid (~> 0.17.2)
+ sidekiq (4.0.1)
+ concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
- redis-namespace (~> 1.5, >= 1.5.2)
sigdump (0.2.3)
sinatra (1.0)
rack (>= 1.0)
@@ -298,7 +254,16 @@ GEM
serverengine (~> 1.5.11)
thor
thread (~> 0.1.7)
+ sprockets (3.5.2)
+ concurrent-ruby (~> 1.0)
+ rack (> 1, < 3)
+ sprockets-rails (3.0.0)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
sqlite3 (1.3.11)
+ sqlite3 (1.3.11-x64-mingw32)
+ sqlite3 (1.3.11-x86-mingw32)
stackprof (0.2.7)
sucker_punch (1.6.0)
celluloid (~> 0.17.2)
@@ -307,6 +272,8 @@ GEM
thread_safe (0.3.5)
timers (4.1.1)
hitimes
+ turbolinks (2.5.3)
+ coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
tzinfo-data (1.2015.7)
@@ -319,6 +286,9 @@ GEM
w3c_validators (1.2)
json
nokogiri
+ websocket-driver (0.6.3)
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.2)
PLATFORMS
ruby
@@ -329,34 +299,31 @@ DEPENDENCIES
activerecord-jdbcmysql-adapter (>= 1.3.0)
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
- arel!
backburner
bcrypt (~> 3.1.10)
+ bcrypt-ruby (~> 3.0.0)
benchmark-ips
byebug
coffee-rails (~> 4.1.0)
dalli (>= 2.2.1)
delayed_job
delayed_job_active_record
- globalid!
- jquery-rails!
+ jquery-rails
json
kindlerb (= 0.1.1)
- listen (~> 3.0.4)
- mail!
+ listen (~> 3.0.5)
minitest (< 5.3.4)
mocha (~> 0.14)
- mysql (>= 2.9.0)
mysql2 (>= 0.4.0)
- nokogiri (>= 1.6.7.rc3)
+ nokogiri (>= 1.6.7.1)
pg (>= 0.18.0)
psych (~> 2.0)
+ puma
qu-rails!
qu-redis
que
queue_classic!
racc (>= 1.4.6)
- rack!
rack-cache (~> 1.2)
rails!
rake (>= 10.3)
@@ -364,20 +331,17 @@ DEPENDENCIES
resque
resque-scheduler
sass!
- sass-rails!
sdoc (~> 0.4.0)
sequel
sidekiq
sneakers
- sprockets (~> 4.0)!
- sprockets-rails (~> 3.0.0.beta3)!
sqlite3 (~> 1.3.6)
stackprof
- sucker_punch
- turbolinks!
+ sucker_punch (< 2.0)
+ turbolinks
tzinfo-data
uglifier (>= 1.3.0)
w3c_validators
BUNDLED WITH
- 1.10.6
+ 1.11.2
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 2b915d7d5c..80bb1e4be2 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-5.0.0.alpha
+5.0.0.beta1
diff --git a/README.md b/README.md
index f823a49f7d..b4f32940fc 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,9 @@ Active Record, Active Model, Action Pack, and Action View can each be used indep
In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library
to generate and send emails; Active Job ([README](activejob/README.md)), a
framework for declaring jobs and making them run on a variety of queueing
-backends; and Active Support ([README](activesupport/README.rdoc)), a collection
+backends; Action Cable ([README](actioncable/README.md)), a framework to
+integrate WebSockets with a Rails application;
+and Active Support ([README](activesupport/README.rdoc)), a collection
of utility classes and standard library extensions that are useful for Rails,
and may also be used independently outside Rails.
@@ -46,18 +48,18 @@ and may also be used independently outside Rails.
1. Install Rails at the command prompt if you haven't yet:
- gem install rails
+ $ gem install rails
2. At the command prompt, create a new Rails application:
- rails new myapp
+ $ rails new myapp
where "myapp" is the application name.
3. Change directory to `myapp` and start the web server:
- cd myapp
- rails server
+ $ cd myapp
+ $ rails server
Run with `--help` or `-h` for options.
diff --git a/Rakefile b/Rakefile
index 2ec39a1c85..b4517e2545 100644
--- a/Rakefile
+++ b/Rakefile
@@ -7,11 +7,12 @@ require 'railties/lib/rails/api/task'
desc "Build gem files for all projects"
task :build => "all:build"
+desc "Prepare the release"
+task :prep_release => "all:prep_release"
+
desc "Release all gems to rubygems and create a tag"
task :release => "all:release"
-PROJECTS = %w(activesupport activemodel actionpack actionview actionmailer activerecord railties activejob)
-
desc 'Run all tests by default'
task :default => %w(test test:isolated)
@@ -19,7 +20,7 @@ task :default => %w(test test:isolated)
desc "Run #{task_name} task for all projects"
task task_name do
errors = []
- PROJECTS.each do |project|
+ FRAMEWORKS.each do |project|
system(%(cd #{project} && #{$0} #{task_name})) || errors << project
end
fail("Errors in #{errors.join(', ')}") unless errors.empty?
@@ -28,7 +29,7 @@ end
desc "Smoke-test all projects"
task :smoke do
- (PROJECTS - %w(activerecord)).each do |project|
+ (FRAMEWORKS - %w(activerecord)).each do |project|
system %(cd #{project} && #{$0} test:isolated)
end
system %(cd activerecord && #{$0} sqlite3:isolated_test)
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
new file mode 100644
index 0000000000..22126d82b7
--- /dev/null
+++ b/actioncable/CHANGELOG.md
@@ -0,0 +1,5 @@
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
+* Added to Rails!
+
+ *DHH*
diff --git a/actioncable/MIT-LICENSE b/actioncable/MIT-LICENSE
new file mode 100644
index 0000000000..27a17cf41b
--- /dev/null
+++ b/actioncable/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015-2016 Basecamp, LLC
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/actioncable/README.md b/actioncable/README.md
new file mode 100644
index 0000000000..636f9935cf
--- /dev/null
+++ b/actioncable/README.md
@@ -0,0 +1,476 @@
+# Action Cable – Integrated WebSockets for Rails
+
+Action Cable seamlessly integrates WebSockets with the rest of your Rails application.
+It allows for real-time features to be written in Ruby in the same style
+and form as the rest of your Rails application, while still being performant
+and scalable. It's a full-stack offering that provides both a client-side
+JavaScript framework and a server-side Ruby framework. You have access to your full
+domain model written with Active Record or your ORM of choice.
+
+
+## Terminology
+
+A single Action Cable server can handle multiple connection instances. It has one
+connection instance per WebSocket connection. A single user may have multiple
+WebSockets open to your application if they use multiple browser tabs or devices.
+The client of a WebSocket connection is called the consumer.
+
+Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates
+a logical unit of work, similar to what a controller does in a regular MVC setup. For example,
+you could have a `ChatChannel` and a `AppearancesChannel`, and a consumer could be subscribed to either
+or to both of these channels. At the very least, a consumer should be subscribed to one channel.
+
+When the consumer is subscribed to a channel, they act as a subscriber. The connection between
+the subscriber and the channel is, surprise-surprise, called a subscription. A consumer
+can act as a subscriber to a given channel any number of times. For example, a consumer
+could subscribe to multiple chat rooms at the same time. (And remember that a physical user may
+have multiple consumers, one per tab/device open to your connection).
+
+Each channel can then again be streaming zero or more broadcastings. A broadcasting is a
+pubsub link where anything transmitted by the broadcaster is sent directly to the channel
+subscribers who are streaming that named broadcasting.
+
+As you can see, this is a fairly deep architectural stack. There's a lot of new terminology
+to identify the new pieces, and on top of that, you're dealing with both client and server side
+reflections of each unit.
+
+## Examples
+
+### A full-stack example
+
+The first thing you must do is define your `ApplicationCable::Connection` class in Ruby. This
+is the place where you authorize the incoming connection, and proceed to establish it
+if all is well. Here's the simplest example starting with the server-side connection class:
+
+```ruby
+# app/channels/application_cable/connection.rb
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_user
+
+ def connect
+ self.current_user = find_verified_user
+ end
+
+ protected
+ def find_verified_user
+ if current_user = User.find_by(id: cookies.signed[:user_id])
+ current_user
+ else
+ reject_unauthorized_connection
+ end
+ end
+ end
+end
+```
+Here `identified_by` is a connection identifier that can be used to find the specific connection again or later.
+Note that anything marked as an identifier will automatically create a delegate by the same name on any channel instances created off the connection.
+
+Then you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
+shared logic between your channels.
+
+```ruby
+# app/channels/application_cable/channel.rb
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
+```
+
+This relies on the fact that you will already have handled authentication of the user, and
+that a successful authentication sets a signed cookie with the `user_id`. This cookie is then
+automatically sent to the connection instance when a new connection is attempted, and you
+use that to set the `current_user`. By identifying the connection by this same current_user,
+you're also ensuring that you can later retrieve all open connections by a given user (and
+potentially disconnect them all if the user is deleted or deauthorized).
+
+The client-side needs to setup a consumer instance of this connection. That's done like so:
+
+```coffeescript
+# app/assets/javascripts/cable.coffee
+#= require action_cable
+
+@App = {}
+App.cable = ActionCable.createConsumer("ws://cable.example.com")
+```
+
+The ws://cable.example.com address must point to your set of Action Cable servers, and it
+must share a cookie namespace with the rest of the application (which may live under http://example.com).
+This ensures that the signed cookie will be correctly sent.
+
+That's all you need to establish the connection! But of course, this isn't very useful in
+itself. This just gives you the plumbing. To make stuff happen, you need content. That content
+is defined by declaring channels on the server and allowing the consumer to subscribe to them.
+
+
+### Channel example 1: User appearances
+
+Here's a simple example of a channel that tracks whether a user is online or not and what page they're on.
+(This is useful for creating presence features like showing a green dot next to a user name if they're online).
+
+First you declare the server-side channel:
+
+```ruby
+# app/channels/appearance_channel.rb
+class AppearanceChannel < ApplicationCable::Channel
+ def subscribed
+ current_user.appear
+ end
+
+ def unsubscribed
+ current_user.disappear
+ end
+
+ def appear(data)
+ current_user.appear on: data['appearing_on']
+ end
+
+ def away
+ current_user.away
+ end
+end
+```
+
+The `#subscribed` callback is invoked when, as we'll show below, a client-side subscription is initiated. In this case,
+we take that opportunity to say "the current user has indeed appeared". That appear/disappear API could be backed by
+Redis or a database or whatever else. Here's what the client-side of that looks like:
+
+```coffeescript
+# app/assets/javascripts/cable/subscriptions/appearance.coffee
+App.cable.subscriptions.create "AppearanceChannel",
+ # Called when the subscription is ready for use on the server
+ connected: ->
+ @install()
+ @appear()
+
+ # Called when the WebSocket connection is closed
+ disconnected: ->
+ @uninstall()
+
+ # Called when the subscription is rejected by the server
+ rejected: ->
+ @uninstall()
+
+ appear: ->
+ # Calls `AppearanceChannel#appear(data)` on the server
+ @perform("appear", appearing_on: $("main").data("appearing-on"))
+
+ away: ->
+ # Calls `AppearanceChannel#away` on the server
+ @perform("away")
+
+
+ buttonSelector = "[data-behavior~=appear_away]"
+
+ install: ->
+ $(document).on "page:change.appearance", =>
+ @appear()
+
+ $(document).on "click.appearance", buttonSelector, =>
+ @away()
+ false
+
+ $(buttonSelector).show()
+
+ uninstall: ->
+ $(document).off(".appearance")
+ $(buttonSelector).hide()
+```
+
+Simply calling `App.cable.subscriptions.create` will setup the subscription, which will call `AppearanceChannel#subscribed`,
+which in turn is linked to original `App.cable` -> `ApplicationCable::Connection` instances.
+
+We then link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side
+channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these
+can be reached as remote procedure calls via a subscription's `perform` method.
+
+### Channel example 2: Receiving new web notifications
+
+The appearance example was all about exposing server functionality to client-side invocation over the WebSocket connection.
+But the great thing about WebSockets is that it's a two-way street. So now let's show an example where the server invokes
+action on the client.
+
+This is a web notification channel that allows you to trigger client-side web notifications when you broadcast to the right
+streams:
+
+```ruby
+# app/channels/web_notifications_channel.rb
+class WebNotificationsChannel < ApplicationCable::Channel
+ def subscribed
+ stream_from "web_notifications_#{current_user.id}"
+ end
+end
+```
+
+```coffeescript
+# Client-side, which assumes you've already requested the right to send web notifications
+App.cable.subscriptions.create "WebNotificationsChannel",
+ received: (data) ->
+ new Notification data["title"], body: data["body"]
+```
+
+```ruby
+# Somewhere in your app this is called, perhaps from a NewCommentJob
+ActionCable.server.broadcast \
+ "web_notifications_#{current_user.id}", { title: 'New things!', body: 'All the news that is fit to print' }
+```
+
+The `ActionCable.server.broadcast` call places a message in the Redis' pubsub queue under a separate broadcasting name for each user. For a user with an ID of 1, the broadcasting name would be `web_notifications_1`.
+The channel has been instructed to stream everything that arrives at `web_notifications_1` directly to the client by invoking the
+`#received(data)` callback. The data is the hash sent as the second parameter to the server-side broadcast call, JSON encoded for the trip
+across the wire, and unpacked for the data argument arriving to `#received`.
+
+
+### Passing Parameters to Channel
+
+You can pass parameters from the client side to the server side when creating a subscription. For example:
+
+```ruby
+# app/channels/chat_channel.rb
+class ChatChannel < ApplicationCable::Channel
+ def subscribed
+ stream_from "chat_#{params[:room]}"
+ end
+end
+```
+
+Pass an object as the first argument to `subscriptions.create`, and that object will become your params hash in your cable channel. The keyword `channel` is required.
+
+```coffeescript
+# Client-side, which assumes you've already requested the right to send web notifications
+App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
+ received: (data) ->
+ @appendLine(data)
+
+ appendLine: (data) ->
+ html = @createLine(data)
+ $("[data-chat-room='Best Room']").append(html)
+
+ createLine: (data) ->
+ """
+ <article class="chat-line">
+ <span class="speaker">#{data["sent_by"]}</span>
+ <span class="body">#{data["body"]}</span>
+ </article>
+ """
+```
+
+```ruby
+# Somewhere in your app this is called, perhaps from a NewCommentJob
+ActionCable.server.broadcast \
+ "chat_#{room}", { sent_by: 'Paul', body: 'This is a cool chat app.' }
+```
+
+
+### Rebroadcasting message
+
+A common use case is to rebroadcast a message sent by one client to any other connected clients.
+
+```ruby
+# app/channels/chat_channel.rb
+class ChatChannel < ApplicationCable::Channel
+ def subscribed
+ stream_from "chat_#{params[:room]}"
+ end
+
+ def receive(data)
+ ActionCable.server.broadcast "chat_#{params[:room]}", data
+ end
+end
+```
+
+```coffeescript
+# Client-side, which assumes you've already requested the right to send web notifications
+App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
+ received: (data) ->
+ # data => { sent_by: "Paul", body: "This is a cool chat app." }
+
+App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
+```
+
+The rebroadcast will be received by all connected clients, _including_ the client that sent the message. Note that params are the same as they were when you subscribed to the channel.
+
+
+### More complete examples
+
+See the [rails/actioncable-examples](http://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app and adding channels.
+
+
+## Configuration
+
+Action Cable has three required configurations: the Redis connection, allowed request origins, and the cable server url (which can optionally be set on the client side).
+
+### Redis
+
+By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/redis/cable.yml')`.
+This file must specify a Redis url for each Rails environment. It may use the following format:
+
+```yaml
+production: &production
+ url: redis://10.10.3.153:6381
+development: &development
+ url: redis://localhost:6379
+test: *development
+```
+
+You can also change the location of the Redis config file in a Rails initializer with something like:
+
+```ruby
+Rails.application.paths.add "config/redis/cable", with: "somewhere/else/cable.yml"
+```
+
+### Allowed Request Origins
+
+Action Cable will only accept requests from specified origins, which are passed to the server config as an array. The origins can be instances of strings or regular expressions, against which a check for match will be performed.
+
+```ruby
+ActionCable.server.config.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
+```
+
+When running in the development environment, this defaults to "http://localhost:3000".
+
+To disable and allow requests from any origin:
+
+```ruby
+ActionCable.server.config.disable_request_forgery_protection = true
+```
+
+### Consumer Configuration
+
+Once you have decided how to run your cable server (see below), you must provide the server url (or path) to your client-side setup.
+There are two ways you can do this.
+
+The first is to simply pass it in when creating your consumer. For a standalone server,
+this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
+something like: `App.cable = ActionCable.createConsumer("/cable")`.
+
+The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
+This uses a url or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
+
+This method is especially useful if your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
+for your ActionCable server, but development might remain http and use the ws scheme. You might use localhost in development and your
+domain in production.
+
+In any case, to vary the websocket url between environments, add the following configuration to each environment:
+
+```ruby
+config.action_cable.url = "ws://example.com:28080"
+```
+
+Then add the following line to your layout before your JavaScript tag:
+
+```erb
+<%= action_cable_meta_tag %>
+```
+
+And finally, create your consumer like so:
+
+```coffeescript
+App.cable = ActionCable.createConsumer()
+```
+
+### Other Configurations
+
+The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
+
+```ruby
+ActionCable.server.config.log_tags = [
+ -> request { request.env['bc.account_id'] || "no-account" },
+ :action_cable,
+ -> request { request.uuid }
+]
+```
+
+For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
+
+Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 100, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute.
+
+
+## Running the cable server
+
+### Standalone
+The cable server(s) is separated from your normal application server. It's still a rack application, but it is its own rack
+application. The recommended basic setup is as follows:
+
+```ruby
+# cable/config.ru
+require ::File.expand_path('../../config/environment', __FILE__)
+Rails.application.eager_load!
+
+require 'action_cable/process/logging'
+
+run ActionCable.server
+```
+
+Then you start the server using a binstub in bin/cable ala:
+```
+#!/bin/bash
+bundle exec puma -p 28080 cable/config.ru
+```
+
+The above will start a cable server on port 28080.
+
+### In app
+
+If you are using a threaded server like Puma or Thin, the current implementation of ActionCable can run side-along with your Rails application. For example, to listen for WebSocket requests on `/cable`, mount the server at that path:
+
+```ruby
+# config/routes.rb
+Example::Application.routes.draw do
+ mount ActionCable.server => '/cable'
+end
+```
+
+For every instance of your server you create and for every worker your server spawns, you will also have a new instance of ActionCable, but the use of Redis keeps messages synced across connections.
+
+### Notes
+
+Beware that currently the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server.
+
+We'll get all this abstracted properly when the framework is integrated into Rails.
+
+The WebSocket server doesn't have access to the session, but it has access to the cookies. This can be used when you need to handle authentication. You can see one way of doing that with Devise in this [article](http://www.rubytutorial.io/actioncable-devise-authentication).
+
+## Dependencies
+
+Action Cable is currently tied to Redis through its use of the pubsub feature to route
+messages back and forth over the WebSocket cable connection. This dependency may well
+be alleviated in the future, but for the moment that's what it is. So be sure to have
+Redis installed and running.
+
+The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
+
+
+## Deployment
+
+Action Cable is powered by a combination of EventMachine and threads. The
+framework plumbing needed for connection handling is handled in the
+EventMachine loop, but the actual channel, user-specified, work is handled
+in a normal Ruby thread. This means you can use all your regular Rails models
+with no problem, as long as you haven't committed any thread-safety sins.
+
+But this also means that Action Cable needs to run in its own server process.
+So you'll have one set of server processes for your normal web work, and another
+set of server processes for the Action Cable. The former can be single-threaded,
+like Unicorn, but the latter must be multi-threaded, like Puma.
+
+## License
+
+Action Cable is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
+
+
+## Support
+
+API documentation is at:
+
+* http://api.rubyonrails.org
+
+Bug reports can be filed for the Ruby on Rails project here:
+
+* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
new file mode 100644
index 0000000000..b6c56e9195
--- /dev/null
+++ b/actioncable/Rakefile
@@ -0,0 +1,13 @@
+require 'rake/testtask'
+
+dir = File.dirname(__FILE__)
+
+task :default => :test
+
+Rake::TestTask.new do |t|
+ t.libs << "test"
+ t.test_files = Dir.glob("#{dir}/test/**/*_test.rb")
+ t.warning = true
+ t.verbose = true
+ t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
+end
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
new file mode 100644
index 0000000000..a04fc932aa
--- /dev/null
+++ b/actioncable/actioncable.gemspec
@@ -0,0 +1,31 @@
+version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
+
+Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = 'actioncable'
+ s.version = version
+ s.summary = 'WebSocket framework for Rails.'
+ s.description = 'Structure many real-time application concerns into channels over a single WebSocket connection.'
+
+ s.required_ruby_version = '>= 2.2.2'
+
+ s.license = 'MIT'
+
+ s.author = ['Pratik Naik', 'David Heinemeier Hansson']
+ s.email = ['pratiknaik@gmail.com', 'david@loudthinking.com']
+ s.homepage = 'http://rubyonrails.org'
+
+ s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*']
+ s.require_path = 'lib'
+
+ s.add_dependency 'actionpack', version
+
+ s.add_dependency 'coffee-rails', '~> 4.1.0'
+ s.add_dependency 'faye-websocket', '~> 0.10.0'
+ s.add_dependency 'websocket-driver', '~> 0.6.1'
+ s.add_dependency 'em-hiredis', '~> 0.3.0'
+ s.add_dependency 'redis', '~> 3.0'
+
+ s.add_development_dependency 'puma'
+ s.add_development_dependency 'mocha'
+end
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
new file mode 100644
index 0000000000..97f485b32e
--- /dev/null
+++ b/actioncable/lib/action_cable.rb
@@ -0,0 +1,50 @@
+#--
+# Copyright (c) 2015-2016 Basecamp, LLC
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+require 'active_support'
+require 'active_support/rails'
+require 'action_cable/version'
+
+module ActionCable
+ extend ActiveSupport::Autoload
+
+ INTERNAL = {
+ identifiers: {
+ ping: '_ping'.freeze
+ },
+ message_types: {
+ confirmation: 'confirm_subscription'.freeze,
+ rejection: 'reject_subscription'.freeze
+ }
+ }
+
+ # Singleton instance of the server
+ module_function def server
+ @server ||= ActionCable::Server::Base.new
+ end
+
+ autoload :Server
+ autoload :Connection
+ autoload :Channel
+ autoload :RemoteConnections
+end
diff --git a/actioncable/lib/action_cable/channel.rb b/actioncable/lib/action_cable/channel.rb
new file mode 100644
index 0000000000..7ae262ce5f
--- /dev/null
+++ b/actioncable/lib/action_cable/channel.rb
@@ -0,0 +1,14 @@
+module ActionCable
+ module Channel
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Base
+ autoload :Broadcasting
+ autoload :Callbacks
+ autoload :Naming
+ autoload :PeriodicTimers
+ autoload :Streams
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
new file mode 100644
index 0000000000..ce9d62635c
--- /dev/null
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -0,0 +1,277 @@
+require 'set'
+
+module ActionCable
+ module Channel
+ # The channel provides the basic structure of grouping behavior into logical units when communicating over the WebSocket connection.
+ # You can think of a channel like a form of controller, but one that's capable of pushing content to the subscriber in addition to simply
+ # responding to the subscriber's direct requests.
+ #
+ # Channel instances are long-lived. A channel object will be instantiated when the cable consumer becomes a subscriber, and then
+ # lives until the consumer disconnects. This may be seconds, minutes, hours, or even days. That means you have to take special care
+ # not to do anything silly in a channel that would balloon its memory footprint or whatever. The references are forever, so they won't be released
+ # as is normally the case with a controller instance that gets thrown away after every request.
+ #
+ # Long-lived channels (and connections) also mean you're responsible for ensuring that the data is fresh. If you hold a reference to a user
+ # record, but the name is changed while that reference is held, you may be sending stale data if you don't take precautions to avoid it.
+ #
+ # The upside of long-lived channel instances is that you can use instance variables to keep reference to objects that future subscriber requests
+ # can interact with. Here's a quick example:
+ #
+ # class ChatChannel < ApplicationCable::Channel
+ # def subscribed
+ # @room = Chat::Room[params[:room_number]]
+ # end
+ #
+ # def speak(data)
+ # @room.speak data, user: current_user
+ # end
+ # end
+ #
+ # The #speak action simply uses the Chat::Room object that was created when the channel was first subscribed to by the consumer when that
+ # subscriber wants to say something in the room.
+ #
+ # == Action processing
+ #
+ # Unlike Action Controllers, channels do not follow a REST constraint form for its actions. It's a remote-procedure call model. You can
+ # declare any public method on the channel (optionally taking a data argument), and this method is automatically exposed as callable to the client.
+ #
+ # Example:
+ #
+ # class AppearanceChannel < ApplicationCable::Channel
+ # def subscribed
+ # @connection_token = generate_connection_token
+ # end
+ #
+ # def unsubscribed
+ # current_user.disappear @connection_token
+ # end
+ #
+ # def appear(data)
+ # current_user.appear @connection_token, on: data['appearing_on']
+ # end
+ #
+ # def away
+ # current_user.away @connection_token
+ # end
+ #
+ # private
+ # def generate_connection_token
+ # SecureRandom.hex(36)
+ # end
+ # end
+ #
+ # In this example, subscribed/unsubscribed are not callable methods, as they were already declared in ActionCable::Channel::Base, but #appear/away
+ # are. #generate_connection_token is also not callable as its a private method. You'll see that appear accepts a data parameter, which it then
+ # uses as part of its model call. #away does not, it's simply a trigger action.
+ #
+ # Also note that in this example, current_user is available because it was marked as an identifying attribute on the connection.
+ # All such identifiers will automatically create a delegation method of the same name on the channel instance.
+ #
+ # == Rejecting subscription requests
+ #
+ # A channel can reject a subscription request in the #subscribed callback by invoking #reject!
+ #
+ # Example:
+ #
+ # class ChatChannel < ApplicationCable::Channel
+ # def subscribed
+ # @room = Chat::Room[params[:room_number]]
+ # reject unless current_user.can_access?(@room)
+ # end
+ # end
+ #
+ # In this example, the subscription will be rejected if the current_user does not have access to the chat room.
+ # On the client-side, Channel#rejected callback will get invoked when the server rejects the subscription request.
+ class Base
+ include Callbacks
+ include PeriodicTimers
+ include Streams
+ include Naming
+ include Broadcasting
+
+ attr_reader :params, :connection, :identifier
+ delegate :logger, to: :connection
+
+ class << self
+ # A list of method names that should be considered actions. This
+ # includes all public instance methods on a channel, less
+ # any internal methods (defined on Base), adding back in
+ # any methods that are internal, but still exist on the class
+ # itself.
+ #
+ # ==== Returns
+ # * <tt>Set</tt> - A set of all methods that should be considered actions.
+ def action_methods
+ @action_methods ||= begin
+ # All public instance methods of this class, including ancestors
+ methods = (public_instance_methods(true) -
+ # Except for public instance methods of Base and its ancestors
+ ActionCable::Channel::Base.public_instance_methods(true) +
+ # Be sure to include shadowed public instance methods of this class
+ public_instance_methods(false)).uniq.map(&:to_s)
+ methods.to_set
+ end
+ end
+
+ protected
+ # action_methods are cached and there is sometimes need to refresh
+ # them. ::clear_action_methods! allows you to do that, so next time
+ # you run action_methods, they will be recalculated
+ def clear_action_methods!
+ @action_methods = nil
+ end
+
+ # Refresh the cached action_methods when a new action_method is added.
+ def method_added(name)
+ super
+ clear_action_methods!
+ end
+ end
+
+ def initialize(connection, identifier, params = {})
+ @connection = connection
+ @identifier = identifier
+ @params = params
+
+ # When a channel is streaming via redis pubsub, we want to delay the confirmation
+ # transmission until redis pubsub subscription is confirmed.
+ @defer_subscription_confirmation = false
+
+ @reject_subscription = nil
+ @subscription_confirmation_sent = nil
+
+ delegate_connection_identifiers
+ subscribe_to_channel
+ end
+
+ # Extract the action name from the passed data and process it via the channel. The process will ensure
+ # that the action requested is a public method on the channel declared by the user (so not one of the callbacks
+ # like #subscribed).
+ def perform_action(data)
+ action = extract_action(data)
+
+ if processable_action?(action)
+ dispatch_action(action, data)
+ else
+ logger.error "Unable to process #{action_signature(action, data)}"
+ end
+ end
+
+ # Called by the cable connection when its cut so the channel has a chance to cleanup with callbacks.
+ # This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
+ def unsubscribe_from_channel
+ run_callbacks :unsubscribe do
+ unsubscribed
+ end
+ end
+
+
+ protected
+ # Called once a consumer has become a subscriber of the channel. Usually the place to setup any streams
+ # you want this channel to be sending to the subscriber.
+ def subscribed
+ # Override in subclasses
+ end
+
+ # Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking
+ # people as offline or the like.
+ def unsubscribed
+ # Override in subclasses
+ end
+
+ # Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
+ # the proper channel identifier marked as the recipient.
+ def transmit(data, via: nil)
+ logger.info "#{self.class.name} transmitting #{data.inspect}".tap { |m| m << " (via #{via})" if via }
+ connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data)
+ end
+
+ def defer_subscription_confirmation!
+ @defer_subscription_confirmation = true
+ end
+
+ def defer_subscription_confirmation?
+ @defer_subscription_confirmation
+ end
+
+ def subscription_confirmation_sent?
+ @subscription_confirmation_sent
+ end
+
+ def reject
+ @reject_subscription = true
+ end
+
+ def subscription_rejected?
+ @reject_subscription
+ end
+
+ private
+ def delegate_connection_identifiers
+ connection.identifiers.each do |identifier|
+ define_singleton_method(identifier) do
+ connection.send(identifier)
+ end
+ end
+ end
+
+
+ def subscribe_to_channel
+ run_callbacks :subscribe do
+ subscribed
+ end
+
+ if subscription_rejected?
+ reject_subscription
+ else
+ transmit_subscription_confirmation unless defer_subscription_confirmation?
+ end
+ end
+
+
+ def extract_action(data)
+ (data['action'].presence || :receive).to_sym
+ end
+
+ def processable_action?(action)
+ self.class.action_methods.include?(action.to_s)
+ end
+
+ def dispatch_action(action, data)
+ logger.info action_signature(action, data)
+
+ if method(action).arity == 1
+ public_send action, data
+ else
+ public_send action
+ end
+ end
+
+ def action_signature(action, data)
+ "#{self.class.name}##{action}".tap do |signature|
+ if (arguments = data.except('action')).any?
+ signature << "(#{arguments.inspect})"
+ end
+ end
+ end
+
+ def transmit_subscription_confirmation
+ unless subscription_confirmation_sent?
+ logger.info "#{self.class.name} is transmitting the subscription confirmation"
+ connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation])
+ @subscription_confirmation_sent = true
+ end
+ end
+
+ def reject_subscription
+ connection.subscriptions.remove_subscription self
+ transmit_subscription_rejection
+ end
+
+ def transmit_subscription_rejection
+ logger.info "#{self.class.name} is transmitting the subscription rejection"
+ connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection])
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/channel/broadcasting.rb b/actioncable/lib/action_cable/channel/broadcasting.rb
new file mode 100644
index 0000000000..afc23d7d1a
--- /dev/null
+++ b/actioncable/lib/action_cable/channel/broadcasting.rb
@@ -0,0 +1,29 @@
+require 'active_support/core_ext/object/to_param'
+
+module ActionCable
+ module Channel
+ module Broadcasting
+ extend ActiveSupport::Concern
+
+ delegate :broadcasting_for, to: :class
+
+ class_methods do
+ # Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel.
+ def broadcast_to(model, message)
+ ActionCable.server.broadcast(broadcasting_for([ channel_name, model ]), message)
+ end
+
+ def broadcasting_for(model) #:nodoc:
+ case
+ when model.is_a?(Array)
+ model.map { |m| broadcasting_for(m) }.join(':')
+ when model.respond_to?(:to_gid_param)
+ model.to_gid_param
+ else
+ model.to_param
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/channel/callbacks.rb b/actioncable/lib/action_cable/channel/callbacks.rb
new file mode 100644
index 0000000000..295d750e86
--- /dev/null
+++ b/actioncable/lib/action_cable/channel/callbacks.rb
@@ -0,0 +1,35 @@
+require 'active_support/callbacks'
+
+module ActionCable
+ module Channel
+ module Callbacks
+ extend ActiveSupport::Concern
+ include ActiveSupport::Callbacks
+
+ included do
+ define_callbacks :subscribe
+ define_callbacks :unsubscribe
+ end
+
+ class_methods do
+ def before_subscribe(*methods, &block)
+ set_callback(:subscribe, :before, *methods, &block)
+ end
+
+ def after_subscribe(*methods, &block)
+ set_callback(:subscribe, :after, *methods, &block)
+ end
+ alias_method :on_subscribe, :after_subscribe
+
+ def before_unsubscribe(*methods, &block)
+ set_callback(:unsubscribe, :before, *methods, &block)
+ end
+
+ def after_unsubscribe(*methods, &block)
+ set_callback(:unsubscribe, :after, *methods, &block)
+ end
+ alias_method :on_unsubscribe, :after_unsubscribe
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/channel/naming.rb b/actioncable/lib/action_cable/channel/naming.rb
new file mode 100644
index 0000000000..4c9d53b15a
--- /dev/null
+++ b/actioncable/lib/action_cable/channel/naming.rb
@@ -0,0 +1,22 @@
+module ActionCable
+ module Channel
+ module Naming
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Returns the name of the channel, underscored, without the <tt>Channel</tt> ending.
+ # If the channel is in a namespace, then the namespaces are represented by single
+ # colon separators in the channel name.
+ #
+ # ChatChannel.channel_name # => 'chat'
+ # Chats::AppearancesChannel.channel_name # => 'chats:appearances'
+ def channel_name
+ @channel_name ||= name.sub(/Channel$/, '').gsub('::',':').underscore
+ end
+ end
+
+ # Delegates to the class' <tt>channel_name</tt>
+ delegate :channel_name, to: :class
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb
new file mode 100644
index 0000000000..7f0fb37afc
--- /dev/null
+++ b/actioncable/lib/action_cable/channel/periodic_timers.rb
@@ -0,0 +1,41 @@
+module ActionCable
+ module Channel
+ module PeriodicTimers
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :periodic_timers, instance_reader: false
+ self.periodic_timers = []
+
+ after_subscribe :start_periodic_timers
+ after_unsubscribe :stop_periodic_timers
+ end
+
+ module ClassMethods
+ # Allow you to call a private method <tt>every</tt> so often seconds. This periodic timer can be useful
+ # for sending a steady flow of updates to a client based off an object that was configured on subscription.
+ # It's an alternative to using streams if the channel is able to do the work internally.
+ def periodically(callback, every:)
+ self.periodic_timers += [ [ callback, every: every ] ]
+ end
+ end
+
+ private
+ def active_periodic_timers
+ @active_periodic_timers ||= []
+ end
+
+ def start_periodic_timers
+ self.class.periodic_timers.each do |callback, options|
+ active_periodic_timers << EventMachine::PeriodicTimer.new(options[:every]) do
+ connection.worker_pool.async_run_periodic_timer(self, callback)
+ end
+ end
+ end
+
+ def stop_periodic_timers
+ active_periodic_timers.each { |timer| timer.cancel }
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
new file mode 100644
index 0000000000..b5ffa17f72
--- /dev/null
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -0,0 +1,114 @@
+module ActionCable
+ module Channel
+ # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pub/sub queue where any data
+ # put into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
+ # streaming a broadcasting at the very moment it sends out an update, you'll not get that update when connecting later.
+ #
+ # Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between
+ # the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new
+ # comments on a given page:
+ #
+ # class CommentsChannel < ApplicationCable::Channel
+ # def follow(data)
+ # stream_from "comments_for_#{data['recording_id']}"
+ # end
+ #
+ # def unfollow
+ # stop_all_streams
+ # end
+ # end
+ #
+ # So the subscribers of this channel will get whatever data is put into the, let's say, `comments_for_45` broadcasting as soon as it's put there.
+ # That looks like so from that side of things:
+ #
+ # ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell'
+ #
+ # If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
+ # The following example would subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`
+ #
+ # class CommentsChannel < ApplicationCable::Channel
+ # def subscribed
+ # post = Post.find(params[:id])
+ # stream_for post
+ # end
+ # end
+ #
+ # You can then broadcast to this channel using:
+ #
+ # CommentsChannel.broadcast_to(@post, @comment)
+ #
+ # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can supply a callback that lets you alter what goes out.
+ # Example below shows how you can use this to provide performance introspection in the process:
+ #
+ # class ChatChannel < ApplicationCable::Channel
+ # def subscribed
+ # @room = Chat::Room[params[:room_number]]
+ #
+ # stream_for @room, -> (encoded_message) do
+ # message = ActiveSupport::JSON.decode(encoded_message)
+ #
+ # if message['originated_at'].present?
+ # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
+ #
+ # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
+ # logger.info "Message took #{elapsed_time}s to arrive"
+ # end
+ #
+ # transmit message
+ # end
+ # end
+ #
+ # You can stop streaming from all broadcasts by calling #stop_all_streams.
+ module Streams
+ extend ActiveSupport::Concern
+
+ included do
+ on_unsubscribe :stop_all_streams
+ end
+
+ # Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
+ # instead of the default of just transmitting the updates straight to the subscriber.
+ def stream_from(broadcasting, callback = nil)
+ # Hold off the confirmation until pubsub#subscribe is successful
+ defer_subscription_confirmation!
+
+ callback ||= default_stream_callback(broadcasting)
+ streams << [ broadcasting, callback ]
+
+ EM.next_tick do
+ pubsub.subscribe(broadcasting, &callback).callback do |reply|
+ transmit_subscription_confirmation
+ logger.info "#{self.class.name} is streaming from #{broadcasting}"
+ end
+ end
+ end
+
+ # Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a
+ # <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
+ # to the subscriber.
+ def stream_for(model, callback = nil)
+ stream_from(broadcasting_for([ channel_name, model ]), callback)
+ end
+
+ def stop_all_streams
+ streams.each do |broadcasting, callback|
+ pubsub.unsubscribe_proc broadcasting, callback
+ logger.info "#{self.class.name} stopped streaming from #{broadcasting}"
+ end.clear
+ end
+
+ private
+ delegate :pubsub, to: :connection
+
+ def streams
+ @_streams ||= []
+ end
+
+ def default_stream_callback(broadcasting)
+ -> (message) do
+ transmit ActiveSupport::JSON.decode(message), via: "streamed from #{broadcasting}"
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb
new file mode 100644
index 0000000000..b672e00682
--- /dev/null
+++ b/actioncable/lib/action_cable/connection.rb
@@ -0,0 +1,16 @@
+module ActionCable
+ module Connection
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Authorization
+ autoload :Base
+ autoload :Identification
+ autoload :InternalChannel
+ autoload :MessageBuffer
+ autoload :WebSocket
+ autoload :Subscriptions
+ autoload :TaggedLoggerProxy
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/authorization.rb b/actioncable/lib/action_cable/connection/authorization.rb
new file mode 100644
index 0000000000..070a70e4e2
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/authorization.rb
@@ -0,0 +1,13 @@
+module ActionCable
+ module Connection
+ module Authorization
+ class UnauthorizedError < StandardError; end
+
+ private
+ def reject_unauthorized_connection
+ logger.error "An unauthorized connection attempt was rejected"
+ raise UnauthorizedError
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
new file mode 100644
index 0000000000..a8cfdf90f3
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -0,0 +1,214 @@
+require 'action_dispatch'
+
+module ActionCable
+ module Connection
+ # For every WebSocket the cable server is accepting, a Connection object will be instantiated. This instance becomes the parent
+ # of all the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
+ # based on an identifier sent by the cable consumer. The Connection itself does not deal with any specific application logic beyond
+ # authentication and authorization.
+ #
+ # Here's a basic example:
+ #
+ # module ApplicationCable
+ # class Connection < ActionCable::Connection::Base
+ # identified_by :current_user
+ #
+ # def connect
+ # self.current_user = find_verified_user
+ # logger.add_tags current_user.name
+ # end
+ #
+ # def disconnect
+ # # Any cleanup work needed when the cable connection is cut.
+ # end
+ #
+ # protected
+ # def find_verified_user
+ # if current_user = User.find_by_identity cookies.signed[:identity_id]
+ # current_user
+ # else
+ # reject_unauthorized_connection
+ # end
+ # end
+ # end
+ # end
+ #
+ # First, we declare that this connection can be identified by its current_user. This allows us later to be able to find all connections
+ # established for that current_user (and potentially disconnect them if the user was removed from an account). You can declare as many
+ # identification indexes as you like. Declaring an identification means that a attr_accessor is automatically set for that key.
+ #
+ # Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes
+ # it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection.
+ #
+ # Finally, we add a tag to the connection-specific logger with name of the current user to easily distinguish their messages in the log.
+ #
+ # Pretty simple, eh?
+ class Base
+ include Identification
+ include InternalChannel
+ include Authorization
+
+ attr_reader :server, :env, :subscriptions, :logger
+ delegate :worker_pool, :pubsub, to: :server
+
+ def initialize(server, env)
+ @server, @env = server, env
+
+ @logger = new_tagged_logger
+
+ @websocket = ActionCable::Connection::WebSocket.new(env)
+ @subscriptions = ActionCable::Connection::Subscriptions.new(self)
+ @message_buffer = ActionCable::Connection::MessageBuffer.new(self)
+
+ @_internal_redis_subscriptions = nil
+ @started_at = Time.now
+ end
+
+ # Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user.
+ # This method should not be called directly. Rely on the #connect (and #disconnect) callback instead.
+ def process
+ logger.info started_request_message
+
+ if websocket.possible? && allow_request_origin?
+ websocket.on(:open) { |event| send_async :on_open }
+ websocket.on(:message) { |event| on_message event.data }
+ websocket.on(:close) { |event| send_async :on_close }
+
+ respond_to_successful_request
+ else
+ respond_to_invalid_request
+ end
+ end
+
+ # Data received over the cable is handled by this method. It's expected that everything inbound is JSON encoded.
+ # The data is routed to the proper channel that the connection has subscribed to.
+ def receive(data_in_json)
+ if websocket.alive?
+ subscriptions.execute_command ActiveSupport::JSON.decode(data_in_json)
+ else
+ logger.error "Received data without a live WebSocket (#{data_in_json.inspect})"
+ end
+ end
+
+ # Send raw data straight back down the WebSocket. This is not intended to be called directly. Use the #transmit available on the
+ # Channel instead, as that'll automatically address the correct subscriber and wrap the message in JSON.
+ def transmit(data)
+ websocket.transmit data
+ end
+
+ # Close the WebSocket connection.
+ def close
+ websocket.close
+ end
+
+ # Invoke a method on the connection asynchronously through the pool of thread workers.
+ def send_async(method, *arguments)
+ worker_pool.async_invoke(self, method, *arguments)
+ end
+
+ # Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`.
+ # This can be returned by a health check against the connection.
+ def statistics
+ {
+ identifier: connection_identifier,
+ started_at: @started_at,
+ subscriptions: subscriptions.identifiers,
+ request_id: @env['action_dispatch.request_id']
+ }
+ end
+
+ def beat
+ transmit ActiveSupport::JSON.encode(identifier: ActionCable::INTERNAL[:identifiers][:ping], message: Time.now.to_i)
+ end
+
+ protected
+ # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
+ def request
+ @request ||= begin
+ environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
+ ActionDispatch::Request.new(environment || env)
+ end
+ end
+
+ # The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks.
+ def cookies
+ request.cookie_jar
+ end
+
+ attr_reader :websocket
+ attr_reader :message_buffer
+
+ private
+ def on_open
+ connect if respond_to?(:connect)
+ subscribe_to_internal_channel
+ beat
+
+ message_buffer.process!
+ server.add_connection(self)
+ rescue ActionCable::Connection::Authorization::UnauthorizedError
+ respond_to_invalid_request
+ end
+
+ def on_message(message)
+ message_buffer.append message
+ end
+
+ def on_close
+ logger.info finished_request_message
+
+ server.remove_connection(self)
+
+ subscriptions.unsubscribe_from_all
+ unsubscribe_from_internal_channel
+
+ disconnect if respond_to?(:disconnect)
+ end
+
+ def allow_request_origin?
+ return true if server.config.disable_request_forgery_protection
+
+ if Array(server.config.allowed_request_origins).any? { |allowed_origin| allowed_origin === env['HTTP_ORIGIN'] }
+ true
+ else
+ logger.error("Request origin not allowed: #{env['HTTP_ORIGIN']}")
+ false
+ end
+ end
+
+ def respond_to_successful_request
+ websocket.rack_response
+ end
+
+ def respond_to_invalid_request
+ close if websocket.alive?
+
+ logger.info finished_request_message
+ [ 404, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ]
+ end
+
+ # Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags.
+ def new_tagged_logger
+ TaggedLoggerProxy.new server.logger,
+ tags: server.config.log_tags.map { |tag| tag.respond_to?(:call) ? tag.call(request) : tag.to_s.camelize }
+ end
+
+ def started_request_message
+ 'Started %s "%s"%s for %s at %s' % [
+ request.request_method,
+ request.filtered_path,
+ websocket.possible? ? ' [WebSocket]' : '',
+ request.ip,
+ Time.now.to_s ]
+ end
+
+ def finished_request_message
+ 'Finished "%s"%s for %s at %s' % [
+ request.filtered_path,
+ websocket.possible? ? ' [WebSocket]' : '',
+ request.ip,
+ Time.now.to_s ]
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb
new file mode 100644
index 0000000000..885ff3f102
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/identification.rb
@@ -0,0 +1,46 @@
+require 'set'
+
+module ActionCable
+ module Connection
+ module Identification
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :identifiers
+ self.identifiers = Set.new
+ end
+
+ class_methods do
+ # Mark a key as being a connection identifier index that can then be used to find the specific connection again later.
+ # Common identifiers are current_user and current_account, but could be anything really.
+ #
+ # Note that anything marked as an identifier will automatically create a delegate by the same name on any
+ # channel instances created off the connection.
+ def identified_by(*identifiers)
+ Array(identifiers).each { |identifier| attr_accessor identifier }
+ self.identifiers += identifiers
+ end
+ end
+
+ # Return a single connection identifier that combines the value of all the registered identifiers into a single gid.
+ def connection_identifier
+ unless defined? @connection_identifier
+ @connection_identifier = connection_gid identifiers.map { |id| instance_variable_get("@#{id}") }.compact
+ end
+
+ @connection_identifier
+ end
+
+ private
+ def connection_gid(ids)
+ ids.map do |o|
+ if o.respond_to? :to_gid_param
+ o.to_gid_param
+ else
+ o.to_s
+ end
+ end.sort.join(":")
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb
new file mode 100644
index 0000000000..c065a24ab7
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/internal_channel.rb
@@ -0,0 +1,45 @@
+module ActionCable
+ module Connection
+ # Makes it possible for the RemoteConnection to disconnect a specific connection.
+ module InternalChannel
+ extend ActiveSupport::Concern
+
+ private
+ def internal_redis_channel
+ "action_cable/#{connection_identifier}"
+ end
+
+ def subscribe_to_internal_channel
+ if connection_identifier.present?
+ callback = -> (message) { process_internal_message(message) }
+ @_internal_redis_subscriptions ||= []
+ @_internal_redis_subscriptions << [ internal_redis_channel, callback ]
+
+ EM.next_tick { pubsub.subscribe(internal_redis_channel, &callback) }
+ logger.info "Registered connection (#{connection_identifier})"
+ end
+ end
+
+ def unsubscribe_from_internal_channel
+ if @_internal_redis_subscriptions.present?
+ @_internal_redis_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe_proc(channel, callback) } }
+ end
+ end
+
+ def process_internal_message(message)
+ message = ActiveSupport::JSON.decode(message)
+
+ case message['type']
+ when 'disconnect'
+ logger.info "Removing connection (#{connection_identifier})"
+ websocket.close
+ end
+ rescue Exception => e
+ logger.error "There was an exception - #{e.class}(#{e.message})"
+ logger.error e.backtrace.join("\n")
+
+ close
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb
new file mode 100644
index 0000000000..2f65a1e84a
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/message_buffer.rb
@@ -0,0 +1,54 @@
+module ActionCable
+ module Connection
+ # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized and is ready to receive them.
+ # Entirely internal operation and should not be used directly by the user.
+ class MessageBuffer
+ def initialize(connection)
+ @connection = connection
+ @buffered_messages = []
+ end
+
+ def append(message)
+ if valid? message
+ if processing?
+ receive message
+ else
+ buffer message
+ end
+ else
+ connection.logger.error "Couldn't handle non-string message: #{message.class}"
+ end
+ end
+
+ def processing?
+ @processing
+ end
+
+ def process!
+ @processing = true
+ receive_buffered_messages
+ end
+
+ protected
+ attr_reader :connection
+ attr_accessor :buffered_messages
+
+ private
+ def valid?(message)
+ message.is_a?(String)
+ end
+
+ def receive(message)
+ connection.send_async :receive, message
+ end
+
+ def buffer(message)
+ buffered_messages << message
+ end
+
+ def receive_buffered_messages
+ receive buffered_messages.shift until buffered_messages.empty?
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
new file mode 100644
index 0000000000..d7f95e6a62
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -0,0 +1,75 @@
+require 'active_support/core_ext/hash/indifferent_access'
+
+module ActionCable
+ module Connection
+ # Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on
+ # the connection to the proper channel. Should not be used directly by the user.
+ class Subscriptions
+ def initialize(connection)
+ @connection = connection
+ @subscriptions = {}
+ end
+
+ def execute_command(data)
+ case data['command']
+ when 'subscribe' then add data
+ when 'unsubscribe' then remove data
+ when 'message' then perform_action data
+ else
+ logger.error "Received unrecognized command in #{data.inspect}"
+ end
+ rescue Exception => e
+ logger.error "Could not execute command from #{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
+ end
+
+ def add(data)
+ id_key = data['identifier']
+ id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
+
+ subscription_klass = connection.server.channel_classes[id_options[:channel]]
+
+ if subscription_klass
+ subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options)
+ else
+ logger.error "Subscription class not found (#{data.inspect})"
+ end
+ end
+
+ def remove(data)
+ logger.info "Unsubscribing from channel: #{data['identifier']}"
+ remove_subscription subscriptions[data['identifier']]
+ end
+
+ def remove_subscription(subscription)
+ subscription.unsubscribe_from_channel
+ subscriptions.delete(subscription.identifier)
+ end
+
+ def perform_action(data)
+ find(data).perform_action ActiveSupport::JSON.decode(data['data'])
+ end
+
+ def identifiers
+ subscriptions.keys
+ end
+
+ def unsubscribe_from_all
+ subscriptions.each { |id, channel| channel.unsubscribe_from_channel }
+ end
+
+ protected
+ attr_reader :connection, :subscriptions
+
+ private
+ delegate :logger, to: :connection
+
+ def find(data)
+ if subscription = subscriptions[data['identifier']]
+ subscription
+ else
+ raise "Unable to find subscription with identifier: #{data['identifier']}"
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
new file mode 100644
index 0000000000..41afa9680a
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
@@ -0,0 +1,40 @@
+module ActionCable
+ module Connection
+ # Allows the use of per-connection tags against the server logger. This wouldn't work using the traditional
+ # <tt>ActiveSupport::TaggedLogging</tt> enhanced Rails.logger, as that logger will reset the tags between requests.
+ # The connection is long-lived, so it needs its own set of tags for its independent duration.
+ class TaggedLoggerProxy
+ attr_reader :tags
+
+ def initialize(logger, tags:)
+ @logger = logger
+ @tags = tags.flatten
+ end
+
+ def add_tags(*tags)
+ @tags += tags.flatten
+ @tags = @tags.uniq
+ end
+
+ def tag(logger)
+ if logger.respond_to?(:tagged)
+ current_tags = tags - logger.formatter.current_tags
+ logger.tagged(*current_tags) { yield }
+ else
+ yield
+ end
+ end
+
+ %i( debug info warn error fatal unknown ).each do |severity|
+ define_method(severity) do |message|
+ log severity, message
+ end
+ end
+
+ protected
+ def log(type, message)
+ tag(@logger) { @logger.send type, message }
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb
new file mode 100644
index 0000000000..670d5690ae
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/web_socket.rb
@@ -0,0 +1,29 @@
+require 'faye/websocket'
+
+module ActionCable
+ module Connection
+ # Decorate the Faye::WebSocket with helpers we need.
+ class WebSocket
+ delegate :rack_response, :close, :on, to: :websocket
+
+ def initialize(env)
+ @websocket = Faye::WebSocket.websocket?(env) ? Faye::WebSocket.new(env) : nil
+ end
+
+ def possible?
+ websocket
+ end
+
+ def alive?
+ websocket && websocket.ready_state == Faye::WebSocket::API::OPEN
+ end
+
+ def transmit(data)
+ websocket.send data
+ end
+
+ protected
+ attr_reader :websocket
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
new file mode 100644
index 0000000000..2d3caa5b0a
--- /dev/null
+++ b/actioncable/lib/action_cable/engine.rb
@@ -0,0 +1,38 @@
+require "rails"
+require "action_cable"
+require "action_cable/helpers/action_cable_helper"
+require "active_support/core_ext/hash/indifferent_access"
+
+module ActionCable
+ class Railtie < Rails::Engine # :nodoc:
+ config.action_cable = ActiveSupport::OrderedOptions.new
+ config.action_cable.url = '/cable'
+
+ config.eager_load_namespaces << ActionCable
+
+ initializer "action_cable.helpers" do
+ ActiveSupport.on_load(:action_view) do
+ include ActionCable::Helpers::ActionCableHelper
+ end
+ end
+
+ initializer "action_cable.logger" do
+ ActiveSupport.on_load(:action_cable) { self.logger ||= ::Rails.logger }
+ end
+
+ initializer "action_cable.set_configs" do |app|
+ options = app.config.action_cable
+ options.allowed_request_origins ||= "http://localhost:3000" if ::Rails.env.development?
+
+ app.paths.add "config/redis/cable", with: "config/redis/cable.yml"
+
+ ActiveSupport.on_load(:action_cable) do
+ if (redis_cable_path = Pathname.new(app.config.paths["config/redis/cable"].first)).exist?
+ self.redis = Rails.application.config_for(redis_cable_path).with_indifferent_access
+ end
+
+ options.each { |k,v| send("#{k}=", v) }
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb
new file mode 100644
index 0000000000..b1286aea6f
--- /dev/null
+++ b/actioncable/lib/action_cable/gem_version.rb
@@ -0,0 +1,15 @@
+module ActionCable
+ # Returns the version of the currently loaded Action Cable as a <tt>Gem::Version</tt>.
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 5
+ MINOR = 0
+ TINY = 0
+ PRE = "beta1"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/actioncable/lib/action_cable/helpers/action_cable_helper.rb b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
new file mode 100644
index 0000000000..b82751468a
--- /dev/null
+++ b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
@@ -0,0 +1,29 @@
+module ActionCable
+ module Helpers
+ module ActionCableHelper
+ # Returns an "action-cable-url" meta tag with the value of the url specified in your
+ # configuration. Ensure this is above your javascript tag:
+ #
+ # <head>
+ # <%= action_cable_meta_tag %>
+ # <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
+ # </head>
+ #
+ # This is then used by ActionCable to determine the url of your websocket server.
+ # Your CoffeeScript can then connect to the server without needing to specify the
+ # url directly:
+ #
+ # #= require cable
+ # @App = {}
+ # App.cable = Cable.createConsumer()
+ #
+ # Make sure to specify the correct server location in each of your environments
+ # config file:
+ #
+ # config.action_cable.url = "ws://example.com:28080"
+ def action_cable_meta_tag
+ tag "meta", name: "action-cable-url", content: Rails.application.config.action_cable.url
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/process/logging.rb b/actioncable/lib/action_cable/process/logging.rb
new file mode 100644
index 0000000000..dce637b3ca
--- /dev/null
+++ b/actioncable/lib/action_cable/process/logging.rb
@@ -0,0 +1,7 @@
+require 'action_cable/server'
+require 'eventmachine'
+
+EM.error_handler do |e|
+ puts "Error raised inside the event loop: #{e.message}"
+ puts e.backtrace.join("\n")
+end
diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb
new file mode 100644
index 0000000000..1230d905ad
--- /dev/null
+++ b/actioncable/lib/action_cable/remote_connections.rb
@@ -0,0 +1,64 @@
+module ActionCable
+ # If you need to disconnect a given connection, you go through the RemoteConnections. You find the connections you're looking for by
+ # searching the identifier declared on the connection. Example:
+ #
+ # module ApplicationCable
+ # class Connection < ActionCable::Connection::Base
+ # identified_by :current_user
+ # ....
+ # end
+ # end
+ #
+ # ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect
+ #
+ # That will disconnect all the connections established for User.find(1) across all servers running on all machines (because it uses
+ # the internal channel that all these servers are subscribed to).
+ class RemoteConnections
+ attr_reader :server
+
+ def initialize(server)
+ @server = server
+ end
+
+ def where(identifier)
+ RemoteConnection.new(server, identifier)
+ end
+
+ private
+ # Represents a single remote connection found via ActionCable.server.remote_connections.where(*).
+ # Exists for the solely for the purpose of calling #disconnect on that connection.
+ class RemoteConnection
+ class InvalidIdentifiersError < StandardError; end
+
+ include Connection::Identification, Connection::InternalChannel
+
+ def initialize(server, ids)
+ @server = server
+ set_identifier_instance_vars(ids)
+ end
+
+ # Uses the internal channel to disconnect the connection.
+ def disconnect
+ server.broadcast internal_redis_channel, type: 'disconnect'
+ end
+
+ # Returns all the identifiers that were applied to this connection.
+ def identifiers
+ server.connection_identifiers
+ end
+
+ private
+ attr_reader :server
+
+ def set_identifier_instance_vars(ids)
+ raise InvalidIdentifiersError unless valid_identifiers?(ids)
+ ids.each { |k,v| instance_variable_set("@#{k}", v) }
+ end
+
+ def valid_identifiers?(ids)
+ keys = ids.keys
+ identifiers.all? { |id| keys.include?(id) }
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/server.rb b/actioncable/lib/action_cable/server.rb
new file mode 100644
index 0000000000..a2a89d5f1e
--- /dev/null
+++ b/actioncable/lib/action_cable/server.rb
@@ -0,0 +1,19 @@
+require 'eventmachine'
+EventMachine.epoll if EventMachine.epoll?
+EventMachine.kqueue if EventMachine.kqueue?
+
+module ActionCable
+ module Server
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Base
+ autoload :Broadcasting
+ autoload :Connections
+ autoload :Configuration
+
+ autoload :Worker
+ autoload :ActiveRecordConnectionManagement, 'action_cable/server/worker/active_record_connection_management'
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
new file mode 100644
index 0000000000..3785bbd154
--- /dev/null
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -0,0 +1,74 @@
+require 'em-hiredis'
+
+module ActionCable
+ module Server
+ # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but
+ # also by the user to reach the RemoteConnections instead for finding and disconnecting connections across all servers.
+ #
+ # Also, this is the server instance used for broadcasting. See Broadcasting for details.
+ class Base
+ include ActionCable::Server::Broadcasting
+ include ActionCable::Server::Connections
+
+ cattr_accessor(:config, instance_accessor: true) { ActionCable::Server::Configuration.new }
+
+ def self.logger; config.logger; end
+ delegate :logger, to: :config
+
+ def initialize
+ end
+
+ # Called by rack to setup the server.
+ def call(env)
+ setup_heartbeat_timer
+ config.connection_class.new(self, env).process
+ end
+
+ # Disconnect all the connections identified by `identifiers` on this server or any others via RemoteConnections.
+ def disconnect(identifiers)
+ remote_connections.where(identifiers).disconnect
+ end
+
+ # Gateway to RemoteConnections. See that class for details.
+ def remote_connections
+ @remote_connections ||= RemoteConnections.new(self)
+ end
+
+ # The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size.
+ def worker_pool
+ @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size)
+ end
+
+ # Requires and returns a hash of all the channel class constants keyed by name.
+ def channel_classes
+ @channel_classes ||= begin
+ config.channel_paths.each { |channel_path| require channel_path }
+ config.channel_class_names.each_with_object({}) { |name, hash| hash[name] = name.constantize }
+ end
+ end
+
+ # The redis pubsub adapter used for all streams/broadcasting.
+ def pubsub
+ @pubsub ||= redis.pubsub
+ end
+
+ # The EventMachine Redis instance used by the pubsub adapter.
+ def redis
+ @redis ||= EM::Hiredis.connect(config.redis[:url]).tap do |redis|
+ redis.on(:reconnect_failed) do
+ logger.info "[ActionCable] Redis reconnect failed."
+ # logger.info "[ActionCable] Redis reconnected. Closing all the open connections."
+ # @connections.map &:close
+ end
+ end
+ end
+
+ # All the identifiers applied to the connection class associated with this server.
+ def connection_identifiers
+ config.connection_class.identifiers
+ end
+ end
+
+ ActiveSupport.run_load_hooks(:action_cable, Base.config)
+ end
+end
diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb
new file mode 100644
index 0000000000..c759239a0e
--- /dev/null
+++ b/actioncable/lib/action_cable/server/broadcasting.rb
@@ -0,0 +1,54 @@
+require 'redis'
+
+module ActionCable
+ module Server
+ # Broadcasting is how other parts of your application can send messages to the channel subscribers. As explained in Channel, most of the time, these
+ # broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example:
+ #
+ # class WebNotificationsChannel < ApplicationCable::Channel
+ # def subscribed
+ # stream_from "web_notifications_#{current_user.id}"
+ # end
+ # end
+ #
+ # # Somewhere in your app this is called, perhaps from a NewCommentJob
+ # ActionCable.server.broadcast \
+ # "web_notifications_1", { title: 'New things!', body: 'All shit fit for print' }
+ #
+ # # Client-side coffescript, which assumes you've already requested the right to send web notifications
+ # App.cable.subscriptions.create "WebNotificationsChannel",
+ # received: (data) ->
+ # new Notification data['title'], body: data['body']
+ module Broadcasting
+ # Broadcast a hash directly to a named <tt>broadcasting</tt>. It'll automatically be JSON encoded.
+ def broadcast(broadcasting, message)
+ broadcaster_for(broadcasting).broadcast(message)
+ end
+
+ # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have a object that
+ # may need multiple spots to transmit to a specific broadcasting over and over.
+ def broadcaster_for(broadcasting)
+ Broadcaster.new(self, broadcasting)
+ end
+
+ # The redis instance used for broadcasting. Not intended for direct user use.
+ def broadcasting_redis
+ @broadcasting_redis ||= Redis.new(config.redis)
+ end
+
+ private
+ class Broadcaster
+ attr_reader :server, :broadcasting
+
+ def initialize(server, broadcasting)
+ @server, @broadcasting = server, broadcasting
+ end
+
+ def broadcast(message)
+ server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}"
+ server.broadcasting_redis.publish broadcasting, ActiveSupport::JSON.encode(message)
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
new file mode 100644
index 0000000000..935133cbba
--- /dev/null
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -0,0 +1,35 @@
+module ActionCable
+ module Server
+ # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak the configuration points
+ # in a Rails config initializer.
+ class Configuration
+ attr_accessor :logger, :log_tags
+ attr_accessor :connection_class, :worker_pool_size
+ attr_accessor :redis, :channels_path
+ attr_accessor :disable_request_forgery_protection, :allowed_request_origins
+ attr_accessor :url
+
+ def initialize
+ @log_tags = []
+
+ @connection_class = ApplicationCable::Connection
+ @worker_pool_size = 100
+
+ @channels_path = Rails.root.join('app/channels')
+
+ @disable_request_forgery_protection = false
+ end
+
+ def channel_paths
+ @channels ||= Dir["#{channels_path}/**/*_channel.rb"]
+ end
+
+ def channel_class_names
+ @channel_class_names ||= channel_paths.collect do |channel_path|
+ Pathname.new(channel_path).basename.to_s.split('.').first.camelize
+ end
+ end
+ end
+ end
+end
+
diff --git a/actioncable/lib/action_cable/server/connections.rb b/actioncable/lib/action_cable/server/connections.rb
new file mode 100644
index 0000000000..47dcea8c20
--- /dev/null
+++ b/actioncable/lib/action_cable/server/connections.rb
@@ -0,0 +1,37 @@
+module ActionCable
+ module Server
+ # Collection class for all the connections that's been established on this specific server. Remember, usually you'll run many cable servers, so
+ # you can't use this collection as an full list of all the connections established against your application. Use RemoteConnections for that.
+ # As such, this is primarily for internal use.
+ module Connections
+ BEAT_INTERVAL = 3
+
+ def connections
+ @connections ||= []
+ end
+
+ def add_connection(connection)
+ connections << connection
+ end
+
+ def remove_connection(connection)
+ connections.delete connection
+ end
+
+ # WebSocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you
+ # then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically
+ # disconnect.
+ def setup_heartbeat_timer
+ EM.next_tick do
+ @heartbeat_timer ||= EventMachine.add_periodic_timer(BEAT_INTERVAL) do
+ EM.next_tick { connections.map(&:beat) }
+ end
+ end
+ end
+
+ def open_connections_statistics
+ connections.map(&:statistics)
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb
new file mode 100644
index 0000000000..3b6c6d44a1
--- /dev/null
+++ b/actioncable/lib/action_cable/server/worker.rb
@@ -0,0 +1,71 @@
+require 'active_support/callbacks'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
+require 'concurrent'
+
+module ActionCable
+ module Server
+ # Worker used by Server.send_async to do connection work in threads. Only for internal use.
+ class Worker
+ include ActiveSupport::Callbacks
+
+ thread_mattr_accessor :connection
+ define_callbacks :work
+ include ActiveRecordConnectionManagement
+
+ def initialize(max_size: 5)
+ @pool = Concurrent::ThreadPoolExecutor.new(
+ min_threads: 1,
+ max_threads: max_size,
+ max_queue: 0,
+ )
+ end
+
+ def async_invoke(receiver, method, *args)
+ @pool.post do
+ invoke(receiver, method, *args)
+ end
+ end
+
+ def invoke(receiver, method, *args)
+ begin
+ self.connection = receiver
+
+ run_callbacks :work do
+ receiver.send method, *args
+ end
+ rescue Exception => e
+ logger.error "There was an exception - #{e.class}(#{e.message})"
+ logger.error e.backtrace.join("\n")
+
+ receiver.handle_exception if receiver.respond_to?(:handle_exception)
+ ensure
+ self.connection = nil
+ end
+ end
+
+ def async_run_periodic_timer(channel, callback)
+ @pool.post do
+ run_periodic_timer(channel, callback)
+ end
+ end
+
+ def run_periodic_timer(channel, callback)
+ begin
+ self.connection = channel.connection
+
+ run_callbacks :work do
+ callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
+ end
+ ensure
+ self.connection = nil
+ end
+ end
+
+ private
+
+ def logger
+ ActionCable.server.logger
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
new file mode 100644
index 0000000000..ecece4e270
--- /dev/null
+++ b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
@@ -0,0 +1,22 @@
+module ActionCable
+ module Server
+ class Worker
+ # Clear active connections between units of work so the long-running channel or connection processes do not hoard connections.
+ module ActiveRecordConnectionManagement
+ extend ActiveSupport::Concern
+
+ included do
+ if defined?(ActiveRecord::Base)
+ set_callback :work, :around, :with_database_connections
+ end
+ end
+
+ def with_database_connections
+ connection.logger.tag(ActiveRecord::Base.logger) { yield }
+ ensure
+ ActiveRecord::Base.clear_active_connections!
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actioncable/lib/action_cable/version.rb b/actioncable/lib/action_cable/version.rb
new file mode 100644
index 0000000000..e17877202b
--- /dev/null
+++ b/actioncable/lib/action_cable/version.rb
@@ -0,0 +1,8 @@
+require_relative 'gem_version'
+
+module ActionCable
+ # Returns the version of the currently loaded Action Cable as a <tt>Gem::Version</tt>
+ def self.version
+ gem_version
+ end
+end
diff --git a/actioncable/lib/assets/javascripts/action_cable.coffee.erb b/actioncable/lib/assets/javascripts/action_cable.coffee.erb
new file mode 100644
index 0000000000..7daea4ebcd
--- /dev/null
+++ b/actioncable/lib/assets/javascripts/action_cable.coffee.erb
@@ -0,0 +1,23 @@
+#= require_self
+#= require action_cable/consumer
+
+@ActionCable =
+ INTERNAL: <%= ActionCable::INTERNAL.to_json %>
+
+ createConsumer: (url = @getConfig("url")) ->
+ new ActionCable.Consumer @createWebSocketURL(url)
+
+ getConfig: (name) ->
+ element = document.head.querySelector("meta[name='action-cable-#{name}']")
+ element?.getAttribute("content")
+
+ createWebSocketURL: (url) ->
+ if url and not /^wss?:/i.test(url)
+ a = document.createElement("a")
+ a.href = url
+ # Fix populating Location properties in IE. Otherwise, protocol will be blank.
+ a.href = a.href
+ a.protocol = a.protocol.replace("http", "ws")
+ a.href
+ else
+ url
diff --git a/actioncable/lib/assets/javascripts/action_cable/connection.coffee b/actioncable/lib/assets/javascripts/action_cable/connection.coffee
new file mode 100644
index 0000000000..fbd7dbd35b
--- /dev/null
+++ b/actioncable/lib/assets/javascripts/action_cable/connection.coffee
@@ -0,0 +1,81 @@
+# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
+
+{message_types} = ActionCable.INTERNAL
+
+class ActionCable.Connection
+ @reopenDelay: 500
+
+ constructor: (@consumer) ->
+ @open()
+
+ send: (data) ->
+ if @isOpen()
+ @webSocket.send(JSON.stringify(data))
+ true
+ else
+ false
+
+ open: =>
+ if @webSocket and not @isState("closed")
+ throw new Error("Existing connection must be closed before opening")
+ else
+ @webSocket = new WebSocket(@consumer.url)
+ @installEventHandlers()
+ true
+
+ close: ->
+ @webSocket?.close()
+
+ reopen: ->
+ if @isState("closed")
+ @open()
+ else
+ try
+ @close()
+ finally
+ setTimeout(@open, @constructor.reopenDelay)
+
+ isOpen: ->
+ @isState("open")
+
+ # Private
+
+ isState: (states...) ->
+ @getState() in states
+
+ getState: ->
+ return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState
+ null
+
+ installEventHandlers: ->
+ for eventName of @events
+ handler = @events[eventName].bind(this)
+ @webSocket["on#{eventName}"] = handler
+ return
+
+ events:
+ message: (event) ->
+ {identifier, message, type} = JSON.parse(event.data)
+
+ switch type
+ when message_types.confirmation
+ @consumer.subscriptions.notify(identifier, "connected")
+ when message_types.rejection
+ @consumer.subscriptions.reject(identifier)
+ else
+ @consumer.subscriptions.notify(identifier, "received", message)
+
+ open: ->
+ @disconnected = false
+ @consumer.subscriptions.reload()
+
+ close: ->
+ @disconnect()
+
+ error: ->
+ @disconnect()
+
+ disconnect: ->
+ return if @disconnected
+ @disconnected = true
+ @consumer.subscriptions.notifyAll("disconnected")
diff --git a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee
new file mode 100644
index 0000000000..99b9a1c6d5
--- /dev/null
+++ b/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee
@@ -0,0 +1,79 @@
+# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting
+# revival reconnections if things go astray. Internal class, not intended for direct user manipulation.
+class ActionCable.ConnectionMonitor
+ @pollInterval:
+ min: 3
+ max: 30
+
+ @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
+
+ identifier: ActionCable.INTERNAL.identifiers.ping
+
+ constructor: (@consumer) ->
+ @consumer.subscriptions.add(this)
+ @start()
+
+ connected: ->
+ @reset()
+ @pingedAt = now()
+ delete @disconnectedAt
+
+ disconnected: ->
+ @disconnectedAt = now()
+
+ received: ->
+ @pingedAt = now()
+
+ reset: ->
+ @reconnectAttempts = 0
+
+ start: ->
+ @reset()
+ delete @stoppedAt
+ @startedAt = now()
+ @poll()
+ document.addEventListener("visibilitychange", @visibilityDidChange)
+
+ stop: ->
+ @stoppedAt = now()
+ document.removeEventListener("visibilitychange", @visibilityDidChange)
+
+ poll: ->
+ setTimeout =>
+ unless @stoppedAt
+ @reconnectIfStale()
+ @poll()
+ , @getInterval()
+
+ getInterval: ->
+ {min, max} = @constructor.pollInterval
+ interval = 5 * Math.log(@reconnectAttempts + 1)
+ clamp(interval, min, max) * 1000
+
+ reconnectIfStale: ->
+ if @connectionIsStale()
+ @reconnectAttempts++
+ unless @disconnectedRecently()
+ @consumer.connection.reopen()
+
+ connectionIsStale: ->
+ secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold
+
+ disconnectedRecently: ->
+ @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold
+
+ visibilityDidChange: =>
+ if document.visibilityState is "visible"
+ setTimeout =>
+ if @connectionIsStale() or not @consumer.connection.isOpen()
+ @consumer.connection.reopen()
+ , 200
+
+ now = ->
+ new Date().getTime()
+
+ secondsSince = (time) ->
+ (now() - time) / 1000
+
+ clamp = (number, min, max) ->
+ Math.max(min, Math.min(max, number))
diff --git a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee b/actioncable/lib/assets/javascripts/action_cable/consumer.coffee
new file mode 100644
index 0000000000..fcd8d0fb6c
--- /dev/null
+++ b/actioncable/lib/assets/javascripts/action_cable/consumer.coffee
@@ -0,0 +1,25 @@
+#= require action_cable/connection
+#= require action_cable/connection_monitor
+#= require action_cable/subscriptions
+#= require action_cable/subscription
+
+# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,
+# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.
+# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription
+# method.
+#
+# The following example shows how this can be setup:
+#
+# @App = {}
+# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1"
+# App.appearance = App.cable.subscriptions.create "AppearanceChannel"
+#
+# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
+class ActionCable.Consumer
+ constructor: (@url) ->
+ @subscriptions = new ActionCable.Subscriptions this
+ @connection = new ActionCable.Connection this
+ @connectionMonitor = new ActionCable.ConnectionMonitor this
+
+ send: (data) ->
+ @connection.send(data)
diff --git a/actioncable/lib/assets/javascripts/action_cable/subscription.coffee b/actioncable/lib/assets/javascripts/action_cable/subscription.coffee
new file mode 100644
index 0000000000..339d676933
--- /dev/null
+++ b/actioncable/lib/assets/javascripts/action_cable/subscription.coffee
@@ -0,0 +1,68 @@
+# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.
+# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding
+# Channel instance on the server side.
+#
+# An example demonstrates the basic functionality:
+#
+# App.appearance = App.cable.subscriptions.create "AppearanceChannel",
+# connected: ->
+# # Called once the subscription has been successfully completed
+#
+# appear: ->
+# @perform 'appear', appearing_on: @appearingOn()
+#
+# away: ->
+# @perform 'away'
+#
+# appearingOn: ->
+# $('main').data 'appearing-on'
+#
+# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server
+# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away).
+# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter.
+#
+# This is how the server component would look:
+#
+# class AppearanceChannel < ApplicationActionCable::Channel
+# def subscribed
+# current_user.appear
+# end
+#
+# def unsubscribed
+# current_user.disappear
+# end
+#
+# def appear(data)
+# current_user.appear on: data['appearing_on']
+# end
+#
+# def away
+# current_user.away
+# end
+# end
+#
+# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.
+# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method.
+class ActionCable.Subscription
+ constructor: (@subscriptions, params = {}, mixin) ->
+ @identifier = JSON.stringify(params)
+ extend(this, mixin)
+ @subscriptions.add(this)
+ @consumer = @subscriptions.consumer
+
+ # Perform a channel action with the optional data passed as an attribute
+ perform: (action, data = {}) ->
+ data.action = action
+ @send(data)
+
+ send: (data) ->
+ @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data))
+
+ unsubscribe: ->
+ @subscriptions.remove(this)
+
+ extend = (object, properties) ->
+ if properties?
+ for key, value of properties
+ object[key] = value
+ object
diff --git a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee
new file mode 100644
index 0000000000..ae041ffa2b
--- /dev/null
+++ b/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee
@@ -0,0 +1,64 @@
+# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user
+# us ActionCable.Subscriptions#create, and it should be called through the consumer like so:
+#
+# @App = {}
+# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1"
+# App.appearance = App.cable.subscriptions.create "AppearanceChannel"
+#
+# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
+class ActionCable.Subscriptions
+ constructor: (@consumer) ->
+ @subscriptions = []
+
+ create: (channelName, mixin) ->
+ channel = channelName
+ params = if typeof channel is "object" then channel else {channel}
+ new ActionCable.Subscription this, params, mixin
+
+ # Private
+
+ add: (subscription) ->
+ @subscriptions.push(subscription)
+ @notify(subscription, "initialized")
+ @sendCommand(subscription, "subscribe")
+
+ remove: (subscription) ->
+ @forget(subscription)
+
+ unless @findAll(subscription.identifier).length
+ @sendCommand(subscription, "unsubscribe")
+
+ reject: (identifier) ->
+ for subscription in @findAll(identifier)
+ @forget(subscription)
+ @notify(subscription, "rejected")
+
+ forget: (subscription) ->
+ @subscriptions = (s for s in @subscriptions when s isnt subscription)
+
+ findAll: (identifier) ->
+ s for s in @subscriptions when s.identifier is identifier
+
+ reload: ->
+ for subscription in @subscriptions
+ @sendCommand(subscription, "subscribe")
+
+ notifyAll: (callbackName, args...) ->
+ for subscription in @subscriptions
+ @notify(subscription, callbackName, args...)
+
+ notify: (subscription, callbackName, args...) ->
+ if typeof subscription is "string"
+ subscriptions = @findAll(subscription)
+ else
+ subscriptions = [subscription]
+
+ for subscription in subscriptions
+ subscription[callbackName]?(args...)
+
+ sendCommand: (subscription, command) ->
+ {identifier} = subscription
+ if identifier is ActionCable.INTERNAL.identifiers.ping
+ @consumer.connection.isOpen()
+ else
+ @consumer.send({command, identifier})
diff --git a/actioncable/lib/rails/generators/channel/USAGE b/actioncable/lib/rails/generators/channel/USAGE
new file mode 100644
index 0000000000..27a934c689
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/USAGE
@@ -0,0 +1,14 @@
+Description:
+============
+ Stubs out a new cable channel for the server (in Ruby) and client (in CoffeeScript).
+ Pass the channel name, either CamelCased or under_scored, and an optional list of channel actions as arguments.
+
+ Note: Turn on the cable connection in app/assets/javascript/cable.coffee after generating any channels.
+
+Example:
+========
+ rails generate channel Chat speak
+
+ creates a Chat channel class and CoffeeScript asset:
+ Channel: app/channels/chat_channel.rb
+ Assets: app/assets/javascript/channels/chat.coffee
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
new file mode 100644
index 0000000000..c5d398810a
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -0,0 +1,26 @@
+module Rails
+ module Generators
+ class ChannelGenerator < NamedBase
+ source_root File.expand_path("../templates", __FILE__)
+
+ argument :actions, type: :array, default: [], banner: "method method"
+
+ class_option :assets, type: :boolean
+
+ check_class_collision suffix: "Channel"
+
+ def create_channel_file
+ template "channel.rb", File.join('app/channels', class_path, "#{file_name}_channel.rb")
+
+ if options[:assets]
+ template "assets/channel.coffee", File.join('app/assets/javascripts/channels', class_path, "#{file_name}.coffee")
+ end
+ end
+
+ protected
+ def file_name
+ @_file_name ||= super.gsub(/\_channel/i, '')
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee
new file mode 100644
index 0000000000..5467811aba
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee
@@ -0,0 +1,14 @@
+App.<%= class_name.underscore %> = App.cable.subscriptions.create "<%= class_name %>Channel",
+ connected: ->
+ # Called when the subscription is ready for use on the server
+
+ disconnected: ->
+ # Called when the subscription has been terminated by the server
+
+ received: (data) ->
+ # Called when there's incoming data on the websocket for this channel
+<% actions.each do |action| -%>
+
+ <%= action %>: ->
+ @perform '<%= action %>'
+<% end -%>
diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb
new file mode 100644
index 0000000000..6cf04ee61f
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/templates/channel.rb
@@ -0,0 +1,17 @@
+# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
+<% module_namespacing do -%>
+class <%= class_name %>Channel < ApplicationCable::Channel
+ def subscribed
+ # stream_from "some_channel"
+ end
+
+ def unsubscribed
+ # Any cleanup needed when channel is unsubscribed
+ end
+<% actions.each do |action| -%>
+
+ def <%= action %>
+ end
+<% end -%>
+end
+<% end -%>
diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb
new file mode 100644
index 0000000000..d41bf3064b
--- /dev/null
+++ b/actioncable/test/channel/base_test.rb
@@ -0,0 +1,184 @@
+require 'test_helper'
+require 'stubs/test_connection'
+require 'stubs/room'
+
+class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
+ class ActionCable::Channel::Base
+ def kick
+ @last_action = [ :kick ]
+ end
+
+ def topic
+ end
+ end
+
+ class BasicChannel < ActionCable::Channel::Base
+ def chatters
+ @last_action = [ :chatters ]
+ end
+ end
+
+ class ChatChannel < BasicChannel
+ attr_reader :room, :last_action
+ after_subscribe :toggle_subscribed
+ after_unsubscribe :toggle_subscribed
+
+ def initialize(*)
+ @subscribed = false
+ super
+ end
+
+ def subscribed
+ @room = Room.new params[:id]
+ @actions = []
+ end
+
+ def unsubscribed
+ @room = nil
+ end
+
+ def toggle_subscribed
+ @subscribed = !@subscribed
+ end
+
+ def leave
+ @last_action = [ :leave ]
+ end
+
+ def speak(data)
+ @last_action = [ :speak, data ]
+ end
+
+ def topic(data)
+ @last_action = [ :topic, data ]
+ end
+
+ def subscribed?
+ @subscribed
+ end
+
+ def get_latest
+ transmit data: 'latest'
+ end
+
+ def receive
+ @last_action = [ :receive ]
+ end
+
+ private
+ def rm_rf
+ @last_action = [ :rm_rf ]
+ end
+ end
+
+ setup do
+ @user = User.new "lifo"
+ @connection = TestConnection.new(@user)
+ @channel = ChatChannel.new @connection, "{id: 1}", { id: 1 }
+ end
+
+ test "should subscribe to a channel on initialize" do
+ assert_equal 1, @channel.room.id
+ end
+
+ test "on subscribe callbacks" do
+ assert @channel.subscribed
+ end
+
+ test "channel params" do
+ assert_equal({ id: 1 }, @channel.params)
+ end
+
+ test "unsubscribing from a channel" do
+ assert @channel.room
+ assert @channel.subscribed?
+
+ @channel.unsubscribe_from_channel
+
+ assert ! @channel.room
+ assert ! @channel.subscribed?
+ end
+
+ test "connection identifiers" do
+ assert_equal @user.name, @channel.current_user.name
+ end
+
+ test "callable action without any argument" do
+ @channel.perform_action 'action' => :leave
+ assert_equal [ :leave ], @channel.last_action
+ end
+
+ test "callable action with arguments" do
+ data = { 'action' => :speak, 'content' => "Hello World" }
+
+ @channel.perform_action data
+ assert_equal [ :speak, data ], @channel.last_action
+ end
+
+ test "should not dispatch a private method" do
+ @channel.perform_action 'action' => :rm_rf
+ assert_nil @channel.last_action
+ end
+
+ test "should not dispatch a public method defined on Base" do
+ @channel.perform_action 'action' => :kick
+ assert_nil @channel.last_action
+ end
+
+ test "should dispatch a public method defined on Base and redefined on channel" do
+ data = { 'action' => :topic, 'content' => "This is Sparta!" }
+
+ @channel.perform_action data
+ assert_equal [ :topic, data ], @channel.last_action
+ end
+
+ test "should dispatch calling a public method defined in an ancestor" do
+ @channel.perform_action 'action' => :chatters
+ assert_equal [ :chatters ], @channel.last_action
+ end
+
+ test "should dispatch receive action when perform_action is called with empty action" do
+ data = { 'content' => 'hello' }
+ @channel.perform_action data
+ assert_equal [ :receive ], @channel.last_action
+ end
+
+ test "transmitting data" do
+ @channel.perform_action 'action' => :get_latest
+
+ expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "message" => { "data" => "latest" }
+ assert_equal expected, @connection.last_transmission
+ end
+
+ test "subscription confirmation" do
+ expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
+ assert_equal expected, @connection.last_transmission
+ end
+
+ test "actions available on Channel" do
+ available_actions = %w(room last_action subscribed unsubscribed toggle_subscribed leave speak subscribed? get_latest receive chatters topic).to_set
+ assert_equal available_actions, ChatChannel.action_methods
+ end
+
+ test "invalid action on Channel" do
+ assert_logged("Unable to process ActionCable::Channel::BaseTest::ChatChannel#invalid_action") do
+ @channel.perform_action 'action' => :invalid_action
+ end
+ end
+
+ private
+ def assert_logged(message)
+ old_logger = @connection.logger
+ log = StringIO.new
+ @connection.instance_variable_set(:@logger, Logger.new(log))
+
+ begin
+ yield
+
+ log.rewind
+ assert_match message, log.read
+ ensure
+ @connection.instance_variable_set(:@logger, old_logger)
+ end
+ end
+end
diff --git a/actioncable/test/channel/broadcasting_test.rb b/actioncable/test/channel/broadcasting_test.rb
new file mode 100644
index 0000000000..1de04243e5
--- /dev/null
+++ b/actioncable/test/channel/broadcasting_test.rb
@@ -0,0 +1,29 @@
+require 'test_helper'
+require 'stubs/test_connection'
+require 'stubs/room'
+
+class ActionCable::Channel::BroadcastingTest < ActiveSupport::TestCase
+ class ChatChannel < ActionCable::Channel::Base
+ end
+
+ setup do
+ @connection = TestConnection.new
+ end
+
+ test "broadcasts_to" do
+ ActionCable.stubs(:server).returns mock().tap { |m| m.expects(:broadcast).with('action_cable:channel:broadcasting_test:chat:Room#1-Campfire', "Hello World") }
+ ChatChannel.broadcast_to(Room.new(1), "Hello World")
+ end
+
+ test "broadcasting_for with an object" do
+ assert_equal "Room#1-Campfire", ChatChannel.broadcasting_for(Room.new(1))
+ end
+
+ test "broadcasting_for with an array" do
+ assert_equal "Room#1-Campfire:Room#2-Campfire", ChatChannel.broadcasting_for([ Room.new(1), Room.new(2) ])
+ end
+
+ test "broadcasting_for with a string" do
+ assert_equal "hello", ChatChannel.broadcasting_for("hello")
+ end
+end
diff --git a/actioncable/test/channel/naming_test.rb b/actioncable/test/channel/naming_test.rb
new file mode 100644
index 0000000000..89ef6ad8b0
--- /dev/null
+++ b/actioncable/test/channel/naming_test.rb
@@ -0,0 +1,10 @@
+require 'test_helper'
+
+class ActionCable::Channel::NamingTest < ActiveSupport::TestCase
+ class ChatChannel < ActionCable::Channel::Base
+ end
+
+ test "channel_name" do
+ assert_equal "action_cable:channel:naming_test:chat", ChatChannel.channel_name
+ end
+end
diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb
new file mode 100644
index 0000000000..1590a12f09
--- /dev/null
+++ b/actioncable/test/channel/periodic_timers_test.rb
@@ -0,0 +1,40 @@
+require 'test_helper'
+require 'stubs/test_connection'
+require 'stubs/room'
+
+class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase
+ class ChatChannel < ActionCable::Channel::Base
+ periodically -> { ping }, every: 5
+ periodically :send_updates, every: 1
+
+ private
+ def ping
+ end
+ end
+
+ setup do
+ @connection = TestConnection.new
+ end
+
+ test "periodic timers definition" do
+ timers = ChatChannel.periodic_timers
+
+ assert_equal 2, timers.size
+
+ first_timer = timers[0]
+ assert_kind_of Proc, first_timer[0]
+ assert_equal 5, first_timer[1][:every]
+
+ second_timer = timers[1]
+ assert_equal :send_updates, second_timer[0]
+ assert_equal 1, second_timer[1][:every]
+ end
+
+ test "timer start and stop" do
+ EventMachine::PeriodicTimer.expects(:new).times(2).returns(true)
+ channel = ChatChannel.new @connection, "{id: 1}", { id: 1 }
+
+ channel.expects(:stop_periodic_timers).once
+ channel.unsubscribe_from_channel
+ end
+end
diff --git a/actioncable/test/channel/rejection_test.rb b/actioncable/test/channel/rejection_test.rb
new file mode 100644
index 0000000000..aa93396d44
--- /dev/null
+++ b/actioncable/test/channel/rejection_test.rb
@@ -0,0 +1,25 @@
+require 'test_helper'
+require 'stubs/test_connection'
+require 'stubs/room'
+
+class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase
+ class SecretChannel < ActionCable::Channel::Base
+ def subscribed
+ reject if params[:id] > 0
+ end
+ end
+
+ setup do
+ @user = User.new "lifo"
+ @connection = TestConnection.new(@user)
+ end
+
+ test "subscription rejection" do
+ @connection.expects(:subscriptions).returns mock().tap { |m| m.expects(:remove_subscription).with instance_of(SecretChannel) }
+ @channel = SecretChannel.new @connection, "{id: 1}", { id: 1 }
+
+ expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "reject_subscription"
+ assert_equal expected, @connection.last_transmission
+ end
+
+end
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
new file mode 100644
index 0000000000..1424ded04c
--- /dev/null
+++ b/actioncable/test/channel/stream_test.rb
@@ -0,0 +1,80 @@
+require 'test_helper'
+require 'stubs/test_connection'
+require 'stubs/room'
+
+class ActionCable::Channel::StreamTest < ActionCable::TestCase
+ class ChatChannel < ActionCable::Channel::Base
+ def subscribed
+ if params[:id]
+ @room = Room.new params[:id]
+ stream_from "test_room_#{@room.id}"
+ end
+ end
+
+ def send_confirmation
+ transmit_subscription_confirmation
+ end
+
+ end
+
+ test "streaming start and stop" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1").returns stub_everything(:pubsub) }
+ channel = ChatChannel.new connection, "{id: 1}", { id: 1 }
+
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe_proc) }
+ channel.unsubscribe_from_channel
+ end
+ end
+
+ test "stream_for" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ EM.next_tick do
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire").returns stub_everything(:pubsub) }
+ end
+
+ channel = ChatChannel.new connection, ""
+ channel.stream_for Room.new(1)
+ end
+ end
+
+ test "stream_from subscription confirmation" do
+ EM.run do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns EM::Hiredis.connect.pubsub
+
+ ChatChannel.new connection, "{id: 1}", { id: 1 }
+ assert_nil connection.last_transmission
+
+ EM::Timer.new(0.1) do
+ expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
+ assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
+
+ EM.run_deferred_callbacks
+ EM.stop
+ end
+ end
+ end
+
+ test "subscription confirmation should only be sent out once" do
+ EM.run do
+ connection = TestConnection.new
+ connection.stubs(:pubsub).returns EM::Hiredis.connect.pubsub
+
+ channel = ChatChannel.new connection, "test_channel"
+ channel.send_confirmation
+ channel.send_confirmation
+
+ EM.run_deferred_callbacks
+
+ expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription"
+ assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation"
+
+ assert_equal 1, connection.transmissions.size
+ EM.stop
+ end
+ end
+
+end
diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb
new file mode 100644
index 0000000000..87d0e79ef3
--- /dev/null
+++ b/actioncable/test/connection/authorization_test.rb
@@ -0,0 +1,31 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket
+
+ def connect
+ reject_unauthorized_connection
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ test "unauthorized connection" do
+ run_in_eventmachine do
+ server = TestServer.new
+ server.config.allowed_request_origins = %w( http://rubyonrails.com )
+
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
+ 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+
+ connection = Connection.new(server, env)
+ connection.websocket.expects(:close)
+
+ connection.process
+ end
+ end
+end
diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb
new file mode 100644
index 0000000000..182562db82
--- /dev/null
+++ b/actioncable/test/connection/base_test.rb
@@ -0,0 +1,117 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::Connection::BaseTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket, :subscriptions, :message_buffer, :connected
+
+ def connect
+ @connected = true
+ end
+
+ def disconnect
+ @connected = false
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test "making a connection with invalid headers" do
+ run_in_eventmachine do
+ connection = ActionCable::Connection::Base.new(@server, Rack::MockRequest.env_for("/test"))
+ response = connection.process
+ assert_equal 404, response[0]
+ end
+ end
+
+ test "websocket connection" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ assert connection.websocket.possible?
+ assert connection.websocket.alive?
+ end
+ end
+
+ test "rack response" do
+ run_in_eventmachine do
+ connection = open_connection
+ response = connection.process
+
+ assert_equal [ -1, {}, [] ], response
+ end
+ end
+
+ test "on connection open" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/))
+ connection.message_buffer.expects(:process!)
+
+ # Allow EM to run on_open callback
+ EM.next_tick do
+ assert_equal [ connection ], @server.connections
+ assert connection.connected
+ end
+ end
+ end
+
+ test "on connection close" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ # Setup the connection
+ EventMachine.stubs(:add_periodic_timer).returns(true)
+ connection.send :on_open
+ assert connection.connected
+
+ connection.subscriptions.expects(:unsubscribe_from_all)
+ connection.send :on_close
+
+ assert ! connection.connected
+ assert_equal [], @server.connections
+ end
+ end
+
+ test "connection statistics" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ statistics = connection.statistics
+
+ assert statistics[:identifier].blank?
+ assert_kind_of Time, statistics[:started_at]
+ assert_equal [], statistics[:subscriptions]
+ end
+ end
+
+ test "explicitly closing a connection" do
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ connection.websocket.expects(:close)
+ connection.close
+ end
+ end
+
+ private
+ def open_connection
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
+ 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+
+ Connection.new(@server, env)
+ end
+end
diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb
new file mode 100644
index 0000000000..a29f65fb97
--- /dev/null
+++ b/actioncable/test/connection/cross_site_forgery_test.rb
@@ -0,0 +1,81 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
+ HOST = 'rubyonrails.com'
+
+ class Connection < ActionCable::Connection::Base
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ teardown do
+ @server.config.disable_request_forgery_protection = false
+ @server.config.allowed_request_origins = []
+ end
+
+ test "disable forgery protection" do
+ @server.config.disable_request_forgery_protection = true
+ assert_origin_allowed 'http://rubyonrails.com'
+ assert_origin_allowed 'http://hax.com'
+ end
+
+ test "explicitly specified a single allowed origin" do
+ @server.config.allowed_request_origins = 'http://hax.com'
+ assert_origin_not_allowed 'http://rubyonrails.com'
+ assert_origin_allowed 'http://hax.com'
+ end
+
+ test "explicitly specified multiple allowed origins" do
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com http://www.rubyonrails.com )
+ assert_origin_allowed 'http://rubyonrails.com'
+ assert_origin_allowed 'http://www.rubyonrails.com'
+ assert_origin_not_allowed 'http://hax.com'
+ end
+
+ test "explicitly specified a single regexp allowed origin" do
+ @server.config.allowed_request_origins = /.*ha.*/
+ assert_origin_not_allowed 'http://rubyonrails.com'
+ assert_origin_allowed 'http://hax.com'
+ end
+
+ test "explicitly specified multiple regexp allowed origins" do
+ @server.config.allowed_request_origins = [/http:\/\/ruby.*/, /.*rai.s.*com/, 'string' ]
+ assert_origin_allowed 'http://rubyonrails.com'
+ assert_origin_allowed 'http://www.rubyonrails.com'
+ assert_origin_not_allowed 'http://hax.com'
+ assert_origin_not_allowed 'http://rails.co.uk'
+ end
+
+ private
+ def assert_origin_allowed(origin)
+ response = connect_with_origin origin
+ assert_equal(-1, response[0])
+ end
+
+ def assert_origin_not_allowed(origin)
+ response = connect_with_origin origin
+ assert_equal 404, response[0]
+ end
+
+ def connect_with_origin(origin)
+ response = nil
+
+ run_in_eventmachine do
+ response = Connection.new(@server, env_for_origin(origin)).process
+ end
+
+ response
+ end
+
+ def env_for_origin(origin)
+ Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', 'SERVER_NAME' => HOST,
+ 'HTTP_ORIGIN' => origin
+ end
+end
diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb
new file mode 100644
index 0000000000..02e6b21845
--- /dev/null
+++ b/actioncable/test/connection/identifier_test.rb
@@ -0,0 +1,77 @@
+require 'test_helper'
+require 'stubs/test_server'
+require 'stubs/user'
+
+class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_user
+ attr_reader :websocket
+
+ public :process_internal_message
+
+ def connect
+ self.current_user = User.new "lifo"
+ end
+ end
+
+ test "connection identifier" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "User#lifo", @connection.connection_identifier
+ end
+ end
+
+ test "should subscribe to internal channel on open and unsubscribe on close" do
+ run_in_eventmachine do
+ pubsub = mock('pubsub')
+ pubsub.expects(:subscribe).with('action_cable/User#lifo')
+ pubsub.expects(:unsubscribe_proc).with('action_cable/User#lifo', kind_of(Proc))
+
+ server = TestServer.new
+ server.stubs(:pubsub).returns(pubsub)
+
+ open_connection server: server
+ close_connection
+ end
+ end
+
+ test "processing disconnect message" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+
+ @connection.websocket.expects(:close)
+ message = ActiveSupport::JSON.encode('type' => 'disconnect')
+ @connection.process_internal_message message
+ end
+ end
+
+ test "processing invalid message" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+
+ @connection.websocket.expects(:close).never
+ message = ActiveSupport::JSON.encode('type' => 'unknown')
+ @connection.process_internal_message message
+ end
+ end
+
+ protected
+ def open_connection_with_stubbed_pubsub
+ server = TestServer.new
+ server.stubs(:pubsub).returns(stub_everything('pubsub'))
+
+ open_connection server: server
+ end
+
+ def open_connection(server:)
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ @connection = Connection.new(server, env)
+
+ @connection.process
+ @connection.send :on_open
+ end
+
+ def close_connection
+ @connection.send :on_close
+ end
+end
diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb
new file mode 100644
index 0000000000..55a9f96cb3
--- /dev/null
+++ b/actioncable/test/connection/multiple_identifiers_test.rb
@@ -0,0 +1,41 @@
+require 'test_helper'
+require 'stubs/test_server'
+require 'stubs/user'
+
+class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_user, :current_room
+
+ def connect
+ self.current_user = User.new "lifo"
+ self.current_room = Room.new "my", "room"
+ end
+ end
+
+ test "multiple connection identifiers" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "Room#my-room:User#lifo", @connection.connection_identifier
+ end
+ end
+
+ protected
+ def open_connection_with_stubbed_pubsub
+ server = TestServer.new
+ server.stubs(:pubsub).returns(stub_everything('pubsub'))
+
+ open_connection server: server
+ end
+
+ def open_connection(server:)
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ @connection = Connection.new(server, env)
+
+ @connection.process
+ @connection.send :on_open
+ end
+
+ def close_connection
+ @connection.send :on_close
+ end
+end
diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb
new file mode 100644
index 0000000000..9d0bda83ef
--- /dev/null
+++ b/actioncable/test/connection/string_identifier_test.rb
@@ -0,0 +1,43 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_token
+
+ def connect
+ self.current_token = "random-string"
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ test "connection identifier" do
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "random-string", @connection.connection_identifier
+ end
+ end
+
+ protected
+ def open_connection_with_stubbed_pubsub
+ @server = TestServer.new
+ @server.stubs(:pubsub).returns(stub_everything('pubsub'))
+
+ open_connection
+ end
+
+ def open_connection
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ @connection = Connection.new(@server, env)
+
+ @connection.process
+ @connection.send :on_open
+ end
+
+ def close_connection
+ @connection.send :on_close
+ end
+end
diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb
new file mode 100644
index 0000000000..62e41484fe
--- /dev/null
+++ b/actioncable/test/connection/subscriptions_test.rb
@@ -0,0 +1,115 @@
+require 'test_helper'
+
+class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
+ class ChatChannel < ActionCable::Channel::Base
+ attr_reader :room, :lines
+
+ def subscribed
+ @room = Room.new params[:id]
+ @lines = []
+ end
+
+ def speak(data)
+ @lines << data
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.stubs(:channel_classes).returns(ChatChannel.name => ChatChannel)
+
+ @chat_identifier = ActiveSupport::JSON.encode(id: 1, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel')
+ end
+
+ test "subscribe command" do
+ run_in_eventmachine do
+ setup_connection
+ channel = subscribe_to_chat_channel
+
+ assert_kind_of ChatChannel, channel
+ assert_equal 1, channel.room.id
+ end
+ end
+
+ test "subscribe command without an identifier" do
+ run_in_eventmachine do
+ setup_connection
+
+ @subscriptions.execute_command 'command' => 'subscribe'
+ assert @subscriptions.identifiers.empty?
+ end
+ end
+
+ test "unsubscribe command" do
+ run_in_eventmachine do
+ setup_connection
+ subscribe_to_chat_channel
+
+ channel = subscribe_to_chat_channel
+ channel.expects(:unsubscribe_from_channel)
+
+ @subscriptions.execute_command 'command' => 'unsubscribe', 'identifier' => @chat_identifier
+ assert @subscriptions.identifiers.empty?
+ end
+ end
+
+ test "unsubscribe command without an identifier" do
+ run_in_eventmachine do
+ setup_connection
+
+ @subscriptions.execute_command 'command' => 'unsubscribe'
+ assert @subscriptions.identifiers.empty?
+ end
+ end
+
+ test "message command" do
+ run_in_eventmachine do
+ setup_connection
+ channel = subscribe_to_chat_channel
+
+ data = { 'content' => 'Hello World!', 'action' => 'speak' }
+ @subscriptions.execute_command 'command' => 'message', 'identifier' => @chat_identifier, 'data' => ActiveSupport::JSON.encode(data)
+
+ assert_equal [ data ], channel.lines
+ end
+ end
+
+ test "unsubscrib from all" do
+ run_in_eventmachine do
+ setup_connection
+
+ channel1 = subscribe_to_chat_channel
+
+ channel2_id = ActiveSupport::JSON.encode(id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel')
+ channel2 = subscribe_to_chat_channel(channel2_id)
+
+ channel1.expects(:unsubscribe_from_channel)
+ channel2.expects(:unsubscribe_from_channel)
+
+ @subscriptions.unsubscribe_from_all
+ end
+ end
+
+ private
+ def subscribe_to_chat_channel(identifier = @chat_identifier)
+ @subscriptions.execute_command 'command' => 'subscribe', 'identifier' => identifier
+ assert_equal identifier, @subscriptions.identifiers.last
+
+ @subscriptions.send :find, 'identifier' => identifier
+ end
+
+ def setup_connection
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ @connection = Connection.new(@server, env)
+
+ @subscriptions = ActionCable::Connection::Subscriptions.new(@connection)
+ end
+end
diff --git a/actioncable/test/stubs/global_id.rb b/actioncable/test/stubs/global_id.rb
new file mode 100644
index 0000000000..334f0d03e8
--- /dev/null
+++ b/actioncable/test/stubs/global_id.rb
@@ -0,0 +1,8 @@
+class GlobalID
+ attr_reader :uri
+ delegate :to_param, :to_s, to: :uri
+
+ def initialize(gid, options = {})
+ @uri = gid
+ end
+end
diff --git a/actioncable/test/stubs/room.rb b/actioncable/test/stubs/room.rb
new file mode 100644
index 0000000000..cd66a0b687
--- /dev/null
+++ b/actioncable/test/stubs/room.rb
@@ -0,0 +1,16 @@
+class Room
+ attr_reader :id, :name
+
+ def initialize(id, name='Campfire')
+ @id = id
+ @name = name
+ end
+
+ def to_global_id
+ GlobalID.new("Room##{id}-#{name}")
+ end
+
+ def to_gid_param
+ to_global_id.to_param
+ end
+end
diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb
new file mode 100644
index 0000000000..384abc5e76
--- /dev/null
+++ b/actioncable/test/stubs/test_connection.rb
@@ -0,0 +1,21 @@
+require 'stubs/user'
+
+class TestConnection
+ attr_reader :identifiers, :logger, :current_user, :transmissions
+
+ def initialize(user = User.new("lifo"))
+ @identifiers = [ :current_user ]
+
+ @current_user = user
+ @logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
+ @transmissions = []
+ end
+
+ def transmit(data)
+ @transmissions << data
+ end
+
+ def last_transmission
+ @transmissions.last
+ end
+end
diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb
new file mode 100644
index 0000000000..f9168f9b78
--- /dev/null
+++ b/actioncable/test/stubs/test_server.rb
@@ -0,0 +1,15 @@
+require 'ostruct'
+
+class TestServer
+ include ActionCable::Server::Connections
+
+ attr_reader :logger, :config
+
+ def initialize
+ @logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
+ @config = OpenStruct.new(log_tags: [])
+ end
+
+ def send_async
+ end
+end
diff --git a/actioncable/test/stubs/user.rb b/actioncable/test/stubs/user.rb
new file mode 100644
index 0000000000..a66b4f87d5
--- /dev/null
+++ b/actioncable/test/stubs/user.rb
@@ -0,0 +1,15 @@
+class User
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def to_global_id
+ GlobalID.new("User##{name}")
+ end
+
+ def to_gid_param
+ to_global_id.to_param
+ end
+end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
new file mode 100644
index 0000000000..325305939f
--- /dev/null
+++ b/actioncable/test/test_helper.rb
@@ -0,0 +1,37 @@
+require File.expand_path('../../../load_paths', __FILE__)
+
+require 'action_cable'
+require 'active_support/testing/autorun'
+
+
+require 'puma'
+require 'em-hiredis'
+
+require 'mocha/setup'
+
+require 'rack/mock'
+
+# Require all the stubs and models
+Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file }
+
+require 'faye/websocket'
+class << Faye::WebSocket
+ remove_method :ensure_reactor_running
+
+ # We don't want Faye to start the EM reactor in tests because it makes testing much harder.
+ # We want to be able to start and stop EM loop in tests to make things simpler.
+ def ensure_reactor_running
+ # no-op
+ end
+end
+
+class ActionCable::TestCase < ActiveSupport::TestCase
+ def run_in_eventmachine
+ EM.run do
+ yield
+
+ EM.run_deferred_callbacks
+ EM.stop
+ end
+ end
+end
diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb
new file mode 100644
index 0000000000..9911a3b98b
--- /dev/null
+++ b/actioncable/test/worker_test.rb
@@ -0,0 +1,47 @@
+require 'test_helper'
+
+class WorkerTest < ActiveSupport::TestCase
+ class Receiver
+ attr_accessor :last_action
+
+ def run
+ @last_action = :run
+ end
+
+ def process(message)
+ @last_action = [ :process, message ]
+ end
+
+ def connection
+ end
+ end
+
+ setup do
+ @worker = ActionCable::Server::Worker.new
+ @receiver = Receiver.new
+ end
+
+ teardown do
+ @receiver.last_action = nil
+ end
+
+ test "invoke" do
+ @worker.invoke @receiver, :run
+ assert_equal :run, @receiver.last_action
+ end
+
+ test "invoke with arguments" do
+ @worker.invoke @receiver, :process, "Hello"
+ assert_equal [ :process, "Hello" ], @receiver.last_action
+ end
+
+ test "running periodic timers with a proc" do
+ @worker.run_periodic_timer @receiver, @receiver.method(:run)
+ assert_equal :run, @receiver.last_action
+ end
+
+ test "running periodic timers with a method" do
+ @worker.run_periodic_timer @receiver, :run
+ assert_equal :run, @receiver.last_action
+ end
+end
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 0ecb0235bc..e74e0bc20a 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,5 +1,7 @@
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
* `config.force_ssl = true` will set
- `config.action_mailer.default_url_options = { protocol: 'https' }`
+ `config.action_mailer.default_url_options = { protocol: 'https' }`.
*Andrew Kampjes*
@@ -58,7 +60,7 @@
*Carlos Souza*
-* Remove deprecate `*_path` helpers in email views.
+* Remove deprecated `*_path` helpers in email views.
*Rafael Mendonça França*
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index e5c2ed8c77..397ebe4201 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -146,7 +146,7 @@ The Base class has the full list of configuration options. Here's an example:
The latest version of Action Mailer can be installed with RubyGems:
- % gem install actionmailer
+ $ gem install actionmailer
Source code can be downloaded as part of the Rails project on GitHub
@@ -173,4 +173,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 782b208ef4..fa6043fdd7 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 312dd1997c..55c017e338 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 0b12860619..bb3cb1be45 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -541,10 +541,6 @@ module ActionMailer
end
end
- def respond_to?(method, include_private = false) #:nodoc:
- super || action_methods.include?(method.to_s)
- end
-
protected
def set_payload_for_mail(payload, mail) #:nodoc:
@@ -566,6 +562,12 @@ module ActionMailer
super
end
end
+
+ private
+
+ def respond_to_missing?(method, include_all = false) #:nodoc:
+ action_methods.include?(method.to_s)
+ end
end
attr_internal :message
@@ -656,18 +658,18 @@ module ActionMailer
#
# mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
#
- # If you do this, then Mail will take the file name and work out the mime type
- # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
- # base64 encode the contents of the attachment all for you.
+ # If you do this, then Mail will take the file name and work out the mime type.
+ # It will also set the Content-Type, Content-Disposition, Content-Transfer-Encoding
+ # and encode the contents of the attachment in Base64.
#
# You can also specify overrides if you want by passing a hash instead of a string:
#
# mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
# content: File.read('/path/to/filename.jpg')}
#
- # If you want to use a different encoding than Base64, you can pass an encoding in,
- # but then it is up to you to pass in the content pre-encoded, and don't expect
- # Mail to know how to decode this data:
+ # If you want to use encoding other than Base64 then you will need to pass encoding
+ # type along with the pre-encoded content as Mail doesn't know how to decode the
+ # data:
#
# file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
# mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index b35d2ed965..175122e00a 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -8,7 +8,7 @@ module ActionMailer
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index 3ec7d3d896..5a5c9d32bb 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -9,9 +9,6 @@ module Rails
def create_mailer_file
template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}_mailer.rb")
- if self.behavior == :invoke
- template "application_mailer.rb", 'app/mailers/application_mailer.rb'
- end
end
hook_for :template_engine, :test_framework
diff --git a/actionmailer/test/fixtures/async_mailer/welcome.erb b/actionmailer/test/fixtures/async_mailer/welcome.erb
deleted file mode 100644
index 01f3f00c63..0000000000
--- a/actionmailer/test/fixtures/async_mailer/welcome.erb
+++ /dev/null
@@ -1 +0,0 @@
-Welcome \ No newline at end of file
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 40f447ada7..e31838db2f 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,108 @@
+* Provide the name of HTTP Status code in assertions.
+
+ *Sean Collins*
+
+* More explicit error message when running `rake routes`. `CONTROLLER` argument
+ can now be supplied in different ways:
+ `Rails::WelcomeController`, `Rails::Welcome`, `rails/welcome`.
+
+ Fixes #22918.
+
+ *Edouard Chin*
+
+* Allow `ActionController::Parameters` instances as an argument to URL
+ helper methods. An `ArgumentError` will be raised if the passed parameters
+ are not secure.
+
+ Fixes #22832.
+
+ *Prathamesh Sonpatki*
+
+* Add option for per-form CSRF tokens.
+
+ *Greg Ose & Ben Toews*
+
+* Add tests and documentation for `ActionController::Renderers::use_renderers`.
+
+ *Benjamin Fleischer*
+
+* Fix `ActionController::Parameters#convert_parameters_to_hashes` to return filtered
+ or unfiltered values based on from where it is called, `to_h` or `to_unsafe_h`
+ respectively.
+
+ Fixes #22841.
+
+ *Prathamesh Sonpatki*
+
+* Add `ActionController::Parameters#include?`
+
+ *Justin Coyne*
+
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
+* Deprecate `redirect_to :back` in favor of `redirect_back`, which accepts a
+ required `fallback_location` argument, thus eliminating the possibility of a
+ `RedirectBackError`.
+
+ *Derek Prior*
+
+* Add `redirect_back` method to `ActionController::Redirecting` to provide a
+ way to safely redirect to the `HTTP_REFERER` if it is present, falling back
+ to a provided redirect otherwise.
+
+ *Derek Prior*
+
+* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1
+
+ With the speed improvements made to `ActionDispatch::IntegrationTest` we no
+ longer need to keep two separate code bases for testing controllers. In
+ Rails 5.1 `ActionController::TestCase` will be deprecated and moved into a
+ gem outside of Rails source.
+
+ This is a documentation deprecation so that going forward so new tests will use
+ `ActionDispatch::IntegrationTest` instead of `ActionController::TestCase`.
+
+ *Eileen M. Uchitelle*
+
+* Add a `response_format` option to `ActionDispatch::DebugExceptions`
+ to configure the format of the response when errors occur in
+ development mode.
+
+ If `response_format` is `:default` the debug info will be rendered
+ in an HTML page. In the other hand, if the provided value is `:api`
+ the debug info will be rendered in the original response format.
+
+ *Jorge Bejar*
+
+* Change the `protect_from_forgery` prepend default to `false`.
+
+ Per this comment
+ https://github.com/rails/rails/pull/18334#issuecomment-69234050 we want
+ `protect_from_forgery` to default to `prepend: false`.
+
+ `protect_from_forgery` will now be inserted into the callback chain at the
+ point it is called in your application. This is useful for cases where you
+ want to `protect_from_forgery` after you perform required authentication
+ callbacks or other callbacks that are required to run after forgery protection.
+
+ If you want `protect_from_forgery` callbacks to always run first, regardless of
+ position they are called in your application then you can add `prepend: true`
+ to your `protect_from_forgery` call.
+
+ Example:
+
+ ```ruby
+ protect_from_forgery prepend: true
+ ```
+
+ *Eileen M. Uchitelle*
+
+* In url_for, never append a question mark to the URL when the query string
+ is empty anyway. (It used to do that when called like `url_for(controller:
+ 'x', action: 'y', q: {})`.)
+
+ *Paul Grayson*
+
* Catch invalid UTF-8 querystring values and respond with BadRequest
Check querystring params for invalid UTF-8 characters, and raise an
@@ -17,26 +122,29 @@
*Agis Anastasopoulos*
-* Add the ability of returning arbitrary headers to ActionDispatch::Static
+* Add the ability of returning arbitrary headers to `ActionDispatch::Static`.
Now ActionDispatch::Static can accept HTTP headers so that developers
will have control of returning arbitrary headers like
'Access-Control-Allow-Origin' when a response is delivered. They can be
configured with `#config`:
- config.public_file_server.headers = {
- "Cache-Control" => "public, max-age=60",
- "Access-Control-Allow-Origin" => "http://rubyonrails.org"
- }
+ Example:
+
+ config.public_file_server.headers = {
+ "Cache-Control" => "public, max-age=60",
+ "Access-Control-Allow-Origin" => "http://rubyonrails.org"
+ }
*Yuki Nishijima*
* Allow multiple `root` routes in same scope level. Example:
- ```ruby
- root 'blog#show', constraints: ->(req) { Hostname.blog_site?(req.host) }
- root 'landing#show'
- ```
+ Example:
+
+ root 'blog#show', constraints: ->(req) { Hostname.blog_site?(req.host) }
+ root 'landing#show'
+
*Rafael Sales*
* Fix regression in mounted engine named routes generation for app deployed to
@@ -47,12 +155,12 @@
*Matthew Erhard*
-* ActionDispatch::Response#new no longer applies default headers. If you want
+* `ActionDispatch::Response#new` no longer applies default headers. If you want
default headers applied to the response object, then call
- `ActionDispatch::Response.create`. This change only impacts people who are
+ `ActionDispatch::Response.create`. This change only impacts people who are
directly constructing an `ActionDispatch::Response` object.
-* Accessing mime types via constants like `Mime::HTML` is deprecated. Please
+* Accessing mime types via constants like `Mime::HTML` is deprecated. Please
change code like this:
Mime::HTML
@@ -105,7 +213,7 @@
*Jeremy Friesen*
-* Using strings or symbols for middleware class names is deprecated. Convert
+* Using strings or symbols for middleware class names is deprecated. Convert
things like this:
middleware.use "Foo::Bar"
@@ -114,10 +222,10 @@
middleware.use Foo::Bar
-* ActionController::TestSession now accepts a default value as well as
+* `ActionController::TestSession` now accepts a default value as well as
a block for generating a default value based off the key provided.
- This fixes calls to session#fetch in ApplicationController instances that
+ This fixes calls to `session#fetch` in `ApplicationController` instances that
take more two arguments or a block from raising `ArgumentError: wrong
number of arguments (2 for 1)` when performing controller tests.
@@ -168,10 +276,10 @@
*Grey Baker*
* Add support for API only apps.
- ActionController::API is added as a replacement of
- ActionController::Base for this kind of applications.
+ `ActionController::API` is added as a replacement of
+ `ActionController::Base` for this kind of applications.
- *Santiago Pastorino & Jorge Bejar*
+ *Santiago Pastorino*, *Jorge Bejar*
* Remove `assigns` and `assert_template`. Both methods have been extracted
into a gem at https://github.com/rails/rails-controller-testing.
@@ -246,7 +354,7 @@
* Allow `Bearer` as token-keyword in `Authorization-Header`.
- Aditionally to `Token`, the keyword `Bearer` is acceptable as a keyword
+ Additionally to `Token`, the keyword `Bearer` is acceptable as a keyword
for the auth-token. The `Bearer` keyword is described in the original
OAuth RFC and used in libraries like Angular-JWT.
@@ -254,7 +362,7 @@
*Peter Schröder*
-* Drop request class from RouteSet constructor.
+* Drop request class from `RouteSet` constructor.
If you would like to use a custom request class, please subclass and implement
the `request_class` method.
@@ -283,7 +391,7 @@
*Jeremy Kemper*, *Yves Senn*
-* Deprecate AbstractController#skip_action_callback in favor of individual skip_callback methods
+* Deprecate `AbstractController#skip_action_callback` in favor of individual skip_callback methods
(which can be made to raise an error if no callback was removed).
*Iain Beeston*
@@ -489,9 +597,7 @@
Fixes an issue where when an exception is raised in the request the additional
payload data is not available.
- See:
- * #14903
- * https://github.com/roidrage/lograge/issues/37
+ See #14903.
*Dieter Komendera*, *Margus Pärt*
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 44c980b070..0720c66cb9 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -28,7 +28,7 @@ can be used outside of Rails.
The latest version of Action Pack can be installed with RubyGems:
- % gem install actionpack
+ $ gem install actionpack
Source code can be downloaded as part of the Rails project on GitHub
@@ -55,4 +55,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 7f349f2741..8edea0f52b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -49,7 +49,7 @@ module AbstractController
# instance methods on that abstract class. Public instance methods of
# a controller would normally be considered action methods, so methods
# declared on abstract classes are being removed.
- # (ActionController::Metal and ActionController::Base are defined as abstract)
+ # (<tt>ActionController::Metal</tt> and ActionController::Base are defined as abstract)
def internal_methods
controller = self
@@ -80,7 +80,7 @@ module AbstractController
# action_methods are cached and there is sometimes need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
- # you run action_methods, they will be recalculated
+ # you run action_methods, they will be recalculated.
def clear_action_methods!
@action_methods = nil
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index d5317e4717..d63ce9c1c3 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -48,7 +48,8 @@ module AbstractController
def _normalize_callback_option(options, from, to) # :nodoc:
if from = options[from]
- from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ")
+ _from = Array(from).map(&:to_s).to_set
+ from = proc {|c| _from.include? c.action_name }
options[to] = Array(options[to]).unshift(from)
end
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 2694d4c12f..b9ad51a9cf 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -14,12 +14,57 @@ module ActionController
#
# expire_fragment('name_of_cache')
module Fragments
+ extend ActiveSupport::Concern
+
+ included do
+ if respond_to?(:class_attribute)
+ class_attribute :fragment_cache_keys
+ else
+ mattr_writer :fragment_cache_keys
+ end
+
+ self.fragment_cache_keys = []
+
+ helper_method :fragment_cache_key if respond_to?(:helper_method)
+ end
+
+ module ClassMethods
+ # Allows you to specify controller-wide key prefixes for
+ # cache fragments. Pass either a constant +value+, or a block
+ # which computes a value each time a cache key is generated.
+ #
+ # For example, you may want to prefix all fragment cache keys
+ # with a global version identifier, so you can easily
+ # invalidate all caches.
+ #
+ # class ApplicationController
+ # fragment_cache_key "v1"
+ # end
+ #
+ # When it's time to invalidate all fragments, simply change
+ # the string constant. Or, progressively roll out the cache
+ # invalidation using a computed value:
+ #
+ # class ApplicationController
+ # fragment_cache_key do
+ # @account.id.odd? ? "v1" : "v2"
+ # end
+ # end
+ def fragment_cache_key(value = nil, &key)
+ self.fragment_cache_keys += [key || ->{ value }]
+ end
+ end
+
# Given a key (as described in +expire_fragment+), returns
# a key suitable for use in reading, writing, or expiring a
- # cached fragment. All keys are prefixed with <tt>views/</tt> and uses
- # ActiveSupport::Cache.expand_cache_key for the expansion.
+ # cached fragment. All keys begin with <tt>views/</tt>,
+ # followed by any controller-wide key prefix values, ending
+ # with the specified +key+ value. The key is expanded using
+ # ActiveSupport::Cache.expand_cache_key.
def fragment_cache_key(key)
- ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
+ head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
+ tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
+ ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
end
# Writes +content+ to the location signified by
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 8e040bb465..f6a93a8940 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -166,7 +166,7 @@ module ActionController
alias :response_code :status # :nodoc:
- # Basic url_for that can be overridden for more robust functionality
+ # Basic url_for that can be overridden for more robust functionality.
def url_for(string)
string
end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index d86a793e4c..f8e0d9cf6c 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -228,7 +228,7 @@ module ActionController
expires_in 100.years, public: public
yield if stale?(etag: "#{version}-#{request.fullpath}",
- last_modified: Time.parse('2011-01-01').utc,
+ last_modified: Time.new(2011, 1, 1).utc,
public: public)
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 0a36fecd27..2ac6e37e34 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -397,7 +397,7 @@ module ActionController
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
TOKEN_KEY = 'token='
- TOKEN_REGEX = /^(Token|Bearer) /
+ TOKEN_REGEX = /^(Token|Bearer)\s+/
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 27b3eb4e58..e3c540bf5f 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -36,8 +36,7 @@ module ActionController
extend ActiveSupport::Concern
module ClassMethods
- def make_response!(response)
- request = response.request
+ def make_response!(request)
if request.get_header("HTTP_VERSION") == "HTTP/1.0"
super
else
@@ -223,12 +222,6 @@ module ActionController
jar.write self unless committed?
end
- def before_sending
- super
- request.cookie_jar.commit!
- headers.freeze
- end
-
def build_buffer(response, body)
buf = Live::Buffer.new response
body.each { |part| buf.write part }
@@ -293,9 +286,5 @@ module ActionController
super
response.close if response
end
-
- def set_response!(response)
- @_response = self.class.make_response! response
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 0febc905f1..b13ba06962 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -20,8 +20,6 @@ module ActionController
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
- # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
- # Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
# === Examples:
#
@@ -30,7 +28,6 @@ module ActionController
# redirect_to "http://www.rubyonrails.org"
# redirect_to "/images/screenshot.jpg"
# redirect_to articles_url
- # redirect_to :back
# redirect_to proc { edit_post_url(@post) }
#
# The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
@@ -61,13 +58,8 @@ module ActionController
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
#
- # When using <tt>redirect_to :back</tt>, if there is no referrer,
- # <tt>ActionController::RedirectBackError</tt> will be raised. You
- # may specify some fallback behavior for this case by rescuing
- # <tt>ActionController::RedirectBackError</tt>.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
- raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
@@ -75,6 +67,32 @@ module ActionController
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
end
+ # Redirects the browser to the page that issued the request (the referrer)
+ # if possible, otherwise redirects to the provided default fallback
+ # location.
+ #
+ # The referrer information is pulled from the HTTP `Referer` (sic) header on
+ # the request. This is an optional header and its presence on the request is
+ # subject to browser security settings and user preferences. If the request
+ # is missing this header, the <tt>fallback_location</tt> will be used.
+ #
+ # redirect_back fallback_location: { action: "show", id: 5 }
+ # redirect_back fallback_location: post
+ # redirect_back fallback_location: "http://www.rubyonrails.org"
+ # redirect_back fallback_location: "/images/screenshot.jpg"
+ # redirect_back fallback_location: articles_url
+ # redirect_back fallback_location: proc { edit_post_url(@post) }
+ #
+ # All options that can be passed to <tt>redirect_to</tt> are accepted as
+ # options and the behavior is indetical.
+ def redirect_back(fallback_location:, **args)
+ if referer = request.headers["Referer"]
+ redirect_to referer, **args
+ else
+ redirect_to fallback_location, **args
+ end
+ end
+
def _compute_redirect_to_location(request, options) #:nodoc:
case options
# The scheme name consist of a letter followed by any combination of
@@ -87,6 +105,12 @@ module ActionController
when String
request.protocol + request.host_with_port + options
when :back
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ `redirect_to :back` is deprecated and will be removed from Rails 5.1.
+ Please use `redirect_back(fallback_location: fallback_location)` where
+ `fallback_location` represents the location to use if the request has
+ no HTTP referer information.
+ MESSAGE
request.headers["Referer"] or raise RedirectBackError
when Proc
_compute_redirect_to_location request, options.call
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 22e0bb5955..90fb34e386 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -11,6 +11,7 @@ module ActionController
Renderers.remove(key)
end
+ # See <tt>Responder#api_behavior</tt>
class MissingRenderer < LoadError
def initialize(format)
super "No renderer defined for format: #{format}"
@@ -20,40 +21,25 @@ module ActionController
module Renderers
extend ActiveSupport::Concern
+ # A Set containing renderer names that correspond to available renderer procs.
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
+ RENDERERS = Set.new
+
included do
class_attribute :_renderers
self._renderers = Set.new.freeze
end
- module ClassMethods
- def use_renderers(*args)
- renderers = _renderers + args
- self._renderers = renderers.freeze
- end
- alias use_renderer use_renderers
- end
-
- def render_to_body(options)
- _render_to_body_with_renderer(options) || super
- end
+ # Used in <tt>ActionController::Base</tt>
+ # and <tt>ActionController::API</tt> to include all
+ # renderers by default.
+ module All
+ extend ActiveSupport::Concern
+ include Renderers
- def _render_to_body_with_renderer(options)
- _renderers.each do |name|
- if options.key?(name)
- _process_options(options)
- method_name = Renderers._render_with_renderer_method_name(name)
- return send(method_name, options.delete(name), options)
- end
+ included do
+ self._renderers = RENDERERS
end
- nil
- end
-
- # A Set containing renderer names that correspond to available renderer procs.
- # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
- RENDERERS = Set.new
-
- def self._render_with_renderer_method_name(key)
- "_render_with_renderer_#{key}"
end
# Adds a new renderer to call within controller actions.
@@ -103,13 +89,70 @@ module ActionController
remove_method(method_name) if method_defined?(method_name)
end
- module All
- extend ActiveSupport::Concern
- include Renderers
+ def self._render_with_renderer_method_name(key)
+ "_render_with_renderer_#{key}"
+ end
- included do
- self._renderers = RENDERERS
+ module ClassMethods
+
+ # Adds, by name, a renderer or renderers to the +_renderers+ available
+ # to call within controller actions.
+ #
+ # It is useful when rendering from an <tt>ActionController::Metal</tt> controller or
+ # otherwise to add an available renderer proc to a specific controller.
+ #
+ # Both <tt>ActionController::Base</tt> and <tt>ActionController::API</tt>
+ # include <tt>ActionController::Renderers::All</tt>, making all renderers
+ # avaialable in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
+ #
+ # Since <tt>ActionController::Metal</tt> controllers cannot render, the controller
+ # must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>,
+ # and <tt>ActionController::Renderers</tt>, and have at lest one renderer.
+ #
+ # Rather than including <tt>ActionController::Renderers::All</tt> and including all renderers,
+ # you may specify which renderers to include by passing the renderer name or names to
+ # +use_renderers+. For example, a controller that includes only the <tt>:json</tt> renderer
+ # (+_render_with_renderer_json+) might look like:
+ #
+ # class MetalRenderingController < ActionController::Metal
+ # include AbstractController::Rendering
+ # include ActionController::Rendering
+ # include ActionController::Renderers
+ #
+ # use_renderers :json
+ #
+ # def show
+ # render json: record
+ # end
+ # end
+ #
+ # You must specify a +use_renderer+, else the +controller.renderer+ and
+ # +controller._renderers+ will be <tt>nil</tt>, and the action will fail.
+ def use_renderers(*args)
+ renderers = _renderers + args
+ self._renderers = renderers.freeze
end
+ alias use_renderer use_renderers
+ end
+
+ # Called by +render+ in <tt>AbstractController::Rendering</tt>
+ # which sets the return value as the +response_body+.
+ #
+ # If no renderer is found, +super+ returns control to
+ # <tt>ActionView::Rendering.render_to_body</tt>, if present.
+ def render_to_body(options)
+ _render_to_body_with_renderer(options) || super
+ end
+
+ def _render_to_body_with_renderer(options)
+ _renderers.each do |name|
+ if options.key?(name)
+ _process_options(options)
+ method_name = Renderers._render_with_renderer_method_name(name)
+ return send(method_name, options.delete(name), options)
+ end
+ end
+ nil
end
add :json do |json, options|
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 64f6f7cf51..91b3403ad5 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -77,6 +77,14 @@ module ActionController #:nodoc:
config_accessor :log_warning_on_csrf_failure
self.log_warning_on_csrf_failure = true
+ # Controls whether the Origin header is checked in addition to the CSRF token.
+ config_accessor :forgery_protection_origin_check
+ self.forgery_protection_origin_check = false
+
+ # Controls whether form-action/method specific CSRF tokens are used.
+ config_accessor :per_form_csrf_tokens
+ self.per_form_csrf_tokens = false
+
helper_method :form_authenticity_token
helper_method :protect_against_forgery?
end
@@ -98,13 +106,13 @@ module ActionController #:nodoc:
#
# Valid Options:
#
- # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <tt>only: [ :create, :create_all ]</tt>.
+ # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
# * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
- # * <tt>:prepend</tt> - By default, the verification of the authentication token is added to the front of the
- # callback chain. If you need to make the verification depend on other callbacks, like authentication methods
- # (say cookies vs OAuth), this might not work for you. Pass <tt>prepend: false</tt> to just add the
- # verification callback in the position of the protect_from_forgery call. This means any callbacks added
- # before are run first.
+ # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
+ # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
+ # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
+ #
+ # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
# * <tt>:with</tt> - Set the method to handle unverified request.
#
# Valid unverified request handling methods are:
@@ -112,7 +120,7 @@ module ActionController #:nodoc:
# * <tt>:reset_session</tt> - Resets the session.
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
def protect_from_forgery(options = {})
- options = options.reverse_merge(prepend: true)
+ options = options.reverse_merge(prepend: false)
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
@@ -257,21 +265,41 @@ module ActionController #:nodoc:
# * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?
!protect_against_forgery? || request.get? || request.head? ||
- valid_authenticity_token?(session, form_authenticity_param) ||
- valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
+ (valid_request_origin? && any_authenticity_token_valid?)
+ end
+
+ # Checks if any of the authenticity tokens from the request are valid.
+ def any_authenticity_token_valid?
+ request_authenticity_tokens.any? do |token|
+ valid_authenticity_token?(session, token)
+ end
+ end
+
+ # Possible authenticity tokens sent in the request.
+ def request_authenticity_tokens
+ [form_authenticity_param, request.x_csrf_token]
end
# Sets the token value for the current session.
- def form_authenticity_token
- masked_authenticity_token(session)
+ def form_authenticity_token(form_options: {})
+ masked_authenticity_token(session, form_options: form_options)
end
# Creates a masked version of the authenticity token that varies
# on each request. The masking is used to mitigate SSL attacks
# like BREACH.
- def masked_authenticity_token(session)
+ def masked_authenticity_token(session, form_options: {})
+ action, method = form_options.values_at(:action, :method)
+
+ raw_token = if per_form_csrf_tokens && action && method
+ action_path = normalize_action_path(action)
+ per_form_csrf_token(session, action_path, method)
+ else
+ real_csrf_token(session)
+ end
+
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
- encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
masked_token = one_time_pad + encrypted_csrf_token
Base64.strict_encode64(masked_token)
end
@@ -301,28 +329,54 @@ module ActionController #:nodoc:
compare_with_real_token masked_token, session
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
- # Split the token into the one-time pad and the encrypted
- # value and decrypt it
- one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
- encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
- csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)
-
- compare_with_real_token csrf_token, session
+ csrf_token = unmask_token(masked_token)
+ compare_with_real_token(csrf_token, session) ||
+ valid_per_form_csrf_token?(csrf_token, session)
else
false # Token is malformed
end
end
+ def unmask_token(masked_token)
+ # Split the token into the one-time pad and the encrypted
+ # value and decrypt it
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
+ xor_byte_strings(one_time_pad, encrypted_csrf_token)
+ end
+
def compare_with_real_token(token, session)
ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
end
+ def valid_per_form_csrf_token?(token, session)
+ if per_form_csrf_tokens
+ correct_token = per_form_csrf_token(
+ session,
+ normalize_action_path(request.fullpath),
+ request.request_method
+ )
+
+ ActiveSupport::SecurityUtils.secure_compare(token, correct_token)
+ else
+ false
+ end
+ end
+
def real_csrf_token(session)
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
Base64.strict_decode64(session[:_csrf_token])
end
+ def per_form_csrf_token(session, action_path, method)
+ OpenSSL::HMAC.digest(
+ OpenSSL::Digest::SHA256.new,
+ real_csrf_token(session),
+ [action_path, method.downcase].join("#")
+ )
+ end
+
def xor_byte_strings(s1, s2)
s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
end
@@ -336,5 +390,20 @@ module ActionController #:nodoc:
def protect_against_forgery?
allow_forgery_protection
end
+
+ # Checks if the request originated from the same origin by looking at the
+ # Origin header.
+ def valid_request_origin?
+ if forgery_protection_origin_check
+ # We accept blank origin headers because some user agents don't send it.
+ request.origin.nil? || request.origin == request.base_url
+ else
+ true
+ end
+ end
+
+ def normalize_action_path(action_path)
+ action_path.split('?').first.to_s.chomp('/')
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 130ba61786..5cbf4157a4 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -1,8 +1,10 @@
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/hash/transform_values'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/string/filters'
require 'active_support/rescuable'
require 'action_dispatch/http/upload'
+require 'rack/test'
require 'stringio'
require 'set'
@@ -107,7 +109,8 @@ module ActionController
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
- delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
+ delegate :keys, :key?, :has_key?, :empty?, :include?, :inspect,
+ :as_json, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
@@ -161,8 +164,8 @@ module ActionController
end
end
- # Returns a safe +Hash+ representation of this parameter with all
- # unpermitted keys removed.
+ # Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt>
+ # representation of this parameter with all unpermitted keys removed.
#
# params = ActionController::Parameters.new({
# name: 'Senjougahara Hitagi',
@@ -174,15 +177,17 @@ module ActionController
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h
if permitted?
- @parameters.to_h
+ convert_parameters_to_hashes(@parameters, :to_h)
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
end
- # Returns an unsafe, unfiltered +Hash+ representation of this parameter.
+ # Returns an unsafe, unfiltered
+ # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
+ # parameter.
def to_unsafe_h
- @parameters.to_h
+ convert_parameters_to_hashes(@parameters, :to_unsafe_h)
end
alias_method :to_unsafe_hash, :to_unsafe_h
@@ -415,7 +420,7 @@ module ActionController
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
- def fetch(key, *args, &block)
+ def fetch(key, *args)
convert_value_to_parameters(
@parameters.fetch(key) {
if block_given?
@@ -510,7 +515,7 @@ module ActionController
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
- def delete(key, &block)
+ def delete(key)
convert_value_to_parameters(@parameters.delete(key))
end
@@ -591,6 +596,21 @@ module ActionController
end
end
+ def convert_parameters_to_hashes(value, using)
+ case value
+ when Array
+ value.map { |v| convert_parameters_to_hashes(v, using) }
+ when Hash
+ value.transform_values do |v|
+ convert_parameters_to_hashes(v, using)
+ end.with_indifferent_access
+ when Parameters
+ value.send(using)
+ else
+ value
+ end
+ end
+
def convert_hashes_to_parameters(key, value)
converted = convert_value_to_parameters(value)
@parameters[key] = converted unless converted.equal?(value)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 442ffd6d7c..b43bb9dc17 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -7,6 +7,13 @@ require 'action_controller/template_assertions'
require 'rails-dom-testing'
module ActionController
+ # :stopdoc:
+ class Metal
+ include Testing::Functional
+ end
+
+ # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
+ # Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
DEFAULT_ENV.delete 'PATH_INFO'
@@ -452,16 +459,16 @@ module ActionController
parameters = nil
end
- if parameters.present? || session.present? || flash.present?
+ if parameters || session || flash
non_kwarg_request_warning
end
end
- if body.present?
+ if body
@request.set_header 'RAW_POST_DATA', body
end
- if http_method.present?
+ if http_method
http_method = http_method.to_s.upcase
else
http_method = "GET"
@@ -469,16 +476,12 @@ module ActionController
parameters ||= {}
- if format.present?
+ if format
parameters[:format] = format
end
@html_document = nil
- unless @controller.respond_to?(:recycle!)
- @controller.extend(Testing::Functional)
- end
-
self.cookies.update @request.cookies
self.cookies.update_cookies_from_jar
@request.set_header 'HTTP_COOKIE', cookies.to_header
@@ -658,4 +661,5 @@ module ActionController
include Behavior
end
+ # :startdoc:
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index f6336c8c7a..1e4df07d6e 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -95,6 +95,7 @@ module ActionDispatch
autoload :TestProcess
autoload :TestRequest
autoload :TestResponse
+ autoload :AssertionResponse
end
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 30ade14c26..842dfa5827 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -91,7 +91,7 @@ module ActionDispatch
DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
- SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate])
+ SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
def cache_control_segments
if cache_control = _cache_control
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 7acf91902d..e9b25339dc 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -69,6 +69,8 @@ module ActionDispatch
Array(Mime[parameters[:format]])
elsif use_accept_header && valid_accept_header
accepts
+ elsif extension_format = format_from_path_extension
+ [extension_format]
elsif xhr?
[Mime[:js]]
else
@@ -160,6 +162,13 @@ module ActionDispatch
def use_accept_header
!self.class.ignore_accept_header
end
+
+ def format_from_path_extension
+ path = @env['action_dispatch.original_path'] || @env['PATH_INFO']
+ if match = path && path.match(/\.(\w+)\z/)
+ Mime[match.captures.first]
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index b64f660ec5..b8d395854c 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -47,15 +47,10 @@ module Mime
def const_missing(sym)
ext = sym.downcase
if Mime[ext]
- ActiveSupport::Deprecation.warn <<-eow
-Accessing mime types via constants is deprecated. Please change:
-
- `Mime::#{sym}`
-
-to:
-
- `Mime[:#{ext}]`
- eow
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Accessing mime types via constants is deprecated.
+ Please change `Mime::#{sym}` to `Mime[:#{ext}]`.
+ MSG
Mime[ext]
else
super
@@ -65,15 +60,10 @@ to:
def const_defined?(sym, inherit = true)
ext = sym.downcase
if Mime[ext]
- ActiveSupport::Deprecation.warn <<-eow
-Accessing mime types via constants is deprecated. Please change:
-
- `Mime.const_defined?(#{sym})`
-
-to:
-
- `Mime[:#{ext}]`
- eow
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Accessing mime types via constants is deprecated.
+ Please change `Mime.const_defined?(#{sym})` to `Mime[:#{ext}]`.
+ MSG
true
else
super
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index c9df787351..cca7376ffa 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -43,7 +43,7 @@ module ActionDispatch
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
def path_parameters
- get_header(PARAMETERS_KEY) || {}
+ get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
end
private
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index bd0f38953a..5427425ef7 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -36,8 +36,8 @@ module ActionDispatch
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
- HTTP_X_FORWARDED_FOR HTTP_VERSION
- HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST
+ HTTP_X_FORWARDED_FOR HTTP_ORIGIN HTTP_VERSION
+ HTTP_X_CSRF_TOKEN HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST
SERVER_ADDR
].freeze
@@ -49,6 +49,10 @@ module ActionDispatch
METHOD
end
+ def self.empty
+ new({})
+ end
+
def initialize(env)
super
@method = nil
@@ -59,6 +63,9 @@ module ActionDispatch
@ip = nil
end
+ def commit_cookie_jar! # :nodoc:
+ end
+
def check_path_parameters!
# If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
@@ -252,7 +259,7 @@ module ActionDispatch
end
# Returns the IP address of client as a +String+,
- # usually set by the RemoteIp middleware.
+ # usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index f0127aa276..14f86c7c07 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -232,7 +232,7 @@ module ActionDispatch # :nodoc:
end
# Sets the HTTP character set. In case of nil parameter
- # it sets the charset to utf-8.
+ # it sets the charset to utf-8.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
@@ -412,6 +412,15 @@ module ActionDispatch # :nodoc:
end
def before_sending
+ # Normally we've already committed by now, but it's possible
+ # (e.g., if the controller action tries to read back its own
+ # response) to get here before that. In that case, we must force
+ # an "early" commit: we're about to freeze the headers, so this is
+ # our last chance.
+ commit! unless committed?
+
+ headers.freeze
+ request.commit_cookie_jar! unless committed?
end
def build_buffer(response, body)
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 92b10b6d3b..37f41ae988 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -81,7 +81,8 @@ module ActionDispatch
def add_params(path, params)
params = { params: params } unless params.is_a?(Hash)
params.reject! { |_,v| v.to_param.nil? }
- path << "?#{params.to_query}" unless params.empty?
+ query = params.to_query
+ path << "?#{query}" unless query.empty?
end
def add_anchor(path, anchor)
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 5ee8810066..018b89a2b7 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -124,7 +124,7 @@ module ActionDispatch
end
def captures
- (length - 1).times.map { |i| self[i + 1] }
+ Array.new(length - 1) { |i| self[i + 1] }
end
def [](x)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 65baf117ba..3477aa8b29 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -12,6 +12,12 @@ module ActionDispatch
end
# :stopdoc:
+ prepend Module.new {
+ def commit_cookie_jar!
+ cookie_jar.commit!
+ end
+ }
+
def have_cookie_jar?
has_header? 'action_dispatch.cookies'.freeze
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 66bb74b9c5..b55c937e0c 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -38,9 +38,10 @@ module ActionDispatch
end
end
- def initialize(app, routes_app = nil)
- @app = app
- @routes_app = routes_app
+ def initialize(app, routes_app = nil, response_format = :default)
+ @app = app
+ @routes_app = routes_app
+ @response_format = response_format
end
def call(env)
@@ -66,41 +67,79 @@ module ActionDispatch
log_error(request, wrapper)
if request.get_header('action_dispatch.show_detailed_exceptions')
- traces = wrapper.traces
-
- trace_to_show = 'Application Trace'
- if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
- trace_to_show = 'Full Trace'
+ case @response_format
+ when :api
+ render_for_api_application(request, wrapper)
+ when :default
+ render_for_default_application(request, wrapper)
end
+ else
+ raise exception
+ end
+ end
- if source_to_show = traces[trace_to_show].first
- source_to_show_id = source_to_show[:id]
- end
+ def render_for_default_application(request, wrapper)
+ template = create_template(request, wrapper)
+ file = "rescues/#{wrapper.rescue_template}"
- template = DebugView.new([RESCUES_TEMPLATE_PATH],
- request: request,
- exception: wrapper.exception,
- traces: traces,
- show_source_idx: source_to_show_id,
- trace_to_show: trace_to_show,
- routes_inspector: routes_inspector(exception),
- source_extracts: wrapper.source_extracts,
- line_number: wrapper.line_number,
- file: wrapper.file
- )
- file = "rescues/#{wrapper.rescue_template}"
-
- if request.xhr?
- body = template.render(template: file, layout: false, formats: [:text])
- format = "text/plain"
- else
- body = template.render(template: file, layout: 'rescues/layout')
- format = "text/html"
- end
- render(wrapper.status_code, body, format)
+ if request.xhr?
+ body = template.render(template: file, layout: false, formats: [:text])
+ format = "text/plain"
else
- raise exception
+ body = template.render(template: file, layout: 'rescues/layout')
+ format = "text/html"
end
+ render(wrapper.status_code, body, format)
+ end
+
+ def render_for_api_application(request, wrapper)
+ body = {
+ status: wrapper.status_code,
+ error: Rack::Utils::HTTP_STATUS_CODES.fetch(
+ wrapper.status_code,
+ Rack::Utils::HTTP_STATUS_CODES[500]
+ ),
+ exception: wrapper.exception.inspect,
+ traces: wrapper.traces
+ }
+
+ content_type = request.formats.first
+ to_format = "to_#{content_type.to_sym}"
+
+ if content_type && body.respond_to?(to_format)
+ formatted_body = body.public_send(to_format)
+ format = content_type
+ else
+ formatted_body = body.to_json
+ format = Mime[:json]
+ end
+
+ render(wrapper.status_code, formatted_body, format)
+ end
+
+ def create_template(request, wrapper)
+ traces = wrapper.traces
+
+ trace_to_show = 'Application Trace'
+ if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
+ trace_to_show = 'Full Trace'
+ end
+
+ if source_to_show = traces[trace_to_show].first
+ source_to_show_id = source_to_show[:id]
+ end
+
+ DebugView.new([RESCUES_TEMPLATE_PATH],
+ request: request,
+ exception: wrapper.exception,
+ traces: traces,
+ show_source_idx: source_to_show_id,
+ trace_to_show: trace_to_show,
+ routes_inspector: routes_inspector(wrapper.exception),
+ source_extracts: wrapper.source_extracts,
+ line_number: wrapper.line_number,
+ file: wrapper.file
+ )
end
def render(status, body, format)
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 0e636b8257..429a98f236 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -36,7 +36,7 @@ module ActionDispatch
# development:
# secret_key_base: 'secret key'
#
- # To generate a secret key for an existing application, run `rake secret`.
+ # To generate a secret key for an existing application, run `rails 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.
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 47f475559a..735b5939dd 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -1,19 +1,23 @@
module ActionDispatch
- # This middleware is added to the stack when `config.force_ssl = true`.
- # It does three jobs to enforce secure HTTP requests:
+ # This middleware is added to the stack when `config.force_ssl = true`, and is passed
+ # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
+ # requests:
#
- # 1. TLS redirect. http:// requests are permanently redirected to https://
- # with the same URL host, path, etc. Pass `:host` and/or `:port` to
- # modify the destination URL. This is always enabled.
+ # 1. TLS redirect: Permanently redirects http:// requests to https://
+ # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
+ # to modify the destination URL
+ # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
+ # `redirect: false` to disable this feature.
#
- # 2. Secure cookies. Sets the `secure` flag on cookies to tell browsers they
- # mustn't be sent along with http:// requests. This is always enabled.
+ # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
+ # mustn't be sent along with http:// requests. Enabled by default. Set
+ # `config.ssl_options` with `secure_cookies: false` to disable this feature.
#
- # 3. HTTP Strict Transport Security (HSTS). Tells the browser to remember
+ # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
# this site as TLS-only and automatically redirect non-TLS requests.
- # Enabled by default. Pass `hsts: false` to disable.
+ # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
#
- # Configure HSTS with `hsts: { … }`:
+ # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
# * `expires`: How long, in seconds, these settings will stick. Defaults to
# `180.days` (recommended). The minimum required to qualify for browser
# preload lists is `18.weeks`.
@@ -26,10 +30,10 @@ module ActionDispatch
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
# Go to https://hstspreload.appspot.com to submit your site for inclusion.
#
- # Disabling HSTS: To turn off HSTS, omitting the header is not enough.
- # Browsers will remember the original HSTS directive until it expires.
- # Instead, use the header to tell browsers to expire HSTS immediately.
- # Setting `hsts: false` is a shortcut for `hsts: { expires: 0 }`.
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
+ # expire HSTS immediately. Setting `hsts: false` is a shortcut for
+ # `hsts: { expires: 0 }`.
class SSL
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
@@ -39,7 +43,7 @@ module ActionDispatch
{ expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
end
- def initialize(app, redirect: {}, hsts: {}, **options)
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
@app = app
if options[:host] || options[:port]
@@ -52,6 +56,7 @@ module ActionDispatch
@redirect = redirect
end
+ @secure_cookies = secure_cookies
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
@@ -61,10 +66,11 @@ module ActionDispatch
if request.ssl?
@app.call(env).tap do |status, headers, body|
set_hsts_header! headers
- flag_cookies_as_secure! headers
+ flag_cookies_as_secure! headers if @secure_cookies
end
else
- redirect_to_https request
+ return redirect_to_https request if @redirect
+ @app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 44fc1ee736..0b4bee5462 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -14,6 +14,15 @@ module ActionDispatch
def name; klass.name; end
+ def ==(middleware)
+ case middleware
+ when Middleware
+ klass == middleware.klass
+ when Class
+ klass == middleware
+ end
+ end
+
def inspect
if klass.is_a?(Class)
klass.to_s
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index ea9ab3821d..41c220236a 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -27,7 +27,7 @@ module ActionDispatch
# in the server's `public/` directory (see Static#call).
def match?(path)
path = ::Rack::Utils.unescape_path path
- return false unless path.valid_encoding?
+ return false unless valid_path?(path)
path = Rack::Utils.clean_path_info path
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
@@ -94,6 +94,10 @@ module ActionDispatch
false
end
end
+
+ def valid_path?(path)
+ path.valid_encoding? && !path.include?("\0")
+ end
end
# This middleware will attempt to return the contents of a file's body from
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 9e7fcbd849..42890225fa 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -109,7 +109,7 @@ module ActionDispatch
@delegate.values
end
- # Writes given value to given key of the session.
+ # Writes given value to given key of the session.
def []=(key, value)
load_for_write!
@delegate[key.to_s] = value
@@ -148,8 +148,8 @@ module ActionDispatch
@delegate.delete key.to_s
end
- # Returns value of given key from the session, or raises +KeyError+
- # if can't find given key in case of not setted dafault value.
+ # Returns value of the given key from the session, or raises +KeyError+
+ # if can't find the given key and no default value is set.
# Returns default value if specified.
#
# session.fetch(:foo)
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 59c3f9248f..d00b2c3eb5 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -237,7 +237,7 @@ module ActionDispatch
#
# == View a list of all your routes
#
- # rake routes
+ # rails routes
#
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
#
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index f3a5268d2e..69e6dd5215 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -65,7 +65,7 @@ module ActionDispatch
routes = collect_routes(routes_to_display)
if routes.none?
- formatter.no_routes
+ formatter.no_routes(collect_routes(@routes), filter)
return formatter.result
end
@@ -84,7 +84,8 @@ module ActionDispatch
def filter_routes(filter)
if filter
- @routes.select { |route| route.defaults[:controller] == filter }
+ filter_name = filter.underscore.sub(/_controller$/, '')
+ @routes.select { |route| route.defaults[:controller] == filter_name }
else
@routes
end
@@ -136,17 +137,27 @@ module ActionDispatch
@buffer << draw_header(routes)
end
- def no_routes
- @buffer << <<-MESSAGE.strip_heredoc
+ def no_routes(routes, filter)
+ @buffer <<
+ if routes.none?
+ <<-MESSAGE.strip_heredoc
You don't have any routes defined!
Please add some routes in config/routes.rb.
-
- For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
MESSAGE
+ elsif missing_controller?(filter)
+ "The controller #{filter} does not exist!"
+ else
+ "No routes were found for this controller"
+ end
+ @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
end
private
+ def missing_controller?(controller_name)
+ [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize)
+ end
+
def draw_section(routes)
header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
@@ -187,7 +198,7 @@ module ActionDispatch
def header(routes)
end
- def no_routes
+ def no_routes(*)
@buffer << <<-MESSAGE.strip_heredoc
<p>You don't have any routes defined!</p>
<ul>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 7c0404ca62..f57d88d1a5 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -11,7 +11,7 @@ module ActionDispatch
class Mapper
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
- class Constraints < Endpoint #:nodoc:
+ class Constraints < Routing::Endpoint #:nodoc:
attr_reader :app, :constraints
SERVE = ->(app, req) { app.serve req }
@@ -187,7 +187,7 @@ module ActionDispatch
# Get all the symbol nodes followed by literals that are not the
# dummy node.
symbols = ast.find_all { |n|
- n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
+ n.cat? && n.left.symbol? && n.right.cat?
}.map(&:left)
# Get all the symbol nodes preceded by literals.
@@ -387,24 +387,6 @@ module ActionDispatch
end
module Base
- # You can specify what Rails should route "/" to with the root method:
- #
- # root to: 'pages#main'
- #
- # For options, see +match+, as +root+ uses it internally.
- #
- # You can also pass a string which will expand
- #
- # root 'pages#main'
- #
- # You should put the root route at the top of <tt>config/routes.rb</tt>,
- # because this means it will be matched first. As this is the most popular route
- # of most Rails applications, this is beneficial.
- def root(options = {})
- name = has_named_route?(:root) ? nil : :root
- match '/', { as: name, via: :get }.merge!(options)
- end
-
# Matches a url pattern to one or more routes.
#
# You should not use the +match+ method in your router
@@ -600,17 +582,20 @@ module ActionDispatch
def mount(app, options = nil)
if options
path = options.delete(:at)
- else
- unless Hash === app
- raise ArgumentError, "must be called with mount point"
- end
-
+ elsif Hash === app
options = app
app, path = options.find { |k, _| k.respond_to?(:call) }
options.delete(app) if app
end
- raise "A rack application must be specified" unless path
+ raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
+ raise ArgumentError, <<-MSG.strip_heredoc unless path
+ Must be called with mount point
+
+ mount SomeRackApp, at: "some_route"
+ or
+ mount(SomeRackApp => "some_route")
+ MSG
rails_app = rails_app? app
options[:as] ||= app_name(app, rails_app)
@@ -1686,7 +1671,20 @@ to this:
@set.add_route(mapping, ast, as, anchor)
end
- def root(path, options={})
+ # You can specify what Rails should route "/" to with the root method:
+ #
+ # root to: 'pages#main'
+ #
+ # For options, see +match+, as +root+ uses it internally.
+ #
+ # You can also pass a string which will expand
+ #
+ # root 'pages#main'
+ #
+ # You should put the root route at the top of <tt>config/routes.rb</tt>,
+ # because this means it will be matched first. As this is the most popular route
+ # of most Rails applications, this is beneficial.
+ def root(path, options = {})
if path.is_a?(String)
options[:to] = path
elsif path.is_a?(Hash) and options.empty?
@@ -1698,11 +1696,11 @@ to this:
if @scope.resources?
with_scope_level(:root) do
path_scope(parent_resource.path) do
- super(options)
+ match_root_route(options)
end
end
else
- super(options)
+ match_root_route(options)
end
end
@@ -1897,6 +1895,11 @@ to this:
ensure
@scope = @scope.parent
end
+
+ def match_root_route(options)
+ name = has_named_route?(:root) ? nil : :root
+ match '/', { :as => name, :via => :get }.merge!(options)
+ end
end
# Routing Concerns allow you to declare common routes that can be reused
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index c4228df925..846b5fa1fc 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -30,9 +30,9 @@ module ActionDispatch
controller = controller req
res = controller.make_response! req
dispatch(controller, params[:action], req, res)
- rescue NameError => e
+ rescue ActionController::RoutingError
if @raise_on_name_error
- raise ActionController::RoutingError, e.message, e.backtrace
+ raise
else
return [404, {'X-Cascade' => 'pass'}, []]
end
@@ -42,6 +42,8 @@ module ActionDispatch
def controller(req)
req.controller_class
+ rescue NameError => e
+ raise ActionController::RoutingError, e.message, e.backtrace
end
def dispatch(controller, action, req, res)
@@ -279,8 +281,17 @@ module ActionDispatch
helper = UrlHelper.create(route, opts, route_key, url_strategy)
mod.module_eval do
define_method(name) do |*args|
- options = nil
- options = args.pop if args.last.is_a? Hash
+ last = args.last
+ options = case last
+ when Hash
+ args.pop
+ when ActionController::Parameters
+ if last.permitted?
+ args.pop.to_h
+ else
+ raise ArgumentError, "Generating an URL from non sanitized request parameters is insecure!"
+ end
+ end
helper.call self, args, options
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index b6c031dcf4..f91679593e 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -172,8 +172,11 @@ module ActionDispatch
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
route_name)
when ActionController::Parameters
+ unless options.permitted?
+ raise ArgumentError.new("Generating an URL from non sanitized request parameters is insecure!")
+ end
route_name = options.delete :use_route
- _routes.url_for(options.to_unsafe_h.symbolize_keys.
+ _routes.url_for(options.to_h.symbolize_keys.
reverse_merge!(url_options), route_name)
when String
options
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
new file mode 100644
index 0000000000..3fb81ff083
--- /dev/null
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -0,0 +1,49 @@
+module ActionDispatch
+ # This is a class that abstracts away an asserted response.
+ # It purposely does not inherit from Response, because it doesn't need it.
+ # That means it does not have headers or a body.
+ #
+ # As an input to the initializer, we take a Fixnum, a String, or a Symbol.
+ # If it's a Fixnum or String, we figure out what its symbolized name.
+ # If it's a Symbol, we figure out what its corresponding code is.
+ # The resulting code will be a Fixnum, for real HTTP codes, and it will
+ # be a String for the pseudo-HTTP codes, such as:
+ # :success, :missing, :redirect and :error
+ class AssertionResponse
+ attr_reader :code, :name
+
+ GENERIC_RESPONSE_CODES = { # :nodoc:
+ success: "2XX",
+ missing: "404",
+ redirect: "3XX",
+ error: "5XX"
+ }
+
+ def initialize(code_or_name)
+ if code_or_name.is_a?(Symbol)
+ @name = code_or_name
+ @code = code_from_name(code_or_name)
+ else
+ @name = name_from_code(code_or_name)
+ @code = code_or_name
+ end
+
+ raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
+ raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
+ end
+
+ def code_and_name
+ "#{code}: #{name}"
+ end
+
+ private
+
+ def code_from_name(name)
+ GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
+ end
+
+ def name_from_code(code)
+ GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index eab20b075d..cd55b7d975 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,4 +1,3 @@
-
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -27,18 +26,12 @@ module ActionDispatch
# # Asserts that the response code was status code 401 (unauthorized)
# assert_response 401
def assert_response(type, message = nil)
- if Symbol === type
- if [:success, :missing, :redirect, :error].include?(type)
- assert_predicate @response, RESPONSE_PREDICATES[type], message
- else
- code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
- if code.nil?
- raise ArgumentError, "Invalid response type :#{type}"
- end
- assert_equal code, @response.response_code, message
- end
+ message ||= generate_response_message(type)
+
+ if RESPONSE_PREDICATES.keys.include?(type)
+ assert @response.send(RESPONSE_PREDICATES[type]), message
else
- assert_equal type, @response.response_code, message
+ assert_equal AssertionResponse.new(type).code, @response.response_code, message
end
end
@@ -82,6 +75,26 @@ module ActionDispatch
handle._compute_redirect_to_location(@request, fragment)
end
end
+
+ def generate_response_message(expected, actual = @response.response_code)
+ "Expected response to be a <#{code_with_name(expected)}>,"\
+ " but was a <#{code_with_name(actual)}>"
+ .concat location_if_redirected
+ end
+
+ def location_if_redirected
+ return '' unless @response.redirection? && @response.location.present?
+ location = normalize_argument_to_redirection(@response.location)
+ " redirect to <#{location}>"
+ end
+
+ def code_with_name(code_or_name)
+ if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
+ code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
+ end
+
+ AssertionResponse.new(code_or_name).code_and_name
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 790f9ea5d2..711ca10419 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -375,6 +375,7 @@ module ActionDispatch
@request = ActionDispatch::Request.new(session.last_request.env)
response = _mock_session.last_response
@response = ActionDispatch::TestResponse.from_response(response)
+ @response.request = @request
@html_document = nil
@url_options = nil
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index c28d701b48..eca0439909 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -26,7 +26,7 @@ module ActionDispatch
@response.redirect_url
end
- # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), type)</tt>:
+ # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>:
#
# post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
#
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index f664dab620..941877d10d 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 255ac9f4ed..5cfb5f02d8 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -8,7 +8,7 @@ module ActionPack
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index ef7aab72c6..1ef10ff956 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -20,7 +20,11 @@ rescue LoadError
puts "'drb/unix' is not available"
end
-PROCESS_COUNT = (ENV['N'] || 4).to_i
+if ENV['TRAVIS']
+ PROCESS_COUNT = 0
+else
+ PROCESS_COUNT = (ENV['N'] || 4).to_i
+end
require 'active_support/testing/autorun'
require 'abstract_controller'
@@ -371,6 +375,12 @@ module RoutingTestHelpers
end
end
+class MetalRenderingController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+ include ActionController::Renderers
+end
+
class ResourcesController < ActionController::Base
def index() head :ok end
alias_method :show, :index
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 82c747680d..579ce0ed29 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -6,7 +6,12 @@ module ActionDispatch
class ResponseAssertionsTest < ActiveSupport::TestCase
include ResponseAssertions
- FakeResponse = Struct.new(:response_code) do
+ FakeResponse = Struct.new(:response_code, :location) do
+ def initialize(*)
+ super
+ self.location ||= "http://test.example.com/posts"
+ end
+
[:successful, :not_found, :redirection, :server_error].each do |sym|
define_method("#{sym}?") do
sym == response_code
@@ -14,6 +19,11 @@ module ActionDispatch
end
end
+ def setup
+ @controller = nil
+ @request = nil
+ end
+
def test_assert_response_predicate_methods
[:success, :missing, :redirect, :error].each do |sym|
@response = FakeResponse.new RESPONSE_PREDICATES[sym].to_s.sub(/\?/, '').to_sym
@@ -58,6 +68,50 @@ module ActionDispatch
assert_response :succezz
}
end
+
+ def test_error_message_shows_404_when_404_asserted_for_success
+ @response = ActionDispatch::Response.new
+ @response.status = 404
+
+ error = assert_raises(Minitest::Assertion) { assert_response :success }
+ expected = "Expected response to be a <2XX: success>,"\
+ " but was a <404: Not Found>"
+ assert_match expected, error.message
+ end
+
+ def test_error_message_shows_404_when_asserted_for_200
+ @response = ActionDispatch::Response.new
+ @response.status = 404
+
+ error = assert_raises(Minitest::Assertion) { assert_response 200 }
+ expected = "Expected response to be a <200: OK>,"\
+ " but was a <404: Not Found>"
+ assert_match expected, error.message
+ end
+
+ def test_error_message_shows_302_redirect_when_302_asserted_for_success
+ @response = ActionDispatch::Response.new
+ @response.status = 302
+ @response.location = 'http://test.host/posts/redirect/1'
+
+ error = assert_raises(Minitest::Assertion) { assert_response :success }
+ expected = "Expected response to be a <2XX: success>,"\
+ " but was a <302: Found>" \
+ " redirect to <http://test.host/posts/redirect/1>"
+ assert_match expected, error.message
+ end
+
+ def test_error_message_shows_302_redirect_when_302_asserted_for_301
+ @response = ActionDispatch::Response.new
+ @response.status = 302
+ @response.location = 'http://test.host/posts/redirect/2'
+
+ error = assert_raises(Minitest::Assertion) { assert_response 301 }
+ expected = "Expected response to be a <301: Moved Permanently>,"\
+ " but was a <302: Found>" \
+ " redirect to <http://test.host/posts/redirect/2>"
+ assert_match expected, error.message
+ end
end
end
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index fb60dbd993..e3f669dbb5 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -93,7 +93,7 @@ end
class ControllerInstanceTests < ActiveSupport::TestCase
def setup
@empty = EmptyController.new
- @empty.set_request!(ActionDispatch::Request.new({}))
+ @empty.set_request!(ActionDispatch::Request.empty)
@empty.set_response!(EmptyController.make_response!(@empty.request))
@contained = Submodule::ContainedEmptyController.new
@empty_controllers = [@empty, @contained]
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index bc0ffd3eaa..d19b3810c2 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -419,3 +419,28 @@ class AutomaticCollectionCacheTest < ActionController::TestCase
assert_equal 1, @controller.partial_rendered_times
end
end
+
+class FragmentCacheKeyTestController < CachingController
+ attr_accessor :account_id
+
+ fragment_cache_key "v1"
+ fragment_cache_key { account_id }
+end
+
+class FragmentCacheKeyTest < ActionController::TestCase
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @controller = FragmentCacheKeyTestController.new
+ @controller.perform_caching = true
+ @controller.cache_store = @store
+ end
+
+ def test_fragment_cache_key
+ @controller.account_id = "123"
+ assert_equal 'views/v1/123/what a key', @controller.fragment_cache_key('what a key')
+
+ @controller.account_id = nil
+ assert_equal 'views/v1//what a key', @controller.fragment_cache_key('what a key')
+ end
+end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 3ecfedefd1..feb882a2b3 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -141,20 +141,10 @@ class HelperTest < ActiveSupport::TestCase
def test_helper_for_nested_controller
assert_equal 'hello: Iz guuut!',
call_controller(Fun::GamesController, "render_hello_world").last.body
- # request = ActionController::TestRequest.new
- #
- # resp = Fun::GamesController.action(:render_hello_world).call(request.env)
- # assert_equal 'hello: Iz guuut!', resp.last.body
end
def test_helper_for_acronym_controller
assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body
- #
- # request = ActionController::TestRequest.new
- # response = ActionDispatch::TestResponse.new
- # request.action = 'test'
- #
- # assert_equal 'test: baz', Fun::PdfController.process(request, response).body
end
def test_default_helpers_only
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 0a5e5402b9..adcf259317 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -5,6 +5,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
before_action :authenticate, only: :index
before_action :authenticate_with_request, only: :display
before_action :authenticate_long_credentials, only: :show
+ before_action :auth_with_special_chars, only: :special_creds
http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
@@ -20,6 +21,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
render plain: 'Only for loooooong credentials'
end
+ def special_creds
+ render plain: 'Only for special credentials'
+ end
+
def search
render plain: 'All inline'
end
@@ -40,6 +45,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
end
end
+ def auth_with_special_chars
+ authenticate_or_request_with_http_basic do |username, password|
+ username == 'login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' && password == 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t'
+ end
+ end
+
def authenticate_long_credentials
authenticate_or_request_with_http_basic do |username, password|
username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890'
@@ -100,7 +111,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_no_match(/\n/, result)
end
- test "succesful authentication with uppercase authorization scheme" do
+ test "successful authentication with uppercase authorization scheme" do
@request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}"
get :index
@@ -133,6 +144,14 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_equal 'Definitely Maybe', @response.body
end
+ test "authentication request with valid credential special chars" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t', 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t')
+ get :special_creds
+
+ assert_response :success
+ assert_equal 'Only for special credentials', @response.body
+ end
+
test "authenticate with class method" do
@request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath')
get :search
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index 9c5a01c318..98e3c891a7 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -94,6 +94,14 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
assert_response :success
end
+ test "authentication request with tab in header" do
+ @request.env['HTTP_AUTHORIZATION'] = "Token\ttoken=\"lifo\""
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body
+ end
+
test "authentication request without credential" do
get :display
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 843dafac06..aab2d9545d 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -442,3 +442,42 @@ module ActionController
end
end
end
+
+class LiveStreamRouterTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ include ActionController::Live
+
+ def index
+ response.headers['Content-Type'] = 'text/event-stream'
+ sse = SSE.new(response.stream)
+ sse.write("{\"name\":\"John\"}")
+ sse.write({ name: "Ryan" })
+ ensure
+ sse.close
+ end
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ routes.draw do
+ get '/test' => 'live_stream_router_test/test#index'
+ end
+
+ def app
+ self.class
+ end
+
+ test "streaming served through the router" do
+ get "/test"
+
+ assert_response :ok
+ assert_match(/data: {\"name\":\"John\"}/, response.body)
+ assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
+ end
+end
diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb
new file mode 100644
index 0000000000..007866a559
--- /dev/null
+++ b/actionpack/test/controller/metal/renderers_test.rb
@@ -0,0 +1,42 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/conversions'
+
+class MetalRenderingJsonController < MetalRenderingController
+ class Model
+ def to_json(options = {})
+ { a: 'b' }.to_json(options)
+ end
+
+ def to_xml(options = {})
+ { a: 'b' }.to_xml(options)
+ end
+ end
+
+ use_renderers :json
+
+ def one
+ render json: Model.new
+ end
+
+ def two
+ render xml: Model.new
+ end
+end
+
+class RenderersMetalTest < ActionController::TestCase
+ tests MetalRenderingJsonController
+
+ def test_render_json
+ get :one
+ assert_response :success
+ assert_equal({ a: 'b' }.to_json, @response.body)
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_xml
+ get :two
+ assert_response :success
+ assert_equal(" ", @response.body)
+ assert_equal 'text/plain', @response.content_type
+ end
+end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index c025c7fa00..76e2d3ff43 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -661,10 +661,6 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_variant_inline_syntax
- get :variant_inline_syntax, format: :js
- assert_equal "text/javascript", @response.content_type
- assert_equal "js", @response.body
-
get :variant_inline_syntax
assert_equal "text/html", @response.content_type
assert_equal "none", @response.body
@@ -674,6 +670,12 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "phone", @response.body
end
+ def test_variant_inline_syntax_with_format
+ get :variant_inline_syntax, format: :js
+ assert_equal "text/javascript", @response.content_type
+ assert_equal "js", @response.body
+ end
+
def test_variant_inline_syntax_without_block
get :variant_inline_syntax_without_block, params: { v: :phone }
assert_equal "text/html", @response.content_type
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index e61f4d241b..c226fa57ee 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -26,7 +26,7 @@ module BareMetalTest
test "response_body value is wrapped in an array when the value is a String" do
controller = BareController.new
- controller.set_request!(ActionDispatch::Request.new({}))
+ controller.set_request!(ActionDispatch::Request.empty)
controller.set_response!(BareController.make_response!(controller.request))
controller.index
assert_equal ["Hello world"], controller.response_body
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 97875c3cbb..a8f4d877a6 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -27,6 +27,12 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params[:person][:name].permitted?
end
+ test "as_json returns the JSON representation of the parameters hash" do
+ assert_not @params.as_json.key? "parameters"
+ assert_not @params.as_json.key? "permitted"
+ assert @params.as_json.key? "person"
+ end
+
test "each carries permitted status" do
@params.permit!
@params.each { |key, value| assert(value.permitted?) if key == "person" }
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 9f7d14e85d..3299f2d9d0 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -256,7 +256,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "to_h returns empty hash on unpermitted params" do
- assert @params.to_h.is_a? Hash
+ assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess
assert_not @params.to_h.is_a? ActionController::Parameters
assert @params.to_h.empty?
end
@@ -264,7 +264,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
test "to_h returns converted hash on permitted params" do
@params.permit!
- assert @params.to_h.is_a? Hash
+ assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess
assert_not @params.to_h.is_a? ActionController::Parameters
end
@@ -273,7 +273,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
ActionController::Parameters.permit_all_parameters = true
params = ActionController::Parameters.new(crab: "Senjougahara Hitagi")
- assert params.to_h.is_a? Hash
+ assert params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess
assert_not @params.to_h.is_a? ActionController::Parameters
assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h)
ensure
@@ -294,7 +294,49 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "to_unsafe_h returns unfiltered params" do
- assert @params.to_h.is_a? Hash
- assert_not @params.to_h.is_a? ActionController::Parameters
+ assert @params.to_unsafe_h.is_a? ActiveSupport::HashWithIndifferentAccess
+ assert_not @params.to_unsafe_h.is_a? ActionController::Parameters
+ end
+
+ test "to_unsafe_h returns unfiltered params even after accessing few keys" do
+ params = ActionController::Parameters.new("f"=>{"language_facet"=>["Tibetan"]})
+ expected = {"f"=>{"language_facet"=>["Tibetan"]}}
+
+ assert params['f'].is_a? ActionController::Parameters
+ assert_equal expected, params.to_unsafe_h
+ end
+
+ test "to_h only deep dups Ruby collections" do
+ company = Class.new do
+ attr_reader :dupped
+ def dup; @dupped = true; end
+ end.new
+
+ params = ActionController::Parameters.new(prem: { likes: %i( dancing ) })
+ assert_equal({ 'prem' => { 'likes' => %i( dancing ) } }, params.permit!.to_h)
+
+ params = ActionController::Parameters.new(companies: [ company, :acme ])
+ assert_equal({ 'companies' => [ company, :acme ] }, params.permit!.to_h)
+ assert_not company.dupped
+ end
+
+ test "to_unsafe_h only deep dups Ruby collections" do
+ company = Class.new do
+ attr_reader :dupped
+ def dup; @dupped = true; end
+ end.new
+
+ params = ActionController::Parameters.new(prem: { likes: %i( dancing ) })
+ assert_equal({ 'prem' => { 'likes' => %i( dancing ) } }, params.to_unsafe_h)
+
+ params = ActionController::Parameters.new(companies: [ company, :acme ])
+ assert_equal({ 'companies' => [ company, :acme ] }, params.to_unsafe_h)
+ assert_not company.dupped
+ end
+
+ test "include? returns true when the key is present" do
+ assert @params.include? :person
+ assert @params.include? 'person'
+ assert_not @params.include? :gorilla
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 631ff7d02a..0b184eace9 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -42,6 +42,10 @@ class RedirectController < ActionController::Base
redirect_to :back, :status => 307
end
+ def redirect_back_with_status
+ redirect_back(fallback_location: "/things/stuff", status: 307)
+ end
+
def host_redirect
redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host'
end
@@ -187,7 +191,11 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_back_with_status
@request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
- get :redirect_to_back_with_status
+
+ assert_deprecated do
+ get :redirect_to_back_with_status
+ end
+
assert_response 307
assert_equal "http://www.example.com/coming/from", redirect_to_url
end
@@ -236,7 +244,11 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_back
@request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
- get :redirect_to_back
+
+ assert_deprecated do
+ get :redirect_to_back
+ end
+
assert_response :redirect
assert_equal "http://www.example.com/coming/from", redirect_to_url
end
@@ -244,10 +256,32 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_back_with_no_referer
assert_raise(ActionController::RedirectBackError) {
@request.env["HTTP_REFERER"] = nil
+
+ assert_deprecated do
+ get :redirect_to_back
+ end
+
get :redirect_to_back
}
end
+ def test_redirect_back
+ referer = "http://www.example.com/coming/from"
+ @request.env["HTTP_REFERER"] = referer
+
+ get :redirect_back_with_status
+
+ assert_response 307
+ assert_equal referer, redirect_to_url
+ end
+
+ def test_redirect_back_with_no_referer
+ get :redirect_back_with_status
+
+ assert_response 307
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
def test_redirect_to_record
with_routing do |set|
set.draw do
@@ -273,10 +307,10 @@ class RedirectTest < ActionController::TestCase
end
def test_redirect_to_params
- error = assert_raise(ActionController::ActionControllerError) do
+ error = assert_raise(ArgumentError) do
get :redirect_to_params
end
- assert_equal "Cannot redirect to a parameter hash!", error.message
+ assert_equal "Generating an URL from non sanitized request parameters is insecure!", error.message
end
def test_redirect_to_with_block
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
deleted file mode 100644
index 1f5215ac55..0000000000
--- a/actionpack/test/controller/render_other_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'abstract_unit'
-
-
-class RenderOtherTest < ActionController::TestCase
- class TestController < ActionController::Base
- def render_simon_says
- render :simon => "foo"
- end
- end
-
- tests TestController
-
- def test_using_custom_render_option
- ActionController.add_renderer :simon do |says, options|
- self.content_type = Mime[:text]
- self.response_body = "Simon says: #{says}"
- end
-
- get :render_simon_says
- assert_equal "Simon says: foo", @response.body
- ensure
- ActionController.remove_renderer :simon
- end
-end
diff --git a/actionpack/test/controller/renderers_test.rb b/actionpack/test/controller/renderers_test.rb
new file mode 100644
index 0000000000..e6c2e4636e
--- /dev/null
+++ b/actionpack/test/controller/renderers_test.rb
@@ -0,0 +1,90 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+require 'active_support/logger'
+
+class RenderersTest < ActionController::TestCase
+ class XmlRenderable
+ def to_xml(options)
+ options[:root] ||= "i-am-xml"
+ "<#{options[:root]}/>"
+ end
+ end
+ class JsonRenderable
+ def as_json(options={})
+ hash = { :a => :b, :c => :d, :e => :f }
+ hash.except!(*options[:except]) if options[:except]
+ hash
+ end
+
+ def to_json(options = {})
+ super :except => [:c, :e]
+ end
+ end
+ class CsvRenderable
+ def to_csv
+ "c,s,v"
+ end
+ end
+ class TestController < ActionController::Base
+
+ def render_simon_says
+ render :simon => "foo"
+ end
+
+ def respond_to_mime
+ respond_to do |type|
+ type.json do
+ render json: JsonRenderable.new
+ end
+ type.js { render json: 'JS', callback: 'alert' }
+ type.csv { render csv: CsvRenderable.new }
+ type.xml { render xml: XmlRenderable.new }
+ type.html { render body: "HTML" }
+ type.rss { render body: "RSS" }
+ type.all { render body: "Nothing" }
+ type.any(:js, :xml) { render body: "Either JS or XML" }
+ end
+ end
+ end
+
+ tests TestController
+
+ def setup
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ super
+ @controller.logger = ActiveSupport::Logger.new(nil)
+ end
+
+ def test_using_custom_render_option
+ ActionController.add_renderer :simon do |says, options|
+ self.content_type = Mime[:text]
+ self.response_body = "Simon says: #{says}"
+ end
+
+ get :render_simon_says
+ assert_equal "Simon says: foo", @response.body
+ ensure
+ ActionController.remove_renderer :simon
+ end
+
+ def test_raises_missing_template_no_renderer
+ assert_raise ActionView::MissingTemplate do
+ get :respond_to_mime, format: 'csv'
+ end
+ assert_equal Mime[:csv], @response.content_type
+ assert_equal "", @response.body
+ end
+
+ def test_adding_csv_rendering_via_renderers_add
+ ActionController::Renderers.add :csv do |value, options|
+ send_data value.to_csv, type: Mime[:csv]
+ end
+ @request.accept = "text/csv"
+ get :respond_to_mime, format: 'csv'
+ assert_equal Mime[:csv], @response.content_type
+ assert_equal "c,s,v", @response.body
+ ensure
+ ActionController::Renderers.remove :csv
+ end
+end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 94ffbe3cd0..1984ad8825 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -128,6 +128,23 @@ class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsin
end
end
+class PerFormTokensController < ActionController::Base
+ protect_from_forgery :with => :exception
+ self.per_form_csrf_tokens = true
+
+ def index
+ render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>"
+ end
+
+ def post_one
+ render plain: ''
+ end
+
+ def post_two
+ render plain: ''
+ end
+end
+
# common test methods
module RequestForgeryProtectionTests
def setup
@@ -304,6 +321,41 @@ module RequestForgeryProtectionTests
assert_not_blocked { put :index }
end
+ def test_should_allow_post_with_origin_checking_and_correct_origin
+ forgery_protection_origin_check do
+ session[:_csrf_token] = @token
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ @request.set_header 'HTTP_ORIGIN', 'http://test.host'
+ post :index, params: { custom_authenticity_token: @token }
+ end
+ end
+ end
+ end
+
+ def test_should_allow_post_with_origin_checking_and_no_origin
+ forgery_protection_origin_check do
+ session[:_csrf_token] = @token
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ post :index, params: { custom_authenticity_token: @token }
+ end
+ end
+ end
+ end
+
+ def test_should_block_post_with_origin_checking_and_wrong_origin
+ forgery_protection_origin_check do
+ session[:_csrf_token] = @token
+ @controller.stub :form_authenticity_token, @token do
+ assert_blocked do
+ @request.set_header 'HTTP_ORIGIN', 'http://bad.host'
+ post :index, params: { custom_authenticity_token: @token }
+ end
+ end
+ end
+ end
+
def test_should_warn_on_missing_csrf_token
old_logger = ActionController::Base.logger
logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
@@ -405,6 +457,16 @@ module RequestForgeryProtectionTests
def assert_cross_origin_not_blocked
assert_not_blocked { yield }
end
+
+ def forgery_protection_origin_check
+ old_setting = ActionController::Base.forgery_protection_origin_check
+ ActionController::Base.forgery_protection_origin_check = true
+ begin
+ yield
+ ensure
+ ActionController::Base.forgery_protection_origin_check = old_setting
+ end
+ end
end
# OK let's get our test on
@@ -495,10 +557,10 @@ class PrependProtectForgeryBaseControllerTest < ActionController::TestCase
assert_equal(expected_callback_order, @controller.called_callbacks)
end
- def test_verify_authenticity_token_is_prepended_by_default
+ def test_verify_authenticity_token_is_not_prepended_by_default
@controller = PrependDefaultController.new
get :index
- expected_callback_order = ["verify_authenticity_token", "custom_action"]
+ expected_callback_order = ["custom_action", "verify_authenticity_token"]
assert_equal(expected_callback_order, @controller.called_callbacks)
end
end
@@ -578,3 +640,158 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase
end
end
end
+
+class PerFormTokensControllerTest < ActionController::TestCase
+ def test_per_form_token_is_same_size_as_global_token
+ get :index
+ expected = ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH
+ actual = @controller.send(:per_form_csrf_token, session, '/path', 'post').size
+ assert_equal expected, actual
+ end
+
+ def test_accepts_token_for_correct_path_and_method
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+
+ def test_rejects_token_for_incorrect_path
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_two'
+ assert_raises(ActionController::InvalidAuthenticityToken) do
+ post :post_two, params: {custom_authenticity_token: form_token}
+ end
+ end
+
+ def test_rejects_token_for_incorrect_method
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_raises(ActionController::InvalidAuthenticityToken) do
+ patch :post_one, params: {custom_authenticity_token: form_token}
+ end
+ end
+
+ def test_accepts_global_csrf_token
+ get :index
+
+ token = @controller.send(:form_authenticity_token)
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: token}
+ end
+ assert_response :success
+ end
+
+ def test_ignores_params
+ get :index, params: {form_path: '/per_form_tokens/post_one?foo=bar'}
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one?foo=baz'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token, baz: 'foo'}
+ end
+ assert_response :success
+ end
+
+ def test_ignores_trailing_slash_during_generation
+ get :index, params: {form_path: '/per_form_tokens/post_one/'}
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+
+ def test_ignores_trailing_slash_during_validation
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+
+ def test_method_is_case_insensitive
+ get :index, params: {form_method: "POST"}
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index a39fede5b9..178cd41922 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -94,6 +94,18 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
end
+ def test_symbols_with_dashes_reversed
+ rs.draw do
+ get ':artist(/omg-:song)', :to => lambda { |env|
+ resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = ActiveSupport::JSON.decode get(URI('http://example.org/journey/omg-faithfully'))
+ assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ end
+
def test_id_with_dash
rs.draw do
get '/journey/:id', :to => lambda { |env|
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index e50373a0cc..b9caddcdb7 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -172,7 +172,7 @@ XML
before_action { @dynamic_opt = 'opt' }
def test_url_options_reset
- render plain: url_for(params)
+ render plain: url_for
end
def default_url_options
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 78e883f134..67212fea38 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -375,6 +375,13 @@ module AbstractController
assert_equal({'query[person][position][]' => 'prof' }.to_query, params[3])
end
+ def test_url_action_controller_parameters
+ add_host!
+ assert_raise(ArgumentError) do
+ W.new.url_for(ActionController::Parameters.new(:controller => 'c', :action => 'a', protocol: 'javascript', f: '%0Aeval(name)'))
+ end
+ end
+
def test_path_generation_for_symbol_parameter_keys
assert_generates("/image", :controller=> :image)
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 84c244c72a..dfcef14344 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -7,7 +7,7 @@ class CookieJarTest < ActiveSupport::TestCase
attr_reader :request
def setup
- @request = ActionDispatch::Request.new({})
+ @request = ActionDispatch::Request.empty
end
def test_fetch
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 30772bd9ed..159bf10545 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -75,6 +75,13 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
+ class BoomerAPI < Boomer
+ def call(env)
+ env['action_dispatch.show_detailed_exceptions'] = @detailed
+ raise "puke!"
+ end
+ end
+
RoutesApp = Struct.new(:routes).new(SharedTestRoutes)
ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp)
DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp)
@@ -205,6 +212,68 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_match(/ActionController::ParameterMissing/, body)
end
+ test "rescue with json error for API request" do
+ @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
+
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 500
+ assert_no_match(/<header>/, body)
+ assert_no_match(/<body>/, body)
+ assert_equal "application/json", response.content_type
+ assert_match(/RuntimeError: puke/, body)
+
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 404
+ assert_no_match(/<body>/, body)
+ assert_equal "application/json", response.content_type
+ assert_match(/#{AbstractController::ActionNotFound.name}/, body)
+
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 405
+ assert_no_match(/<body>/, body)
+ assert_equal "application/json", response.content_type
+ assert_match(/ActionController::MethodNotAllowed/, body)
+
+ get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 405
+ assert_no_match(/<body>/, body)
+ assert_equal "application/json", response.content_type
+ assert_match(/ActionController::UnknownHttpMethod/, body)
+
+ get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 400
+ assert_no_match(/<body>/, body)
+ assert_equal "application/json", response.content_type
+ assert_match(/ActionController::BadRequest/, body)
+
+ get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 400
+ assert_no_match(/<body>/, body)
+ assert_equal "application/json", response.content_type
+ assert_match(/ActionController::ParameterMissing/, body)
+ end
+
+ test "rescue with json on API request returns only allowed formats or json as a fallback" do
+ @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
+
+ get "/index.json", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 500
+ assert_equal "application/json", response.content_type
+ assert_match(/RuntimeError: puke/, body)
+
+ get "/index.html", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 500
+ assert_no_match(/<header>/, body)
+ assert_no_match(/<body>/, body)
+ assert_equal "application/json", response.content_type
+ assert_match(/RuntimeError: puke/, body)
+
+ get "/index.xml", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_response 500
+ assert_equal "application/xml", response.content_type
+ assert_match(/RuntimeError: puke/, body)
+ end
+
test "does not show filtered parameters" do
@app = DevelopmentApp
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index 160cdc1582..e4475f4233 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -6,7 +6,7 @@ module ActionController
class ResponseTest < ActiveSupport::TestCase
def setup
@response = Live::Response.new
- @response.request = ActionDispatch::Request.new({}) #yolo
+ @response.request = ActionDispatch::Request.empty
end
def test_header_merge
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index e783df855e..df27e41997 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -158,7 +158,7 @@ module ActionDispatch
assert_equal '/*path.:format', fakeset.asts.first.to_s
end
- def test_raising_helpful_error_on_invalid_arguments
+ def test_raising_error_when_path_is_not_passed
fakeset = FakeSet.new
mapper = Mapper.new fakeset
app = lambda { |env| [200, {}, [""]] }
@@ -166,6 +166,18 @@ module ActionDispatch
mapper.mount app
end
end
+
+ def test_raising_error_when_rack_app_is_not_passed
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ assert_raises ArgumentError do
+ mapper.mount 10, as: "exciting"
+ end
+
+ assert_raises ArgumentError do
+ mapper.mount as: "exciting"
+ end
+ end
end
end
end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 33aa616474..a8c8e0784f 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -131,4 +131,20 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack.last.klass
end
end
+
+ test "can check if Middleware are equal - Class" do
+ assert_equal @stack.last, BarMiddleware
+ end
+
+ test "includes a class" do
+ assert_equal true, @stack.include?(BarMiddleware)
+ end
+
+ test "can check if Middleware are equal - Middleware" do
+ assert_equal @stack.last, @stack.last
+ end
+
+ test "includes a middleware" do
+ assert_equal true, @stack.include?(ActionDispatch::MiddlewareStack::Middleware.new(BarMiddleware, nil, nil))
+ end
end
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index ae0e7e93ed..7dcbcc5c21 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -7,7 +7,7 @@ module ActionDispatch
attr_reader :req
def setup
- @req = ActionDispatch::Request.new({})
+ @req = ActionDispatch::Request.empty
end
def test_create_adds_itself_to_env
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 08c4554721..0edad72fd9 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -4,9 +4,13 @@ class BaseRequestTest < ActiveSupport::TestCase
def setup
@env = {
:ip_spoofing_check => true,
- :tld_length => 1,
"rack.input" => "foo"
}
+ @original_tld_length = ActionDispatch::Http::URL.tld_length
+ end
+
+ def teardown
+ ActionDispatch::Http::URL.tld_length = @original_tld_length
end
def url_for(options = {})
@@ -19,9 +23,9 @@ class BaseRequestTest < ActiveSupport::TestCase
ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true
@trusted_proxies ||= nil
ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies)
- tld_length = env.key?(:tld_length) ? env.delete(:tld_length) : 1
+ ActionDispatch::Http::URL.tld_length = env.delete(:tld_length) if env.key?(:tld_length)
+
ip_app.call(env)
- ActionDispatch::Http::URL.tld_length = tld_length
env = @env.merge(env)
ActionDispatch::Request.new(env)
@@ -254,15 +258,6 @@ end
class RequestDomain < BaseRequestTest
test "domains" do
- request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org'
- assert_equal "rubyonrails.org", request.domain
-
- request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk"
- assert_equal "rubyonrails.co.uk", request.domain(2)
-
- request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk", :tld_length => 2
- assert_equal "rubyonrails.co.uk", request.domain
-
request = stub_request 'HTTP_HOST' => "192.168.1.200"
assert_nil request.domain
@@ -271,25 +266,18 @@ class RequestDomain < BaseRequestTest
request = stub_request 'HTTP_HOST' => "192.168.1.200.com"
assert_equal "200.com", request.domain
- end
- test "subdomains" do
- request = stub_request 'HTTP_HOST' => "www.rubyonrails.org"
- assert_equal %w( www ), request.subdomains
- assert_equal "www", request.subdomain
+ request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org'
+ assert_equal "rubyonrails.org", request.domain
request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk"
- assert_equal %w( www ), request.subdomains(2)
- assert_equal "www", request.subdomain(2)
-
- request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk"
- assert_equal %w( dev www ), request.subdomains(2)
- assert_equal "dev.www", request.subdomain(2)
+ assert_equal "rubyonrails.co.uk", request.domain(2)
- request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2
- assert_equal %w( dev www ), request.subdomains
- assert_equal "dev.www", request.subdomain
+ request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk", :tld_length => 2
+ assert_equal "rubyonrails.co.uk", request.domain
+ end
+ test "subdomains" do
request = stub_request 'HTTP_HOST' => "foobar.foobar.com"
assert_equal %w( foobar ), request.subdomains
assert_equal "foobar", request.subdomain
@@ -309,6 +297,22 @@ class RequestDomain < BaseRequestTest
request = stub_request 'HTTP_HOST' => nil
assert_equal [], request.subdomains
assert_equal "", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => "www.rubyonrails.org"
+ assert_equal %w( www ), request.subdomains
+ assert_equal "www", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk"
+ assert_equal %w( www ), request.subdomains(2)
+ assert_equal "www", request.subdomain(2)
+
+ request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk"
+ assert_equal %w( dev www ), request.subdomains(2)
+ assert_equal "dev.www", request.subdomain(2)
+
+ request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2
+ assert_equal %w( dev www ), request.subdomains
+ assert_equal "dev.www", request.subdomain
end
end
@@ -893,6 +897,27 @@ class RequestFormat < BaseRequestTest
ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header
end
end
+
+ test "format taken from the path extension" do
+ request = stub_request 'PATH_INFO' => '/foo.xml'
+ assert_called(request, :parameters, times: 1, returns: {}) do
+ assert_equal [Mime[:xml]], request.formats
+ end
+
+ request = stub_request 'PATH_INFO' => '/foo.123'
+ assert_called(request, :parameters, times: 1, returns: {}) do
+ assert_equal [Mime[:html]], request.formats
+ end
+ end
+
+ test "formats from accept headers have higher precedence than path extension" do
+ request = stub_request 'HTTP_ACCEPT' => 'application/json',
+ 'PATH_INFO' => '/foo.xml'
+
+ assert_called(request, :parameters, times: 1, returns: {}) do
+ assert_equal [Mime[:json]], request.formats
+ end
+ end
end
class RequestMimeType < BaseRequestTest
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 981d820ccf..43ee2efd9f 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -5,6 +5,7 @@ require 'rack/content_length'
class ResponseTest < ActiveSupport::TestCase
def setup
@response = ActionDispatch::Response.create
+ @response.request = ActionDispatch::Request.empty
end
def test_can_wait_until_commit
@@ -36,9 +37,31 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal "closed stream", e.message
end
+ def test_read_body_during_action
+ @response.body = "Hello, World!"
+
+ # even though there's no explicitly set content-type,
+ assert_equal nil, @response.content_type
+
+ # after the action reads back @response.body,
+ assert_equal "Hello, World!", @response.body
+
+ # the response can be built.
+ status, headers, body = @response.to_a
+ assert_equal 200, status
+ assert_equal({
+ "Content-Type" => "text/html; charset=utf-8"
+ }, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["Hello, World!"], parts
+ end
+
def test_response_body_encoding
body = ["hello".encode(Encoding::UTF_8)]
response = ActionDispatch::Response.new 200, {}, body
+ response.request = ActionDispatch::Request.empty
assert_equal Encoding::UTF_8, response.body.encoding
end
@@ -261,6 +284,7 @@ class ResponseTest < ActiveSupport::TestCase
test "can be explicitly destructured into status, headers and an enumerable body" do
response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
+ response.request = ActionDispatch::Request.empty
status, headers, body = *response
assert_equal 404, status
@@ -356,6 +380,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
resp.cache_control[:public] = true
resp.etag = '123'
resp.body = 'Hello'
+ resp.request = ActionDispatch::Request.empty
}.to_a
}
@@ -392,6 +417,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
resp.charset = 'utf-16'
resp.content_type = Mime[:xml]
resp.body = 'Hello'
+ resp.request = ActionDispatch::Request.empty
}.to_a
}
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index a17d07c40b..7382c267c7 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -7,6 +7,9 @@ class MountedRackApp
end
end
+class Rails::DummyController
+end
+
module ActionDispatch
module Routing
class RoutesInspectorTest < ActiveSupport::TestCase
@@ -331,6 +334,41 @@ module ActionDispatch
" cart GET /cart(.:format) cart#show"
], output
end
+
+ def test_routes_with_undefined_filter
+ output = draw(:filter => 'Rails::MissingController') do
+ get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+ end
+
+ assert_equal [
+ "The controller Rails::MissingController does not exist!",
+ "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ ], output
+ end
+
+ def test_no_routes_matched_filter
+ output = draw(:filter => 'rails/dummy') do
+ get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+ end
+
+ assert_equal [
+ "No routes were found for this controller",
+ "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ ], output
+ end
+
+ def test_no_routes_were_defined
+ output = draw(:filter => 'Rails::DummyController') { }
+
+ assert_equal [
+ "You don't have any routes defined!",
+ "",
+ "Please add some routes in config/routes.rb.",
+ "",
+ "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ ], output
+ end
+
end
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 8972f3e74d..62d65ec5c0 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3578,6 +3578,27 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'HEAD', @response.body
end
+ def test_passing_action_parameters_to_url_helpers_raises_error_if_parameters_are_not_permitted
+ draw do
+ root :to => 'projects#index'
+ end
+ params = ActionController::Parameters.new(id: '1')
+
+ assert_raises ArgumentError do
+ root_path(params)
+ end
+ end
+
+ def test_passing_action_parameters_to_url_helpers_is_allowed_if_parameters_are_permitted
+ draw do
+ root :to => 'projects#index'
+ end
+ params = ActionController::Parameters.new(id: '1')
+ params.permit!
+
+ assert_equal '/?id=1', root_path(params)
+ end
+
private
def draw(&block)
@@ -4592,3 +4613,44 @@ class TestDefaultUrlOptions < ActionDispatch::IntegrationTest
assert_equal '/en/posts/2014/12/13', archived_posts_path(2014, 12, 13)
end
end
+
+class TestErrorsInController < ActionDispatch::IntegrationTest
+ class ::PostsController < ActionController::Base
+ def foo
+ nil.i_do_not_exist
+ end
+
+ def bar
+ NonExistingClass.new
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ get '/:controller(/:action)'
+ end
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ def test_legit_no_method_errors_are_not_caught
+ get '/posts/foo'
+ assert_equal 500, response.status
+ end
+
+ def test_legit_name_errors_are_not_caught
+ get '/posts/bar'
+ assert_equal 500, response.status
+ end
+
+ def test_legit_routing_not_found_responses
+ get '/posts/baz'
+ assert_equal 404, response.status
+
+ get '/i_do_not_exist'
+ assert_equal 404, response.status
+ end
+end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index ffdf775836..14894d4b82 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -8,7 +8,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
case req.path
when "/not_found"
raise AbstractController::ActionNotFound
- when "/bad_params"
+ when "/bad_params", "/bad_params.json"
begin
raise StandardError.new
rescue
@@ -120,4 +120,18 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
assert_response 405
assert_equal "", body
end
+
+ test "bad params exception is returned in the correct format" do
+ @app = ProductionApp
+
+ get "/bad_params", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
+ assert_response 400
+ assert_match(/400 error/, body)
+
+ get "/bad_params.json", headers: { 'action_dispatch.show_exceptions' => true }
+ assert_equal "application/json; charset=utf-8", response.headers["Content-Type"]
+ assert_response 400
+ assert_equal("{\"status\":400,\"error\":\"Bad Request\"}", body)
+ end
end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index 7a5b8393dc..c66a0e6a7a 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -12,25 +12,31 @@ class SSLTest < ActionDispatch::IntegrationTest
end
class RedirectSSLTest < SSLTest
- def assert_not_redirected(url, headers: {})
- self.app = build_app
+
+ def assert_not_redirected(url, headers: {}, redirect: {}, deprecated_host: nil,
+ deprecated_port: nil)
+
+ self.app = build_app ssl_options: { redirect: redirect,
+ host: deprecated_host, port: deprecated_port
+ }
+
get url, headers: headers
assert_response :ok
end
- def assert_redirected(host: nil, port: nil, status: 301, body: [],
- deprecated_host: nil, deprecated_port: nil,
+ def assert_redirected(redirect: {}, deprecated_host: nil, deprecated_port: nil,
from: 'http://a/b?c=d', to: from.sub('http', 'https'))
- self.app = build_app ssl_options: {
- redirect: { host: host, port: port, status: status, body: body },
+ redirect = { status: 301, body: [] }.merge(redirect)
+
+ self.app = build_app ssl_options: { redirect: redirect,
host: deprecated_host, port: deprecated_port
}
get from
- assert_response status
+ assert_response redirect[:status] || 301
assert_redirected_to to
- assert_equal body.join, @response.body
+ assert_equal redirect[:body].join, @response.body
end
test 'https is not redirected' do
@@ -46,31 +52,31 @@ class RedirectSSLTest < SSLTest
end
test 'redirect with non-301 status' do
- assert_redirected status: 307
+ assert_redirected redirect: { status: 307 }
end
test 'redirect with custom body' do
- assert_redirected body: ['foo']
+ assert_redirected redirect: { body: ['foo'] }
end
test 'redirect to specific host' do
- assert_redirected host: 'ssl', to: 'https://ssl/b?c=d'
+ assert_redirected redirect: { host: 'ssl' }, to: 'https://ssl/b?c=d'
end
test 'redirect to default port' do
- assert_redirected port: 443
+ assert_redirected redirect: { port: 443 }
end
test 'redirect to non-default port' do
- assert_redirected port: 8443, to: 'https://a:8443/b?c=d'
+ assert_redirected redirect: { port: 8443 }, to: 'https://a:8443/b?c=d'
end
test 'redirect to different host and non-default port' do
- assert_redirected host: 'ssl', port: 8443, to: 'https://ssl:8443/b?c=d'
+ assert_redirected redirect: { host: 'ssl', port: 8443 }, to: 'https://ssl:8443/b?c=d'
end
test 'redirect to different host including port' do
- assert_redirected host: 'ssl:443', to: 'https://ssl:443/b?c=d'
+ assert_redirected redirect: { host: 'ssl:443' }, to: 'https://ssl:443/b?c=d'
end
test ':host is deprecated, moved within redirect: { host: … }' do
@@ -84,6 +90,10 @@ class RedirectSSLTest < SSLTest
assert_redirected deprecated_port: 1, to: 'https://a:1/b?c=d'
end
end
+
+ test 'no redirect with redirect set to false' do
+ assert_not_redirected 'http://example.org', redirect: false
+ end
end
class StrictTransportSecurityTest < SSLTest
@@ -187,6 +197,11 @@ class SecureCookiesTest < SSLTest
assert_cookies 'problem=def; path=/; Secure; HttpOnly'
end
+ def test_cookies_as_not_secure_with_secure_cookies_disabled
+ get headers: { 'Set-Cookie' => DEFAULT }, ssl_options: { secure_cookies: false }
+ assert_cookies(*DEFAULT.split("\n"))
+ end
+
def test_no_cookies
get
assert_nil response.headers['Set-Cookie']
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 1da57ab50b..ea8b5e904e 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -40,6 +40,10 @@ module StaticTests
assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding('ASCII-8BIT')).body
end
+ def test_handles_urls_with_null_byte
+ assert_equal "Hello, World!", get("/doorkeeper%00").body
+ end
+
def test_sets_cache_control
app = assert_deprecated do
ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index fd4ede4d1b..8c9782bb90 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -129,6 +129,13 @@ module TestUrlGeneration
)
end
+ test "generating URLS with empty querystring" do
+ assert_equal "/bars.json", bars_path(
+ a: {},
+ format: 'json'
+ )
+ end
+
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 0981d47d7a..98ac2c1c22 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,38 @@
+* Create a new `ActiveSupport::SafeBuffer` instance when `content_for` is flushed.
+
+ Fixes #19890
+
+ *Yoong Kang Lim*
+
+* Fix `collection_radio_buttons` hidden_field name and make it appear
+ before the actual input radio tags to make the real value override
+ the hidden when passed.
+
+ Fixes #22773.
+
+ *Santiago Pastorino*
+
+* `ActionView::TestCase::Controller#params` returns an instance of
+ `ActionController::Parameters`.
+
+ *Justin Coyne*
+
+* Fix regression in `submit_tag` when a symbol is used as label argument.
+
+ *Yuuji Yaginuma*
+
+
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
+* `I18n.translate` helper will wrap the missing translation keys
+ in a <span> tag only if `debug_missing_translation` configuration
+ be true. Default value is `true`. For example in `application.rb`:
+
+ # in order to turn off missing key wrapping
+ config.action_view.debug_missing_translation = false
+
+ *Sameer Rahmani*
+
* Respect value of `:object` if `:object` is false when rendering.
Fixes #22260.
@@ -35,16 +70,16 @@
*Bernerd Schaefer*
-* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option
- to handle placement of delimiter, to support currency formats like INR
-
- Example:
-
+* `number_to_currency` and `number_with_delimiter` now accept a custom `delimiter_pattern` option
+ to handle placement of delimiter, to support currency formats like INR.
+
+ Example:
+
number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n")
- # => '₹ 12,30,000.00'
-
+ # => '₹ 12,30,000.00'
+
*Vipul A M*
-
+
* Make `disable_with` the default behavior for submit tags. Disables the
button on submit to prevent double submits.
@@ -86,7 +121,7 @@
Which could happen if the rendering was done directly in the controller
and not in a template.
- Fixes #20535
+ Fixes #20535.
*Roque Pinel*
@@ -95,7 +130,7 @@
*Dov Murik*
-* Raise an ArgumentError when a false value for `include_blank` is passed to a
+* Raise an `ArgumentError` when a false value for `include_blank` is passed to a
required select field (to comply with the HTML5 spec).
*Grey Baker*
@@ -113,7 +148,7 @@
* `translate` should handle `raise` flag correctly in case of both main and default
translation is missing.
- Fixes #19967
+ Fixes #19967.
*Bernard Potocki*
@@ -135,7 +170,7 @@
* `translate` should accept nils as members of the `:default`
parameter without raising a translation missing error.
- Fixes #19419
+ Fixes #19419.
*Justin Coyne*
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/README.rdoc b/actionview/README.rdoc
index 8b1f85f748..7a3e5e31d0 100644
--- a/actionview/README.rdoc
+++ b/actionview/README.rdoc
@@ -9,7 +9,7 @@ used to inline short Ruby snippets inside HTML), and XML Builder.
The latest version of Action View can be installed with RubyGems:
- % gem install actionview
+ $ gem install actionview
Source code can be downloaded as part of the Rails project on GitHub
@@ -36,4 +36,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index c3bbac27fd..0a87500a52 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb
index ba24510e56..bc61920848 100644
--- a/actionview/lib/action_view/flows.rb
+++ b/actionview/lib/action_view/flows.rb
@@ -15,7 +15,7 @@ module ActionView
# Called by each renderer object to set the layout contents.
def set(key, value)
- @content[key] = value
+ @content[key] = ActiveSupport::SafeBuffer.new(value)
end
# Called by content_for
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 4f45f5b8c8..20d408741e 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -8,7 +8,7 @@ module ActionView
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 91e934cd64..cc54faa778 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -136,7 +136,7 @@ module ActionView
tag(
"link",
"rel" => tag_options[:rel] || "alternate",
- "type" => tag_options[:type] || Mime[type].to_s,
+ "type" => tag_options[:type] || Template::Types[type].to_s,
"title" => tag_options[:title] || type.to_s.upcase,
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
)
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index bb1cdd0f8d..dba70e284e 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -51,7 +51,7 @@ module ActionView
# * <tt>:language</tt>: Defaults to "en-US".
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
- # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
# 2005 is used (as an "I don't care" value).
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index e473aeaea9..18b2102d73 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -216,14 +216,6 @@ module ActionView
end
end
- # Given a key (as described in ActionController::Caching::Fragments.expire_fragment),
- # returns a key suitable for use in reading, writing, or expiring a
- # cached fragment. All keys are prefixed with <tt>views/</tt> and uses
- # ActiveSupport::Cache.expand_cache_key for the expansion.
- def fragment_cache_key(key)
- ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
- end
-
private
def fragment_name_with_digest(name, virtual_path) #:nodoc:
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 93c7cba395..df8d0affd0 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -9,8 +9,8 @@ module ActionView
# It provides a method to capture blocks into variables through capture and
# a way to capture a block of markup for use in a layout through content_for.
module CaptureHelper
- # The capture method allows you to extract part of a template into a
- # variable. You can then use this variable anywhere in your templates or layout.
+ # The capture method extracts part of a template as a String object.
+ # You can then use this object anywhere in your templates, layout, or helpers.
#
# The capture method can be used in ERB templates...
#
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 2a367b85af..b43d99ebb7 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1922,6 +1922,8 @@ module ActionView
@object_name.to_s.humanize
end
+ model = model.downcase
+
defaults = []
defaults << :"helpers.submit.#{object_name}.#{key}"
defaults << :"helpers.submit.#{key}"
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 0191064326..d521553481 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -447,7 +447,7 @@ module ActionView
unless tag_options["data-disable-with"] == false || (tag_options["data"] && tag_options["data"][:disable_with] == false)
disable_with_text = tag_options["data-disable-with"]
disable_with_text ||= tag_options["data"][:disable_with] if tag_options["data"]
- disable_with_text ||= value.clone
+ disable_with_text ||= value.to_s.clone
tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
else
tag_options["data"].delete(:disable_with) if tag_options["data"]
@@ -870,10 +870,16 @@ module ActionView
''
when /^post$/i, "", nil
html_options["method"] = "post"
- token_tag(authenticity_token)
+ token_tag(authenticity_token, form_options: {
+ action: html_options["action"],
+ method: "post"
+ })
else
html_options["method"] = "post"
- method_tag(method) + token_tag(authenticity_token)
+ method_tag(method) + token_tag(authenticity_token, form_options: {
+ action: html_options["action"],
+ method: method
+ })
end
if html_options.delete("enforce_utf8") { true }
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index d7182d1fac..161aa031c6 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -63,6 +63,14 @@ module ActionView
# Formats a +number+ into a currency string (e.g., $13.65). You
# can customize the format in the +options+ hash.
#
+ # The currency unit and number formatting of the current locale will be used
+ # unless otherwise specified in the provided options. No currency conversion
+ # is performed. If the user is given a way to change their locale, they will
+ # also be able to change the relative value of the currency displayed with
+ # this helper. If your application will ever support multiple locales, you
+ # may want to specify a constant <tt>:locale</tt> option or consider
+ # using a library capable of currency conversion.
+ #
# ==== Options
#
# * <tt>:locale</tt> - Sets the locale to be used for formatting
@@ -261,6 +269,8 @@ module ActionView
# number_to_human_size(1234567) # => 1.18 MB
# number_to_human_size(1234567890) # => 1.15 GB
# number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567890123456) # => 1.1 PB
+ # number_to_human_size(1234567890123456789) # => 1.07 EB
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 3256d44e18..3dda47a458 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -23,6 +23,10 @@ module ActionView
def render_component(builder)
builder.check_box + builder.label
end
+
+ def hidden_field_name #:nodoc:
+ "#{super}[]"
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index b87b4281d6..1d3b1ecf0b 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -97,16 +97,20 @@ module ActionView
# Append a hidden field to make sure something will be sent back to the
# server if all radio buttons are unchecked.
if options.fetch('include_hidden', true)
- rendered_collection + hidden_field
+ hidden_field + rendered_collection
else
rendered_collection
end
end
def hidden_field #:nodoc:
- hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]"
+ hidden_name = @html_options[:name] || hidden_field_name
@template_object.hidden_field_tag(hidden_name, "", id: nil)
end
+
+ def hidden_field_name #:nodoc:
+ "#{tag_name(false, @options[:index])}"
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 432693bc23..58ce042f12 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -204,12 +204,12 @@ module ActionView
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
# +plural+ is supplied, it will use that when count is > 1, otherwise
- # it will use the Inflector to determine the plural form.
+ # it will use the Inflector to determine the plural form for the given locale,
+ # which defaults to I18n.locale
#
- # If passed an optional +locale:+ parameter, the word will be pluralized
- # using rules defined for that language (you must define your own
- # inflection rules for languages other than English). See
- # ActiveSupport::Inflector.pluralize
+ # The word will be pluralized using rules defined for the locale
+ # (you must define your own inflection rules for languages other than English).
+ # See ActiveSupport::Inflector.pluralize
#
# pluralize(1, 'person')
# # => 1 person
@@ -217,7 +217,7 @@ module ActionView
# pluralize(2, 'person')
# # => 2 people
#
- # pluralize(3, 'person', 'users')
+ # pluralize(3, 'person', plural: 'users')
# # => 3 users
#
# pluralize(0, 'person')
@@ -225,7 +225,14 @@ module ActionView
#
# pluralize(2, 'Person', locale: :de)
# # => 2 Personen
- def pluralize(count, singular, plural = nil, locale: nil)
+ def pluralize(count, singular, deprecated_plural = nil, plural: nil, locale: I18n.locale)
+ if deprecated_plural
+ ActiveSupport::Deprecation.warn("Passing plural as a positional argument " \
+ "is deprecated and will be removed in Rails 5.1. Use e.g. " \
+ "pluralize(1, 'person', plural: 'people') instead.")
+ plural ||= deprecated_plural
+ end
+
word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular
else
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 4c4d2c4457..152e1b1211 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -6,7 +6,15 @@ module ActionView
# = Action View Translation Helpers
module Helpers
module TranslationHelper
+ extend ActiveSupport::Concern
+
include TagHelper
+
+ included do
+ mattr_accessor :debug_missing_translation
+ self.debug_missing_translation = true
+ end
+
# Delegates to <tt>I18n#translate</tt> but also performs three additional
# functions.
#
@@ -95,6 +103,8 @@ module ActionView
title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ')
end
+ return title unless ActionView::Base.debug_missing_translation
+
content_tag('span', keys.last.to_s.titleize, class: 'translation_missing', title: title)
end
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index baebc34b4b..3a4561a083 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -311,7 +311,11 @@ module ActionView
form_options[:action] = url
form_options[:'data-remote'] = true if remote
- request_token_tag = form_method == 'post' ? token_tag : ''
+ request_token_tag = if form_method == 'post'
+ token_tag(nil, form_options: form_options)
+ else
+ ''
+ end
html_options = convert_options_to_data_attributes(options, html_options)
html_options['type'] = 'submit'
@@ -579,9 +583,9 @@ module ActionView
html_options["data-method"] = method
end
- def token_tag(token=nil)
+ def token_tag(token=nil, form_options: {})
if token != false && protect_against_forgery?
- token ||= form_authenticity_token
+ token ||= form_authenticity_token(form_options: form_options)
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
else
''
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index d3935788ef..63a3c4ea5e 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -67,7 +67,7 @@ module ActionView
def self.get(details)
if details[:formats]
details = details.dup
- details[:formats] &= Mime::SET.symbols
+ details[:formats] &= Template::Types.symbols
end
@details_keys[details] ||= new
end
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index e829d86c99..59d869d92d 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -6,6 +6,7 @@ module ActionView
class Railtie < Rails::Railtie # :nodoc:
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.embed_authenticity_token_in_remote_forms = false
+ config.action_view.debug_missing_translation = true
config.eager_load_namespaces << ActionView
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index 4b44eb5520..4a2547b0fb 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -5,24 +5,25 @@ module ActionView
# RecordIdentifier encapsulates methods used by various ActionView helpers
# to associate records with DOM elements.
#
- # Consider for example the following code that displays the body of a post:
+ # Consider for example the following code that form of post:
#
- # <%= div_for(post) do %>
- # <%= post.body %>
+ # <%= form_for(post) do |f| %>
+ # <%= f.text_field :body %>
# <% end %>
#
# When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML
# is:
#
- # <div id="new_post" class="post">
- # </div>
+ # <form class="new_post" id="new_post" action="/posts" accept-charset="UTF-8" method="post">
+ # <input type="text" name="post[body]" id="post_body" />
+ # </form>
#
# When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
# is:
#
- # <div id="post_42" class="post">
- # What a wonderful world!
- # </div>
+ # <form class="edit_post" id="edit_post_42" action="/posts/42" accept-charset="UTF-8" method="post">
+ # <input type="text" value="What a wonderful world!" name="post[body]" id="post_body" />
+ # </form>
#
# In both cases, the +id+ and +class+ of the wrapping DOM element are
# automatically generated, following naming conventions encapsulated by the
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 8604637da2..3ca7f9d220 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -84,7 +84,7 @@ module ActionView
end
def rendered_format
- Mime[lookup_context.rendered_format]
+ Template::Types[lookup_context.rendered_format]
end
private
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index 0105e88a49..ad4c353608 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -2,13 +2,15 @@ module ActionView #:nodoc:
# = Action View Template Handlers
class Template
module Handlers #:nodoc:
+ autoload :Raw, 'action_view/template/handlers/raw'
autoload :ERB, 'action_view/template/handlers/erb'
+ autoload :Html, 'action_view/template/handlers/html'
autoload :Builder, 'action_view/template/handlers/builder'
- autoload :Raw, 'action_view/template/handlers/raw'
def self.extended(base)
base.register_default_template_handler :raw, Raw.new
base.register_template_handler :erb, ERB.new
+ base.register_template_handler :html, Html.new
base.register_template_handler :builder, Builder.new
base.register_template_handler :ruby, :source.to_proc
end
diff --git a/actionview/lib/action_view/template/handlers/html.rb b/actionview/lib/action_view/template/handlers/html.rb
new file mode 100644
index 0000000000..ccaa8d1469
--- /dev/null
+++ b/actionview/lib/action_view/template/handlers/html.rb
@@ -0,0 +1,9 @@
+module ActionView
+ module Template::Handlers
+ class Html < Raw
+ def call(template)
+ "ActionView::OutputBuffer.new #{super}"
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb
index b08fb0870f..760f517431 100644
--- a/actionview/lib/action_view/template/handlers/raw.rb
+++ b/actionview/lib/action_view/template/handlers/raw.rb
@@ -2,9 +2,7 @@ module ActionView
module Template::Handlers
class Raw
def call(template)
- escaped = template.source.gsub(':'.freeze, '\:'.freeze)
-
- '%q:' + escaped + ':;'
+ "#{template.source.inspect};"
end
end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 7859c58b43..6ddd2b66b3 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -3,7 +3,7 @@ require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
require "action_view/template"
require "thread"
-require "concurrent"
+require "concurrent/map"
module ActionView
# = Action View Resolver
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index be45fcf742..c233d06ccb 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -5,19 +5,12 @@ module ActionView
class Template
class Types
class Type
- cattr_accessor :types
- self.types = Set.new
-
- def self.register(*t)
- types.merge(t.map(&:to_s))
- end
-
- register :html, :text, :js, :css, :xml, :json
+ SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
def self.[](type)
- return type if type.is_a?(self)
-
- if type.is_a?(Symbol) || types.member?(type.to_s)
+ if type.is_a?(self)
+ type
+ else
new(type)
end
end
@@ -28,16 +21,18 @@ module ActionView
@symbol = symbol.to_sym
end
- delegate :to_s, :to_sym, :to => :symbol
+ def to_s
+ @symbol.to_s
+ end
alias to_str to_s
def ref
- to_sym || to_s
+ @symbol
end
+ alias to_sym ref
def ==(type)
- return false if type.blank?
- symbol.to_sym == type.to_sym
+ @symbol == type.to_sym unless type.blank?
end
end
@@ -52,6 +47,10 @@ module ActionView
def self.[](type)
type_klass[type]
end
+
+ def self.symbols
+ type_klass::SET.symbols
+ end
end
end
end
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index f6b5696a13..120962b5aa 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -28,7 +28,7 @@ module ActionView
@response = ActionDispatch::TestResponse.new
@request.env.delete('PATH_INFO')
- @params = {}
+ @params = ActionController::Parameters.new
end
end
diff --git a/actionview/test/fixtures/layouts/render_partial_html.erb b/actionview/test/fixtures/layouts/render_partial_html.erb
new file mode 100644
index 0000000000..d4dbb6c76c
--- /dev/null
+++ b/actionview/test/fixtures/layouts/render_partial_html.erb
@@ -0,0 +1,2 @@
+<%= render :partial => 'test/partialhtml' %>
+<%= yield %>
diff --git a/actionview/test/fixtures/test/_partialhtml.html b/actionview/test/fixtures/test/_partialhtml.html
new file mode 100644
index 0000000000..afe39b730a
--- /dev/null
+++ b/actionview/test/fixtures/test/_partialhtml.html
@@ -0,0 +1 @@
+<h1>partial html</h1> \ No newline at end of file
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index 65c68fc34a..a122fe17c9 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -31,6 +31,27 @@ end
class GoodCustomer < Customer
end
+class TicketType < Struct.new(:name)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ extend ActiveModel::Translation
+
+ def initialize(*args)
+ super
+ @persisted = false
+ end
+
+ def persisted=(boolean)
+ @persisted = boolean
+ end
+
+ def persisted?
+ @persisted
+ end
+
+ attr_accessor :name
+end
+
class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index fe40010528..8592a2a083 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -27,6 +27,8 @@ class AssetTagHelperTest < ActionView::TestCase
end
AssetPathToTag = {
+ %(asset_path("")) => %(),
+ %(asset_path(" ")) => %(),
%(asset_path("foo")) => %(/foo),
%(asset_path("style.css")) => %(/style.css),
%(asset_path("xmlhr.js")) => %(/xmlhr.js),
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 1e099d482c..7e6761e580 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -148,6 +148,19 @@ class CaptureHelperTest < ActionView::TestCase
assert ! content_for?(:something_else)
end
+ def test_content_for_should_be_html_safe_after_flush_empty
+ assert ! content_for?(:title)
+ content_for :title do
+ content_tag(:p, 'title')
+ end
+ assert content_for(:title).html_safe?
+ content_for :title, "", flush: true
+ content_for(:title) do
+ content_tag(:p, 'title')
+ end
+ assert content_for(:title).html_safe?
+ end
+
def test_provide
assert !content_for?(:title)
provide :title, "hi"
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index c4234a71c3..92e77599f4 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -390,11 +390,6 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), {}, :class => 'selector', :accesskey => 'M')
- #result = select_month(Time.mktime(2003, 8, 16), {}, :class => 'selector', :accesskey => 'M')
- #assert result.include?('<select id="date_month" name="date[month]"')
- #assert result.include?('class="selector"')
- #assert result.include?('accesskey="M"')
- #assert result.include?('<option value="1">January')
end
def test_select_month_with_default_prompt
@@ -474,11 +469,6 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005}, :class => 'selector', :accesskey => 'M')
- #result = select_year(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005}, :class => 'selector', :accesskey => 'M')
- #assert result.include?('<select id="date_year" name="date[year]"')
- #assert result.include?('class="selector"')
- #assert result.include?('accesskey="M"')
- #assert result.include?('<option value="2003"')
end
def test_select_year_with_default_prompt
@@ -639,12 +629,6 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M')
-
- #result = select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M')
- #assert result.include?('<select id="date_minute" name="date[minute]"')
- #assert result.include?('class="selector"')
- #assert result.include?('accesskey="M"')
- #assert result.include?('<option value="00">00')
end
def test_select_minute_with_default_prompt
@@ -709,12 +693,6 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M')
-
- #result = select_second(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M')
- #assert result.include?('<select id="date_second" name="date[second]"')
- #assert result.include?('class="selector"')
- #assert result.include?('accesskey="M"')
- #assert result.include?('<option value="00">00')
end
def test_select_second_with_default_prompt
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index b59be8e36c..4f7ea88169 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -202,35 +202,35 @@ class FormCollectionsHelperTest < ActionView::TestCase
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name
- assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 1
+ assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 1
end
test 'collection radio buttons generates a hidden field using the given :name in :html_options' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
- with_collection_radio_buttons :user, :category_ids, collection, :id, :name, {}, { name: "user[other_category_ids][]" }
+ with_collection_radio_buttons :user, :category_ids, collection, :id, :name, {}, { name: "user[other_category_ids]" }
- assert_select "input[type=hidden][name='user[other_category_ids][]'][value='']", count: 1
+ assert_select "input[type=hidden][name='user[other_category_ids]'][value='']", count: 1
end
test 'collection radio buttons generates a hidden field with index if it was provided' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, { index: 322 }
- assert_select "input[type=hidden][name='user[322][category_ids][]'][value='']", count: 1
+ assert_select "input[type=hidden][name='user[322][category_ids]'][value='']", count: 1
end
test 'collection radio buttons does not generate a hidden field if include_hidden option is false' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, include_hidden: false
- assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0
+ assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0
end
test 'collection radio buttons does not generate a hidden field if include_hidden option is false with key as string' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, 'include_hidden' => false
- assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0
+ assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0
end
# COLLECTION CHECK BOXES
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 1f7ff3ca7c..1be1c68c14 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -128,6 +128,8 @@ class FormHelperTest < ActionView::TestCase
@post_delegator.title = 'Hello World'
@car = Car.new("#000FFF")
+
+ @ticket_type = TicketType.new
end
Routes = ActionDispatch::Routing::RouteSet.new
@@ -136,6 +138,8 @@ class FormHelperTest < ActionView::TestCase
resources :comments
end
+ resources :ticket_types
+
namespace :admin do
resources :posts do
resources :comments
@@ -1600,11 +1604,11 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"<label for='post_active_true'>true</label>" +
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
- "<label for='post_active_false'>false</label>" +
- "<input type='hidden' name='post[active][]' value='' />"
+ "<label for='post_active_false'>false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1622,13 +1626,13 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<label for='post_active_true'>"+
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"true</label>" +
"<label for='post_active_false'>"+
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
- "false</label>" +
- "<input type='hidden' name='post[active][]' value='' />"
+ "false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1648,13 +1652,13 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post_1", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<label for='post_active_true'>"+
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"true</label>" +
"<label for='post_active_false'>"+
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
"false</label>"+
- "<input type='hidden' name='post[active][]' value='' />" +
"<input id='post_id' name='post[id]' type='hidden' value='1' />"
end
@@ -1670,11 +1674,11 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" +
"<label for='foo_post_active_true'>true</label>" +
"<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" +
- "<label for='foo_post_active_false'>false</label>" +
- "<input type='hidden' name='post[active][]' value='' />"
+ "<label for='foo_post_active_false'>false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1689,11 +1693,11 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input type='hidden' name='post[1][active]' value='' />" +
"<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" +
"<label for='post_1_active_true'>true</label>" +
"<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" +
- "<label for='post_1_active_false'>false</label>" +
- "<input type='hidden' name='post[1][active][]' value='' />"
+ "<label for='post_1_active_false'>false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1708,13 +1712,13 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
"<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"<label for='post_tag_ids_1'>Tag 1</label>" +
"<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
"<label for='post_tag_ids_2'>Tag 2</label>" +
"<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
- "<label for='post_tag_ids_3'>Tag 3</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "<label for='post_tag_ids_3'>Tag 3</label>"
end
assert_dom_equal expected, output_buffer
@@ -1732,6 +1736,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
"<label for='post_tag_ids_1'>" +
"<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"Tag 1</label>" +
@@ -1740,8 +1745,7 @@ class FormHelperTest < ActionView::TestCase
"Tag 2</label>" +
"<label for='post_tag_ids_3'>" +
"<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
- "Tag 3</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "Tag 3</label>"
end
assert_dom_equal expected, output_buffer
@@ -1762,6 +1766,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post_1", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />"+
"<label for='post_tag_ids_1'>" +
"<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"Tag 1</label>" +
@@ -1771,7 +1776,6 @@ class FormHelperTest < ActionView::TestCase
"<label for='post_tag_ids_3'>" +
"<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
"Tag 3</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"+
"<input id='post_id' name='post[id]' type='hidden' value='1' />"
end
@@ -1788,9 +1792,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
"<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "<label for='foo_post_tag_ids_1'>Tag 1</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "<label for='foo_post_tag_ids_1'>Tag 1</label>"
end
assert_dom_equal expected, output_buffer
@@ -1806,9 +1810,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input name='post[1][tag_ids][]' type='hidden' value='' />" +
"<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
- "<label for='post_1_tag_ids_1'>Tag 1</label>" +
- "<input name='post[1][tag_ids][]' type='hidden' value='' />"
+ "<label for='post_1_tag_ids_1'>Tag 1</label>"
end
assert_dom_equal expected, output_buffer
@@ -1872,6 +1876,20 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_lowercase_model_name_default_submit_button_value
+ form_for(@ticket_type) do |f|
+ concat f.submit
+ end
+
+ expected =
+ '<form class="new_ticket_type" id="new_ticket_type" action="/ticket_types" accept-charset="UTF-8" method="post">' +
+ hidden_fields +
+ '<input type="submit" name="commit" value="Create ticket type" data-disable-with="Create ticket type" />' +
+ '</form>'
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_symbol_object_name
form_for(@post, as: "other_name", html: { id: "create-post" }) do |f|
concat f.label(:title, class: 'post_title')
@@ -2239,7 +2257,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts', 'new_post', 'new_post') do
- "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />"
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
assert_dom_equal expected, output_buffer
@@ -2254,7 +2272,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
- "<input name='commit' data-disable-with='Confirm Post changes' type='submit' value='Confirm Post changes' />"
+ "<input name='commit' data-disable-with='Confirm post changes' type='submit' value='Confirm post changes' />"
end
assert_dom_equal expected, output_buffer
@@ -2282,7 +2300,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do
- "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />"
+ "<input name='commit' data-disable-with='Update your post' type='submit' value='Update your post' />"
end
assert_dom_equal expected, output_buffer
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index de1eb89dc5..359ecbc637 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -510,6 +510,13 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_submit_tag_with_symbol_value
+ assert_dom_equal(
+ %(<input data-disable-with="Save" name='commit' type="submit" value="Save" />),
+ submit_tag(:Save)
+ )
+ end
+
def test_button_tag
assert_dom_equal(
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 84aca222b2..b63c315a33 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -9,6 +9,10 @@ module RenderTestCases
@assigns = { :secret => 'in the sauce' }
@view = Class.new(ActionView::Base) do
def view_cache_dependencies; end
+
+ def fragment_cache_key(key)
+ ActiveSupport::Cache.expand_cache_key(key, :views)
+ end
end.new(paths, @assigns)
@controller_view = TestController.new.view_context
@@ -67,7 +71,7 @@ module RenderTestCases
e = assert_raise ActionView::Template::Error do
@view.render(:template => "with_format", :formats => [:json])
end
- assert_includes(e.message, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:raw, :erb, :builder, :ruby]}.")
+ assert_includes(e.message, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby]}.")
end
def test_render_file_with_locale
@@ -463,6 +467,11 @@ module RenderTestCases
@view.render(:file => "test/hello_world", :layout => "layouts/yield_with_render_partial_inside")
end
+ def test_render_partial_with_html_only_extension
+ assert_equal %(<h1>partial html</h1>\nHello world!\n),
+ @view.render(:file => "test/hello_world", :layout => "layouts/render_partial_html")
+ end
+
def test_render_layout_with_block_and_yield
assert_equal %(Content from block!\n),
@view.render(:layout => "layouts/yield_only") { "Content from block!" }
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index b057d43ee0..d69d5819b6 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -42,6 +42,10 @@ module ActionView
assert_same view, view
end
+ test "exposes params" do
+ assert params.is_a? ActionController::Parameters
+ end
+
test "exposes view as _view for backwards compatibility" do
assert_same _view, view
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index fae1965ffa..fb98ac6330 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -379,24 +379,36 @@ class TextHelperTest < ActionView::TestCase
assert_equal("1.25 counts", pluralize('1.25', "count"))
assert_equal("1.0 count", pluralize('1.0', "count"))
assert_equal("1.00 count", pluralize('1.00', "count"))
- assert_equal("2 counters", pluralize(2, "count", "counters"))
- assert_equal("0 counters", pluralize(nil, "count", "counters"))
+ assert_equal("2 counters", pluralize(2, "count", plural: "counters"))
+ assert_equal("0 counters", pluralize(nil, "count", plural: "counters"))
assert_equal("2 people", pluralize(2, "person"))
assert_equal("10 buffaloes", pluralize(10, "buffalo"))
assert_equal("1 berry", pluralize(1, "berry"))
assert_equal("12 berries", pluralize(12, "berry"))
end
- def test_pluralization_with_locale
- ActiveSupport::Inflector.inflections(:de) do |inflect|
- inflect.plural(/(person)$/i, '\1en')
- inflect.singular(/(person)en$/i, '\1')
- end
+ def test_localized_pluralization
+ old_locale = I18n.locale
+
+ begin
+ I18n.locale = :de
+
+ ActiveSupport::Inflector.inflections(:de) do |inflect|
+ inflect.irregular 'region', 'regionen'
+ end
- assert_equal("2 People", pluralize(2, "Person", locale: :en))
- assert_equal("2 Personen", pluralize(2, "Person", locale: :de))
+ assert_equal("1 region", pluralize(1, "region"))
+ assert_equal("2 regionen", pluralize(2, "region"))
+ assert_equal("2 regions", pluralize(2, "region", locale: :en))
+ ensure
+ I18n.locale = old_locale
+ end
+ end
- ActiveSupport::Inflector.inflections(:de).clear
+ def test_deprecated_plural_as_positional_argument
+ assert_deprecated do
+ pluralize(2, 'count', 'counters')
+ end
end
def test_cycle_class
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 631bceadd8..38b9284767 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -54,6 +54,16 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
end
+ def test_returns_missing_translation_message_without_span_wrap
+ old_value = ActionView::Base.debug_missing_translation
+ ActionView::Base.debug_missing_translation = false
+
+ expected = 'translation missing: en.translations.missing'
+ assert_equal expected, translate(:"translations.missing")
+ ensure
+ ActionView::Base.debug_missing_translation = old_value
+ end
+
def test_returns_missing_translation_message_wrapped_into_span
expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>'
assert_equal expected, translate(:"translations.missing")
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 48d0a9a47a..89cabb8f6b 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -38,6 +38,10 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal "/?a=b&c=d", url_for(hash_for(a: :b, c: :d))
end
+ def test_url_for_does_not_include_empty_hashes
+ assert_equal "/", url_for(hash_for(a: {}))
+ end
+
def test_url_for_with_back
referer = 'http://www.example.com/referer'
@controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer))
@@ -578,7 +582,7 @@ class UrlHelperTest < ActiveSupport::TestCase
self.request_forgery
end
- def form_authenticity_token
+ def form_authenticity_token(*args)
"secret"
end
@@ -632,10 +636,6 @@ class UrlHelperControllerTest < ActionController::TestCase
render inline: "<%= url_for controller: 'url_helper_controller_test/url_helper', action: 'show_url_for' %>"
end
- def show_overridden_url_for
- render inline: "<%= url_for params.merge(controller: 'url_helper_controller_test/url_helper', action: 'show_url_for') %>"
- end
-
def show_named_route
render inline: "<%= show_named_route_#{params[:kind]} %>"
end
@@ -669,11 +669,6 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
- 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
-
def test_named_route_url_shows_host_and_path
get :show_named_route, params: { kind: 'url' }
assert_equal 'http://test.host/url_helper_controller_test/url_helper/show_named_route',
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 79235019fe..8687af5eba 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,5 @@
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
* Fixed serializing `:at` option for `assert_enqueued_with`
and `assert_performed_with`.
@@ -26,7 +28,7 @@
*Jean Boussier*
-* Include I18n.locale into job serialization/deserialization and use it around
+* Include `I18n.locale` into job serialization/deserialization and use it around
`perform`.
Fixes #20799.
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
index 0cef8cdda0..a3ffb46b3f 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2015 David Heinemeier Hansson
+Copyright (c) 2014-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/README.md b/activejob/README.md
index d9ff561695..7268186c00 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -102,7 +102,7 @@ see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.
The latest version of Active Job can be installed with RubyGems:
```
- % gem install activejob
+ $ gem install activejob
```
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index 24e38e495f..bc1671b508 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -19,5 +19,5 @@ Gem::Specification.new do |s|
s.require_path = 'lib'
s.add_dependency 'activesupport', version
- s.add_dependency 'globalid', '>= 0.3.0'
+ s.add_dependency 'globalid', '>= 0.3.6'
end
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index eb8091a805..f7e05f7cd3 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2014-2015 David Heinemeier Hansson
+# Copyright (c) 2014-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index e56bc79328..33bd5b4eb3 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -25,7 +25,7 @@ module ActiveJob
# Raised when an unsupported argument type is set as a job argument. We
# currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
- # Bignum and objects that can be represented as GlobalIDs (ex: Active Record).
+ # Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record).
# Raised if you set the key for a Hash something else than a string or
# a symbol. Also raised when trying to serialize an object which can't be
# identified with a Global ID - such as an unpersisted Active Record model.
@@ -34,7 +34,7 @@ module ActiveJob
module Arguments
extend self
# :nodoc:
- TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
+ TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum, BigDecimal ]
# Serializes a set of arguments. Whitelisted types are returned
# as-is. Arrays/Hashes are serialized element by element.
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 27a5de93f4..35356405b5 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveJob
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index 933972a52b..eb8ad185aa 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -10,7 +10,7 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
[ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
- 'a', true, false,
+ 'a', true, false, BigDecimal.new(5),
[ 1, 'a' ],
{ 'a' => 1 }
].each do |arg|
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index a3368cd197..e4769d2f40 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,5 @@
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
* Validate multiple contexts on `valid?` and `invalid?` at once.
Example:
@@ -125,5 +127,6 @@
`ActiveSupport.halt_callback_chains_on_return_false` option, will
either not work at all or display a deprecation warning.
+ *claudiob*
Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 20414c1d61..77f5761993 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -235,7 +235,7 @@ behavior out of the box:
The latest version of Active Model can be installed with RubyGems:
- % gem install activemodel
+ $ gem install activemodel
Source code can be downloaded as part of the Rails project on GitHub
@@ -262,4 +262,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 8d00b3aa27..53206580f0 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -19,6 +19,4 @@ Gem::Specification.new do |s|
s.require_path = 'lib'
s.add_dependency 'activesupport', version
-
- s.add_dependency 'builder', '~> 3.1'
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 4e1b3f7495..b95c174d6e 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 4726a68f69..ef6141a51d 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -81,6 +81,18 @@ module ActiveModel
super
end
+ # Copies the errors from <tt>other</tt>.
+ #
+ # other - The ActiveModel::Errors instance.
+ #
+ # Examples
+ #
+ # person.errors.copy!(other)
+ def copy!(other) # :nodoc:
+ @messages = other.messages.dup
+ @details = other.details.dup
+ end
+
# Clear the error messages.
#
# person.errors.full_messages # => ["name cannot be nil"]
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index 762f4fe939..d74a3912d6 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveModel
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb
index 7101bad566..34e09f0aba 100644
--- a/activemodel/lib/active_model/type/time.rb
+++ b/activemodel/lib/active_model/type/time.rb
@@ -29,12 +29,16 @@ module ActiveModel
return value unless value.is_a?(::String)
return if value.empty?
- dummy_time_value = "2000-01-01 #{value}"
+ if value =~ /^2000-01-01/
+ dummy_time_value = value
+ else
+ dummy_time_value = "2000-01-01 #{value}"
+ end
fast_string_to_time(dummy_time_value) || begin
time_hash = ::Date._parse(dummy_time_value)
return if time_hash[:hour].nil?
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
end
end
end
diff --git a/activemodel/lib/active_model/type/value.rb b/activemodel/lib/active_model/type/value.rb
index 5fea0561a6..0d2d6873a8 100644
--- a/activemodel/lib/active_model/type/value.rb
+++ b/activemodel/lib/active_model/type/value.rb
@@ -84,12 +84,21 @@ module ActiveModel
false
end
+ def map(value) # :nodoc:
+ yield value
+ end
+
def ==(other)
self.class == other.class &&
precision == other.precision &&
scale == other.scale &&
limit == other.limit
end
+ alias eql? ==
+
+ def hash
+ [self.class, precision, scale, limit].hash
+ end
def assert_valid_value(*)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 9c1e8b4ba7..ad7012df48 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -39,6 +39,10 @@ module ActiveModel
return
end
+ unless raw_value.is_a?(Numeric)
+ value = parse_raw_value_as_a_number(raw_value)
+ end
+
options.slice(*CHECKS.keys).each do |option, option_value|
case option
when :odd, :even
@@ -63,12 +67,15 @@ module ActiveModel
protected
def is_number?(raw_value)
- parsed_value = Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
- !parsed_value.nil?
+ !parse_raw_value_as_a_number(raw_value).nil?
rescue ArgumentError, TypeError
false
end
+ def parse_raw_value_as_a_number(raw_value)
+ Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
+ end
+
def is_integer?(raw_value)
/\A[+-]?\d+\z/ === raw_value.to_s
end
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index 85455c112c..e4ecc0adb4 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -28,7 +28,7 @@ class CallbacksTest < ActiveModel::TestCase
false
end
- after_create "@callbacks << :final_callback"
+ ActiveSupport::Deprecation.silence { after_create "@callbacks << :final_callback" }
def initialize(options = {})
@callbacks = []
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index f6d171bec6..a5ac055033 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -410,4 +410,14 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.clear
assert person.errors.details.empty?
end
+
+ test "copy errors" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ person = Person.new
+ person.errors.copy!(errors)
+
+ assert_equal [:name], person.errors.messages.keys
+ assert_equal [:name], person.errors.details.keys
+ end
end
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index cedc812ec7..2c89388f14 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -88,6 +88,11 @@ class ActiveModelI18nTests < ActiveModel::TestCase
assert_equal 'child model', Child.model_name.human
end
+ def test_translated_model_with_namespace
+ I18n.backend.store_translations 'en', activemodel: { models: { 'person/gender': 'gender model' } }
+ assert_equal 'gender model', Person::Gender.model_name.human
+ end
+
def test_translated_model_names_with_ancestors_fallback
I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } }
assert_equal 'person model', Child.model_name.human
diff --git a/activemodel/test/cases/types_test.rb b/activemodel/test/cases/types_test.rb
index f937208580..558c56f157 100644
--- a/activemodel/test/cases/types_test.rb
+++ b/activemodel/test/cases/types_test.rb
@@ -64,6 +64,9 @@ module ActiveModel
time_string = Time.now.utc.strftime("%T")
assert_equal time_string, type.cast(time_string).strftime("%T")
+
+ assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast('2015-06-13T19:45:54+03:00')
+ assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast('06:07:08+09:00')
end
def test_type_cast_datetime_and_timestamp
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 04ec74bad3..74a048537d 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -79,6 +79,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([97.18, 98, BigDecimal.new('98')]) # Notice the 97.18 as a float is greater than 97.18 as a BigDecimal due to floating point precision
end
+ def test_validates_numericality_with_greater_than_using_string_value
+ Topic.validates_numericality_of :approved, greater_than: 10
+
+ invalid!(['-10', '9', '9.9', '10'], 'must be greater than 10')
+ valid!(['10.1', '11'])
+ end
+
def test_validates_numericality_with_greater_than_or_equal
Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10
@@ -93,6 +100,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([97.18, 98, BigDecimal.new('97.19')])
end
+ def test_validates_numericality_with_greater_than_or_equal_using_string_value
+ Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10
+
+ invalid!(['-10', '9', '9.9'], 'must be greater than or equal to 10')
+ valid!(['10', '10.1', '11'])
+ end
+
def test_validates_numericality_with_equal_to
Topic.validates_numericality_of :approved, equal_to: 10
@@ -107,6 +121,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([BigDecimal.new('97.18')])
end
+ def test_validates_numericality_with_equal_to_using_string_value
+ Topic.validates_numericality_of :approved, equal_to: 10
+
+ invalid!(['-10', '9', '9.9', '10.1', '11'], 'must be equal to 10')
+ valid!(['10'])
+ end
+
def test_validates_numericality_with_less_than
Topic.validates_numericality_of :approved, less_than: 10
@@ -121,6 +142,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([-97.0, 97.0, -97, 97, BigDecimal.new('-97'), BigDecimal.new('97')])
end
+ def test_validates_numericality_with_less_than_using_string_value
+ Topic.validates_numericality_of :approved, less_than: 10
+
+ invalid!(['10', '10.1', '11'], 'must be less than 10')
+ valid!(['-10', '9', '9.9'])
+ end
+
def test_validates_numericality_with_less_than_or_equal_to
Topic.validates_numericality_of :approved, less_than_or_equal_to: 10
@@ -135,6 +163,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([-97.18, BigDecimal.new('-97.18'), BigDecimal.new('97.18')])
end
+ def test_validates_numericality_with_less_than_or_equal_using_string_value
+ Topic.validates_numericality_of :approved, less_than_or_equal_to: 10
+
+ invalid!(['10.1', '11'], 'must be less than or equal to 10')
+ valid!(['-10', '9', '9.9', '10'])
+ end
+
def test_validates_numericality_with_odd
Topic.validates_numericality_of :approved, odd: true
@@ -163,6 +198,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([-1, 42])
end
+ def test_validates_numericality_with_other_than_using_string_value
+ Topic.validates_numericality_of :approved, other_than: 0
+
+ invalid!(['0', '0.0'])
+ valid!(['-1', '1.1', '42'])
+ end
+
def test_validates_numericality_with_proc
Topic.send(:define_method, :min_approved, lambda { 5 })
Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new(&:min_approved)
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 03c7943308..c73580138d 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -101,6 +101,7 @@ class ValidatesWithTest < ActiveModel::TestCase
validator.expect(:new, validator, [{foo: :bar, if: "1 == 1", class: Topic}])
validator.expect(:validate, nil, [topic])
validator.expect(:is_a?, false, [Symbol])
+ validator.expect(:is_a?, false, [String])
Topic.validates_with(validator, if: "1 == 1", foo: :bar)
assert topic.valid?
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 3724b1a387..e14a9a972c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,162 @@
+* Add expression support on the schema default.
+
+ Example:
+
+ create_table :posts do |t|
+ t.datetime :published_at, default: -> { 'NOW()' }
+ end
+
+ *Ryuta Kamizono*
+
+* Fix regression when loading fixture files with symbol keys.
+
+ Fixes #22584.
+
+ *Yves Senn*
+
+* Use `version` column as primary key for schema_migrations table because
+ `schema_migrations` versions are guaranteed to be unique.
+
+ This makes it possible to use `update_attributes` on models that do
+ not have a primary key.
+
+ *Richard Schneeman*
+
+* Add short-hand methods for text and blob types in MySQL.
+
+ In Pg and Sqlite3, `:text` and `:binary` have variable unlimited length.
+ But in MySQL, these have limited length for each types (ref #21591, #21619).
+ This change adds short-hand methods for each text and blob types.
+
+ Example:
+
+ create_table :foos do |t|
+ t.tinyblob :tiny_blob
+ t.mediumblob :medium_blob
+ t.longblob :long_blob
+ t.tinytext :tiny_text
+ t.mediumtext :medium_text
+ t.longtext :long_text
+ end
+
+ *Ryuta Kamizono*
+
+* Take into account UTC offset when assigning string representation of
+ timestamp with offset specified to attribute of time type.
+
+ *Andrey Novikov*
+
+* When calling `first` with a `limit` argument, return directly from the
+ `loaded?` records if available.
+
+ *Ben Woosley*
+
+* Deprecate sending the `offset` argument to `find_nth`. Please use the
+ `offset` method on relation instead.
+
+ *Ben Woosley*
+
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
+* Order the result of `find(ids)` to match the passed array, if the relation
+ has no explicit order defined.
+
+ Fixes #20338.
+
+ *Miguel Grazziotin*, *Matthew Draper*
+
+* Omit default limit values in dumped schema. It's tidier, and if the defaults
+ change in the future, we can address that via Migration API Versioning.
+
+ *Jean Boussier*
+
+* Support passing the schema name as a prefix to table name in
+ `ConnectionAdapters::SchemaStatements#indexes`. Previously the prefix would
+ be considered a full part of the index name, and only the schema in the
+ current search path would be considered.
+
+ *Grey Baker*
+
+* Ignore index name in `index_exists?` and `remove_index` when not passed a
+ name to check for.
+
+ *Grey Baker*
+
+* Extract support for the legacy `mysql` database adapter from core. It will
+ live on in a separate gem for now, but most users should just use `mysql2`.
+
+ *Abdelkader Boudih*
+
+* ApplicationRecord is a new superclass for all app models, analogous to app
+ controllers subclassing ApplicationController instead of
+ ActionController::Base. This gives apps a single spot to configure app-wide
+ model behavior.
+
+ Newly generated applications have `app/models/application_record.rb`
+ present by default.
+
+ *Genadi Samokovarov*
+
+* Version the API presented to migration classes, so we can change parameter
+ defaults without breaking existing migrations, or forcing them to be
+ rewritten through a deprecation cycle.
+
+ New migrations specify the Rails version they were written for:
+
+ class AddStatusToOrders < ActiveRecord::Migration[5.0]
+ def change
+ # ...
+ end
+ end
+
+ *Matthew Draper*, *Ravil Bayramgalin*
+
+* Use bind params for `limit` and `offset`. This will generate significantly
+ fewer prepared statements for common tasks like pagination. To support this
+ change, passing a string containing a comma to `limit` has been deprecated,
+ and passing an Arel node to `limit` is no longer supported.
+
+ Fixes #22250.
+
+ *Sean Griffin*
+
+* Introduce after_{create,update,delete}_commit callbacks.
+
+ Before:
+
+ after_commit :add_to_index_later, on: :create
+ after_commit :update_in_index_later, on: :update
+ after_commit :remove_from_index_later, on: :destroy
+
+ After:
+
+ after_create_commit :add_to_index_later
+ after_update_commit :update_in_index_later
+ after_destroy_commit :remove_from_index_later
+
+ Fixes #22515.
+
+ *Genadi Samokovarov*
+
+* Respect the column default values for `inheritance_column` when
+ instantiating records through the base class.
+
+ Fixes #17121.
+
+ Example:
+
+ # The schema of BaseModel has `t.string :type, default: 'SubType'`
+ subtype = BaseModel.new
+ assert_equals SubType, subtype.class
+
+ *Kuldeep Aggarwal*
+
+* Fix `rake db:structure:dump` on Postgres when multiple schemas are used.
+
+ Fixes #22346.
+
+ *Nick Muerdter*, *ckoenig*
+
* Add schema dumping support for PostgreSQL geometric data types.
*Ryuta Kamizono*
@@ -211,15 +370,15 @@
Example:
- config.generators do |g|
- g.orm :active_record, primary_key_type: :uuid
- end
+ config.generators do |g|
+ g.orm :active_record, primary_key_type: :uuid
+ end
*Jon McCartie*
* Don't cache arguments in `#find_by` if they are an `ActiveRecord::Relation`.
- Fixes #20817
+ Fixes #20817.
*Hiroaki Izu*
@@ -252,7 +411,7 @@
*Sean Griffin*
-* Give `AcriveRecord::Relation#update` its own deprecation warning when
+* Give `ActiveRecord::Relation#update` its own deprecation warning when
passed an `ActiveRecord::Base` instance.
Fixes #21945.
@@ -289,10 +448,10 @@
To load the fixtures file `accounts.yml` as the `User` model, use:
- _fixture:
- model_class: User
- david:
- name: David
+ _fixture:
+ model_class: User
+ david:
+ name: David
Fixes #9516.
@@ -348,9 +507,9 @@
* Ensure `select` quotes aliased attributes, even when using `from`.
- Fixes #21488
+ Fixes #21488.
- *Sean Griffin & @johanlunds*
+ *Sean Griffin*, *@johanlunds*
* MySQL: support `unsigned` numeric data types.
@@ -413,6 +572,13 @@
*Wojciech Wnętrzak*
+* Instantiating an AR model with `ActionController::Parameters` now raises
+ an `ActiveModel::ForbiddenAttributesError` if the parameters include a
+ `type` field that has not been explicitly permitted. Previously, the
+ `type` field was simply ignored in the same situation.
+
+ *Prem Sichanugrist*
+
* PostgreSQL, `create_schema`, `drop_schema` and `rename_table` now quote
schema names.
@@ -669,7 +835,7 @@
* Include the `Enumerable` module in `ActiveRecord::Relation`
- *Sean Griffin & bogdan*
+ *Sean Griffin*, *bogdan*
* Use `Enumerable#sum` in `ActiveRecord::Relation` if a block is given.
@@ -705,7 +871,7 @@
Fixes #20515.
- *Sean Griffin & jmondo*
+ *Sean Griffin*, *jmondo*
* Deprecate the PostgreSQL `:point` type in favor of a new one which will return
`Point` objects instead of an `Array`
@@ -1095,13 +1261,16 @@
*Sean Griffin*
* `scoping` no longer pollutes the current scope of sibling classes when using
- STI. e.x.
+ STI.
+
+ Fixes #18806.
+
+ Example:
StiOne.none.scoping do
StiTwo.all
end
- Fixes #18806.
*Sean Griffin*
@@ -1142,7 +1311,7 @@
* Use `SCHEMA` instead of `DB_STRUCTURE` for specifying a structure file.
- This makes the db:structure tasks consistent with test:load_structure.
+ This makes the `db:structure` tasks consistent with `test:load_structure`.
*Dieter Komendera*
@@ -1178,7 +1347,7 @@
Fixes #17621.
- *Eileen M. Uchitelle, Aaron Patterson*
+ *Eileen M. Uchitelle*, *Aaron Patterson*
* Fix n+1 query problem when eager loading nil associations (fixes #18312)
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 7c2197229d..1f496cf280 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 3eac8cc422..cfbee4d6f7 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -125,7 +125,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
)
{Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for
- MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html],
+ MySQL[link:classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html],
PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
@@ -138,7 +138,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
* Database agnostic schema management with Migrations.
- class AddSystemSettings < ActiveRecord::Migration
+ class AddSystemSettings < ActiveRecord::Migration[5.0]
def up
create_table :system_settings do |t|
t.string :name
@@ -188,7 +188,7 @@ Admit the Database:
The latest version of Active Record can be installed with RubyGems:
- % gem install activerecord
+ $ gem install activerecord
Source code can be downloaded as part of the Rails project on GitHub:
@@ -215,4 +215,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index bae40604b1..a74fcf2df7 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -20,7 +20,6 @@ example:
Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
- $ bundle exec rake test:mysql
$ bundle exec rake test:mysql2
$ bundle exec rake test:postgresql
$ bundle exec rake test:sqlite3
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index c93099a921..0564dca94a 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -17,14 +17,14 @@ def run_without_aborting(*tasks)
abort "Errors running #{errors.join(', ')}" if errors.any?
end
-desc 'Run mysql, mysql2, sqlite, and postgresql tests by default'
+desc 'Run mysql2, sqlite, and postgresql tests by default'
task :default => :test
-desc 'Run mysql, mysql2, sqlite, and postgresql tests'
+desc 'Run mysql2, sqlite, and postgresql tests'
task :test do
tasks = defined?(JRUBY_VERSION) ?
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
- %w(test_mysql test_mysql2 test_sqlite3 test_postgresql)
+ %w(test_mysql2 test_sqlite3 test_postgresql)
run_without_aborting(*tasks)
end
@@ -32,7 +32,7 @@ namespace :test do
task :isolated do
tasks = defined?(JRUBY_VERSION) ?
%w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
- %w(isolated_test_mysql isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
+ %w(isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
run_without_aborting(*tasks)
end
end
@@ -43,7 +43,7 @@ namespace :db do
task :drop => ['db:mysql:drop', 'db:postgresql:drop']
end
-%w( mysql mysql2 postgresql sqlite3 sqlite3_mem db2 oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
+%w( mysql2 postgresql sqlite3 sqlite3_mem db2 oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
namespace :test do
Rake::TestTask.new(adapter => "#{adapter}:env") { |t|
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
@@ -87,14 +87,14 @@ namespace :db do
namespace :mysql do
desc 'Build the MySQL test databases'
task :build do
- config = ARTest.config['connections']['mysql']
+ config = ARTest.config['connections']['mysql2']
%x( mysql --user=#{config['arunit']['username']} --password=#{config['arunit']['password']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
%x( mysql --user=#{config['arunit2']['username']} --password=#{config['arunit2']['password']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
end
desc 'Drop the MySQL test databases'
task :drop do
- config = ARTest.config['connections']['mysql']
+ config = ARTest.config['connections']['mysql2']
%x( mysqladmin --user=#{config['arunit']['username']} --password=#{config['arunit']['password']} -f drop #{config['arunit']['database']} )
%x( mysqladmin --user=#{config['arunit2']['username']} --password=#{config['arunit2']['password']} -f drop #{config['arunit2']['database']} )
end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index bd95b57303..4405da2812 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '7.0.0.alpha'
+ s.add_dependency 'arel', '~> 7.0'
end
diff --git a/activerecord/bin/test b/activerecord/bin/test
index f8adf2aabc..7417b068bf 100755
--- a/activerecord/bin/test
+++ b/activerecord/bin/test
@@ -6,7 +6,7 @@ module Minitest
opts.separator ""
opts.separator "Active Record options:"
opts.on("-a", "--adapter [ADAPTER]",
- "Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql, mysql2, postgresql)") do |adapter|
+ "Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql2, postgresql)") do |adapter|
ENV["ARCONN"] = adapter.strip
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 264f869c68..ab3846ae65 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -40,6 +40,7 @@ module ActiveRecord
autoload :CounterCache
autoload :DynamicMatchers
autoload :Enum
+ autoload :InternalMetadata
autoload :Explain
autoload :Inheritance
autoload :Integration
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index b806a2f832..f6d8e8a342 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1164,6 +1164,7 @@ module ActiveRecord
# 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
# parent object, unless the parent object is a new record.
+ # This will also run validations and callbacks of associated object(s).
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
@@ -1181,7 +1182,8 @@ module ActiveRecord
# [collection=objects]
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
- # direct.
+ # direct by default. You can specify <tt>dependent: :destroy</tt> or
+ # <tt>dependent: :nullify</tt> to override this.
# [collection_singular_ids]
# Returns an array of the associated objects' ids
# [collection_singular_ids=ids]
@@ -1639,7 +1641,7 @@ module ActiveRecord
# The join table should not have a primary key or a model associated with it. You must manually generate the
# join table with a migration such as this:
#
- # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0]
# def change
# create_join_table :developers, :projects
# end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 81c535d962..f02d146e89 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -106,8 +106,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
touch = reflection.options[:touch]
callback = lambda { |record|
- touch_method = touching_delayed_records? ? :touch : :touch_later
- BelongsTo.touch_record(record, foreign_key, n, touch, touch_method)
+ BelongsTo.touch_record(record, foreign_key, n, touch, belongs_to_touch_method)
}
model.after_save callback, if: :changed?
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index f32dddb8f0..473b80a658 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -414,12 +414,16 @@ module ActiveRecord
def replace_on_target(record, index, skip_callbacks)
callback(:before_add, record) unless skip_callbacks
+
+ was_loaded = loaded?
yield(record) if block_given?
- if index
- @target[index] = record
- else
- @target << record
+ unless !was_loaded && loaded?
+ if index
+ @target[index] = record
+ else
+ @target << record
+ end
end
callback(:after_add, record) unless skip_callbacks
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 a6ad09a38a..be65cf318c 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -74,9 +74,8 @@ module ActiveRecord
value = foreign_klass.base_class.name
column = klass.columns_hash[reflection.type.to_s]
- substitute = klass.connection.substitute_at(column)
binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
- constraint = constraint.and table[reflection.type].eq substitute
+ constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new)
end
joins << table.create_join(table, table.create_on(constraint), join_type)
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 24aa47bb51..6c83058202 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -18,7 +18,8 @@ module ActiveRecord
through_records = owners.map do |owner|
association = owner.association through_reflection.name
- [owner, Array(association.reader)]
+ center = target_records_from_association(association)
+ [owner, Array(center)]
end
reset_association owners, through_reflection.name
@@ -49,7 +50,7 @@ module ActiveRecord
rhs_records = middles.flat_map { |r|
association = r.association source_reflection.name
- association.reader
+ target_records_from_association(association)
}.compact
rhs_records.sort_by { |rhs| record_offset[rhs] }
@@ -91,6 +92,10 @@ module ActiveRecord
scope
end
+
+ def target_records_from_association(association)
+ association.loaded? ? association.target : association.reader
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 4ae585d3f5..423a93964e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -191,6 +191,18 @@ module ActiveRecord
end
end
+ # Returns true if the given attribute exists, otherwise false.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # Person.has_attribute?('name') # => true
+ # Person.has_attribute?(:age) # => true
+ # Person.has_attribute?(:nothing) # => false
+ def has_attribute?(attr_name)
+ attribute_types.key?(attr_name.to_s)
+ end
+
# Returns the column object for the named attribute.
# Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
# named attribute does not exist.
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 9e693b6aee..061628725d 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/strip'
+
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
@@ -7,9 +9,9 @@ module ActiveRecord
end
def cast(value)
- if value.is_a?(Array)
- value.map { |v| cast(v) }
- elsif value.is_a?(Hash)
+ return if value.nil?
+
+ if value.is_a?(Hash)
set_time_zone_without_conversion(super)
elsif value.respond_to?(:in_time_zone)
begin
@@ -17,18 +19,22 @@ module ActiveRecord
rescue ArgumentError
nil
end
+ else
+ map(super) { |t| cast(t) }
end
end
private
def convert_time_to_time_zone(value)
- if value.is_a?(Array)
- value.map { |v| convert_time_to_time_zone(v) }
- elsif value.acts_like?(:time)
+ return if value.nil?
+
+ if value.acts_like?(:time)
value.in_time_zone
- else
+ elsif value.is_a?(::Float)
value
+ else
+ map(value) { |v| convert_time_to_time_zone(v) }
end
end
@@ -77,7 +83,7 @@ module ActiveRecord
!result &&
cast_type.type == :time &&
time_zone_aware_types.include?(:not_explicitly_configured)
- ActiveSupport::Deprecation.warn(<<-MESSAGE)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
Time columns will become time zone aware in Rails 5.1. This
still causes `String`s to be parsed as if they were in `Time.zone`,
and `Time`s to be converted to `Time.zone`.
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index fc12c3f45a..bac5a38a5d 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -331,15 +331,30 @@ module ActiveRecord
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
+ indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
+
record.errors.each do |attribute, message|
- if index.nil? || (!reflection.options[:index_errors] && !ActiveRecord::Base.index_nested_attribute_errors)
- attribute = "#{reflection.name}.#{attribute}"
- else
+ if indexed_attribute
attribute = "#{reflection.name}[#{index}].#{attribute}"
+ else
+ attribute = "#{reflection.name}.#{attribute}"
end
errors[attribute] << message
errors[attribute].uniq!
end
+
+ record.errors.details.each_key do |attribute|
+ if indexed_attribute
+ reflection_attribute = "#{reflection.name}[#{index}].#{attribute}"
+ else
+ reflection_attribute = "#{reflection.name}.#{attribute}"
+ end
+
+ record.errors.details[attribute].each do |error|
+ errors.details[reflection_attribute] << error
+ errors.details[reflection_attribute].uniq!
+ end
+ end
else
errors.add(reflection.name)
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 9782e58299..4a31a1aa84 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -261,7 +261,7 @@ module ActiveRecord #:nodoc:
# The +errors+ property of this exception contains an array of
# AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
- # * RecordInvalid - raised by {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] and
+ # * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
# when the record is invalid.
# * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method.
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index cfd8cbda67..854f9776a3 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -175,21 +175,6 @@ module ActiveRecord
# end
# end
#
- # The callback macros usually accept a symbol for the method they're supposed to run, but you can also
- # pass a "method string", which will then be evaluated within the binding of the callback. Example:
- #
- # class Topic < ActiveRecord::Base
- # before_destroy 'self.class.delete_all "parent_id = #{id}"'
- # end
- #
- # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
- # is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
- #
- # class Topic < ActiveRecord::Base
- # before_destroy 'self.class.delete_all "parent_id = #{id}"',
- # 'puts "Evaluated after parents are destroyed"'
- # end
- #
# == <tt>before_validation*</tt> returning statements
#
# If the +before_validation+ callback throws +:abort+, the process will be
@@ -208,12 +193,12 @@ module ActiveRecord
#
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
# callback (+log_children+ in this case) should be executed before the children get destroyed by the
- # <tt>dependent: destroy</tt> option.
+ # <tt>dependent: :destroy</tt> option.
#
# Let's look at the code below:
#
# class Topic < ActiveRecord::Base
- # has_many :children, dependent: destroy
+ # has_many :children, dependent: :destroy
#
# before_destroy :log_children
#
@@ -228,7 +213,7 @@ module ActiveRecord
# You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
#
# class Topic < ActiveRecord::Base
- # has_many :children, dependent: destroy
+ # has_many :children, dependent: :destroy
#
# before_destroy :log_children, prepend: true
#
@@ -238,7 +223,7 @@ module ActiveRecord
# end
# end
#
- # This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
+ # This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
#
# == \Transactions
#
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 486b7b6d25..ccd2899489 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -197,7 +197,7 @@ module ActiveRecord
elapsed = Time.now - t0
if elapsed >= timeout
- msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
+ msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
[timeout, elapsed]
raise ConnectionTimeoutError, msg
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 848aeb821c..7a2a1a0e33 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -58,8 +58,8 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
- def select_values(arel, name = nil)
- arel, binds = binds_from_relation arel, []
+ def select_values(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
select_rows(to_sql(arel, binds), name, binds).map(&:first)
end
@@ -106,7 +106,7 @@ module ActiveRecord
exec_query(sql, name, binds)
end
- # Returns the last auto-generated ID from the affected table.
+ # Executes an INSERT query and returns the new record's ID
#
# +id_value+ will be returned unless the value is nil, in
# which case the database will attempt to calculate the last inserted
@@ -115,20 +115,24 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
- value = exec_insert(sql, name, binds, pk, sequence_name)
+ sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
+ value = exec_insert(sql, name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end
+ alias create insert
+ alias insert_sql insert
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
exec_update(to_sql(arel, binds), name, binds)
end
+ alias update_sql update
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
exec_delete(to_sql(arel, binds), name, binds)
end
+ alias delete_sql delete
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
@@ -208,7 +212,7 @@ module ActiveRecord
# * You are joining an existing open transaction
# * You are creating a nested (savepoint) transaction
#
- # The mysql, mysql2 and postgresql adapters support setting the transaction
+ # The 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.
@@ -296,14 +300,15 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- columns = schema_cache.columns_hash(table_name)
+ fixture = fixture.stringify_keys
+ columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
if column = columns[name]
type = lookup_cast_type_from_column(column)
Relation::QueryAttribute.new(name, value, type)
else
- raise Fixture::FixtureError, %(table "#{table_name}" has no column named "#{name}".)
+ raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
end
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
@@ -344,18 +349,12 @@ module ActiveRecord
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
# on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
# an UPDATE statement, so in the MySQL adapters we redefine this to do that.
- def join_to_update(update, select) #:nodoc:
- key = update.key
+ def join_to_update(update, select, key) # :nodoc:
subselect = subquery_for(key, select)
update.where key.in(subselect)
end
-
- def join_to_delete(delete, select, key) #:nodoc:
- subselect = subquery_for(key, select)
-
- delete.where key.in(subselect)
- end
+ alias join_to_delete join_to_update
protected
@@ -375,24 +374,8 @@ module ActiveRecord
exec_query(sql, name, binds, prepare: true)
end
- # Returns the last auto-generated ID from the affected table.
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- execute(sql, name)
- id_value
- end
-
- # Executes the update statement and returns the number of rows affected.
- def update_sql(sql, name = nil)
- execute(sql, name)
- end
-
- # Executes the delete statement and returns the number of rows affected.
- def delete_sql(sql, name = nil)
- update_sql(sql, name)
- end
-
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
- [sql, binds]
+ [sql, binds, pk, sequence_name]
end
def last_inserted_id(result)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 9ec0a67c8f..7e3760d34b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -93,7 +93,7 @@ module ActiveRecord
# Override to return the quoted table name for assignment. Defaults to
# table quoting.
#
- # This works for mysql and mysql2 where table.column can be used to
+ # This works for mysql2 where table.column can be used to
# resolve ambiguity.
#
# We override this in the sqlite3 and postgresql adapters to use only
@@ -102,9 +102,13 @@ module ActiveRecord
quote_table_name("#{table}.#{attr}")
end
- def quote_default_expression(value, column) #:nodoc:
- value = lookup_cast_type(column.sql_type).serialize(value)
- quote(value)
+ def quote_default_expression(value, column) # :nodoc:
+ if value.is_a?(Proc)
+ value.call
+ else
+ value = lookup_cast_type(column.sql_type).serialize(value)
+ quote(value)
+ end
end
def quoted_true
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 159cbcb85a..1cda23dc1d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -190,7 +190,7 @@ module ActiveRecord
# Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
# is actually of this type:
#
- # class SomeMigration < ActiveRecord::Migration
+ # class SomeMigration < ActiveRecord::Migration[5.0]
# def up
# create_table :foo do |t|
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index e252ddb4cf..a95109fdae 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -61,8 +61,8 @@ module ActiveRecord
end
def schema_limit(column)
- limit = column.limit || native_database_types[column.type][:limit]
- limit.inspect if limit
+ limit = column.limit
+ limit.inspect if limit && limit != native_database_types[column.type][:limit]
end
def schema_precision(column)
@@ -76,11 +76,17 @@ module ActiveRecord
def schema_default(column)
type = lookup_cast_type_from_column(column)
default = type.deserialize(column.default)
- unless default.nil?
+ if default.nil?
+ schema_expression(column)
+ else
type.type_cast_for_schema(default)
end
end
+ def schema_expression(column)
+ "-> { #{column.default_function.inspect} }" if column.default_function
+ end
+
def schema_collation(column)
column.collation.inspect if column.collation
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index b50d28862c..70868ebd03 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -82,11 +82,10 @@ module ActiveRecord
#
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name).map(&:to_s)
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
checks = []
- checks << lambda { |i| i.name == index_name }
checks << lambda { |i| i.columns == column_names }
checks << lambda { |i| i.unique } if options[:unique]
+ checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
end
@@ -700,15 +699,15 @@ module ActiveRecord
# Removes the given index from the table.
#
- # Removes the +index_accounts_on_column+ in the +accounts+ table.
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
#
# remove_index :accounts, :branch_id
#
- # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
#
# remove_index :accounts, column: :branch_id
#
- # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table.
+ # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
#
# remove_index :accounts, column: [:branch_id, :party_id]
#
@@ -969,6 +968,10 @@ module ActiveRecord
ActiveRecord::SchemaMigration.create_table
end
+ def initialize_internal_metadata_table
+ ActiveRecord::InternalMetadata.create_table
+ end
+
def assume_migrated_upto_version(version, migrations_paths)
migrations_paths = Array(migrations_paths)
version = version.to_i
@@ -1029,11 +1032,12 @@ module ActiveRecord
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
+ # PostgreSQL, MySQL, 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:
+ #
+ def columns_for_distinct(columns, orders) # :nodoc:
columns
end
@@ -1127,21 +1131,35 @@ module ActiveRecord
end
def index_name_for_remove(table_name, options = {})
- index_name = index_name(table_name, options)
+ # if the adapter doesn't support the indexes call the best we can do
+ # is return the default index name for the options provided
+ return index_name(table_name, options) unless respond_to?(:indexes)
- unless index_name_exists?(table_name, index_name, true)
- if options.is_a?(Hash) && options.has_key?(:name)
- options_without_column = options.dup
- options_without_column.delete :column
- index_name_without_column = index_name(table_name, options_without_column)
+ checks = []
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
- end
+ if options.is_a?(Hash)
+ checks << lambda { |i| i.name == options[:name].to_s } if options.has_key?(:name)
+ column_names = Array(options[:column]).map(&:to_s)
+ else
+ column_names = Array(options).map(&:to_s)
+ end
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
+ if column_names.any?
+ checks << lambda { |i| i.columns.join('_and_') == column_names.join('_and_') }
end
- index_name
+ raise ArgumentError "No name or columns specified" if checks.none?
+
+ matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } }
+
+ if matching_indexes.count > 1
+ raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
+ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
+ elsif matching_indexes.none?
+ raise ArgumentError, "No indexes found on #{table_name} with the options provided."
+ else
+ matching_indexes.first.name
+ end
end
def rename_table_indexes(table_name, new_name)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 295a7bed87..14d04a6388 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -33,6 +33,7 @@ module ActiveRecord
class NullTransaction #:nodoc:
def initialize; end
+ def state; end
def closed?; true; end
def open?; false; end
def joinable?; false; end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4d4dc07b04..d9b42d4283 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -23,6 +23,7 @@ module ActiveRecord
autoload :TableDefinition
autoload :Table
autoload :AlterTable
+ autoload :ReferenceDefinition
end
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
@@ -95,14 +96,15 @@ module ActiveRecord
attr_reader :prepared_statements
- def initialize(connection, logger = nil, pool = nil) #:nodoc:
+ def initialize(connection, logger = nil, config = {}) # :nodoc:
super()
@connection = connection
@owner = nil
@instrumenter = ActiveSupport::Notifications.instrumenter
@logger = logger
- @pool = pool
+ @config = config
+ @pool = nil
@schema_cache = SchemaCache.new self
@visitor = nil
@prepared_statements = false
@@ -309,12 +311,6 @@ module ActiveRecord
{}
end
- # Returns a bind substitution value given a bind +column+
- # NOTE: The column param is currently being used by the sqlserver-adapter
- def substitute_at(column, _unused = 0)
- Arel::Nodes::BindParam.new
- end
-
# REFERENTIAL INTEGRITY ====================================
# Override to turn off referential integrity while executing <tt>&block</tt>.
@@ -375,7 +371,7 @@ module ActiveRecord
end
# Provides access to the underlying database driver for this adapter. For
- # example, this method returns a Mysql object in case of MysqlAdapter,
+ # example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
# and a PGconn object in case of PostgreSQLAdapter.
#
# This is useful for when you need to call a proprietary method such as
@@ -390,19 +386,17 @@ module ActiveRecord
def release_savepoint(name = nil)
end
- def case_sensitive_modifier(node, table_attribute)
- node
- end
-
def case_sensitive_comparison(table, attribute, column, value)
- table_attr = table[attribute]
- value = case_sensitive_modifier(value, table_attr) unless value.nil?
- table_attr.eq(value)
+ if value.nil?
+ table[attribute].eq(value)
+ else
+ table[attribute].eq(Arel::Nodes::BindParam.new)
+ end
end
def case_insensitive_comparison(table, attribute, column, value)
if can_perform_case_insensitive_comparison_for?(column)
- table[attribute].lower.eq(table.lower(value))
+ table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
else
case_sensitive_comparison(table, attribute, column, value)
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 735bc0e67a..3e84786be0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,7 +1,9 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/mysql/column'
require 'active_record/connection_adapters/mysql/schema_creation'
require 'active_record/connection_adapters/mysql/schema_definitions'
require 'active_record/connection_adapters/mysql/schema_dumper'
+require 'active_record/connection_adapters/mysql/type_metadata'
require 'active_support/core_ext/string/strip'
@@ -19,99 +21,13 @@ module ActiveRecord
MySQL::SchemaCreation.new(self)
end
- class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
-
- def initialize(*)
- super
- assert_valid_default(default)
- extract_default
- end
-
- def extract_default
- if blob_or_text_column?
- @default = null || strict ? nil : ''
- elsif missing_default_forged_as_empty_string?(default)
- @default = nil
- end
- end
-
- def has_default?
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
- super
- end
-
- def blob_or_text_column?
- sql_type =~ /blob/i || type == :text
- end
-
- def unsigned?
- /unsigned/ === sql_type
- end
-
- def case_sensitive?
- collation && !collation.match(/_ci$/)
- end
-
- def auto_increment?
- extra == 'auto_increment'
- end
-
- private
-
- # MySQL misreports NOT NULL column default when none is given.
- # We can't detect this for columns which may have a legitimate ''
- # default (string) but we can for others (integer, datetime, boolean,
- # and the rest).
- #
- # Test whether the column has default '', is not null, and is not
- # a type allowing default ''.
- def missing_default_forged_as_empty_string?(default)
- type != :string && !null && default == ''
- end
-
- def assert_valid_default(default)
- if blob_or_text_column? && default.present?
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- end
- end
-
- class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :extra, :strict
-
- def initialize(type_metadata, extra: "", strict: false)
- super(type_metadata)
- @type_metadata = type_metadata
- @extra = extra
- @strict = strict
- end
-
- def ==(other)
- other.is_a?(MysqlTypeMetadata) &&
- attributes_for_hash == other.attributes_for_hash
- end
- alias eql? ==
-
- def hash
- attributes_for_hash.hash
- end
-
- protected
-
- def attributes_for_hash
- [self.class, @type_metadata, extra, strict]
- end
- end
-
##
# :singleton-method:
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
- # as boolean. If you wish to disable this emulation (which was the default
- # behavior in versions 0.13.1 and earlier) you can add the following line
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
+ # as boolean. If you wish to disable this emulation you can add the following line
# to your application.rb file:
#
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
class_attribute :emulate_booleans
self.emulate_booleans = true
@@ -143,8 +59,7 @@ module ActiveRecord
# FIXME: Make the first parameter more similar for the two adapters
def initialize(connection, logger, connection_options, config)
- super(connection, logger)
- @connection_options, @config = connection_options, config
+ super(connection, logger, config)
@quoted_column_names, @quoted_table_names = {}, {}
@visitor = Arel::Visitors::MySQL.new self
@@ -249,7 +164,7 @@ module ActiveRecord
end
def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- Column.new(field, default, sql_type_metadata, null, default_function, collation)
+ MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -401,18 +316,13 @@ module ActiveRecord
log(sql, name) { @connection.query(sql) }
end
- # MysqlAdapter has to free a result after using it, so we use this method to write
- # stuff in an abstract way without concerning ourselves about whether it needs to be
- # explicitly freed or not.
- def execute_and_free(sql, name = nil) #:nodoc:
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
+ # to write stuff in an abstract way without concerning ourselves about whether it
+ # needs to be explicitly freed or not.
+ def execute_and_free(sql, name = nil) # :nodoc:
yield execute(sql, name)
end
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.affected_rows
- end
-
def begin_db_transaction
execute "BEGIN"
end
@@ -433,7 +343,7 @@ module ActiveRecord
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
# these, we must use a subquery.
- def join_to_update(update, select) #:nodoc:
+ def join_to_update(update, select, key) # :nodoc:
if select.limit || select.offset || select.orders.any?
super
else
@@ -522,6 +432,7 @@ module ActiveRecord
end
def table_exists?(table_name)
+ # Update lib/active_record/internal_metadata.rb when this gets removed
ActiveSupport::Deprecation.warn(<<-MSG.squish)
#table_exists? currently checks both tables and views.
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
@@ -584,12 +495,16 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name)#:nodoc:
+ def columns(table_name) # :nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
+ else
+ new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ end
end
end
end
@@ -638,6 +553,7 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
+ create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
@@ -766,16 +682,11 @@ module ActiveRecord
SQL
end
- def case_sensitive_modifier(node, table_attribute)
- node = Arel::Nodes.build_quoted node, table_attribute
- Arel::Nodes::Bin.new(node)
- end
-
def case_sensitive_comparison(table, attribute, column, value)
- if column.case_sensitive?
- table[attribute].eq(value)
- else
+ if value.nil? || column.case_sensitive?
super
+ else
+ table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
end
end
@@ -783,10 +694,25 @@ module ActiveRecord
if column.case_sensitive?
super
else
- table[attribute].eq(value)
+ table[attribute].eq(Arel::Nodes::BindParam.new)
end
end
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
+ # distinct queries, and requires that the ORDER BY include the distinct column.
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
+ 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)\b/i, '')
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
+
+ [super, *order_columns].join(', ')
+ end
+
def strict_mode?
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
@@ -839,7 +765,7 @@ module ActiveRecord
def register_integer_type(mapping, key, options) # :nodoc:
mapping.register_type(key) do |sql_type|
- if /unsigned/i =~ sql_type
+ if /\bunsigned\z/ === sql_type
Type::UnsignedInteger.new(options)
else
Type::Integer.new(options)
@@ -856,7 +782,7 @@ module ActiveRecord
end
def fetch_type_metadata(sql_type, extra = "")
- MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
end
def add_index_length(option_strings, column_names, options = {})
@@ -966,11 +892,13 @@ module ActiveRecord
subsubselect = select.clone
subsubselect.projections = [key]
+ # Materialize subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subsubselect.distinct unless select.limit || select.offset || select.orders.any?
+
subselect = Arel::SelectManager.new(select.engine)
subselect.project Arel.sql(key.name)
- # Materialized subquery by adding distinct
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subselect.from subsubselect.distinct.as('__active_record_temp')
+ subselect.from subsubselect.as('__active_record_temp')
end
def mariadb?
@@ -1033,9 +961,12 @@ module ActiveRecord
end
end
+ def create_table_info_cache # :nodoc:
+ @create_table_info_cache ||= {}
+ end
+
def create_table_info(table_name) # :nodoc:
- @create_table_info_cache = {}
- @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
@@ -1102,11 +1033,8 @@ module ActiveRecord
end
end
- ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql)
ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
- ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
- ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql)
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 08d46fca96..f633892dee 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -175,7 +175,7 @@ module ActiveRecord
rescue Gem::LoadError => e
raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)."
rescue LoadError => e
- raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
+ raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
end
adapter_method = "#{spec[:adapter]}_connection"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
new file mode 100644
index 0000000000..9c45fdd44a
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ class Column < ConnectionAdapters::Column # :nodoc:
+ delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
+
+ def initialize(*)
+ super
+ assert_valid_default
+ extract_default
+ end
+
+ def has_default?
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
+ super
+ end
+
+ def blob_or_text_column?
+ /\A(?:tiny|medium|long)?blob\b/ === sql_type || type == :text
+ end
+
+ def unsigned?
+ /\bunsigned\z/ === sql_type
+ end
+
+ def case_sensitive?
+ collation && collation !~ /_ci\z/
+ end
+
+ def auto_increment?
+ extra == 'auto_increment'
+ end
+
+ private
+
+ def extract_default
+ if blob_or_text_column?
+ @default = null || strict ? nil : ''
+ end
+ end
+
+ def assert_valid_default
+ if blob_or_text_column? && default.present?
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index ca7dfda80d..157e75dbf7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -11,6 +11,30 @@ module ActiveRecord
args.each { |name| column(name, :blob, options) }
end
+ def tinyblob(*args, **options)
+ args.each { |name| column(name, :tinyblob, options) }
+ end
+
+ def mediumblob(*args, **options)
+ args.each { |name| column(name, :mediumblob, options) }
+ end
+
+ def longblob(*args, **options)
+ args.each { |name| column(name, :longblob, options) }
+ end
+
+ def tinytext(*args, **options)
+ args.each { |name| column(name, :tinytext, options) }
+ end
+
+ def mediumtext(*args, **options)
+ args.each { |name| column(name, :mediumtext, options) }
+ end
+
+ def longtext(*args, **options)
+ args.each { |name| column(name, :longtext, options) }
+ end
+
def json(*args, **options)
args.each { |name| column(name, :json, options) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
new file mode 100644
index 0000000000..e1e3f7b472
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
+ attr_reader :extra, :strict
+
+ def initialize(type_metadata, extra: "", strict: false)
+ super(type_metadata)
+ @type_metadata = type_metadata
+ @extra = extra
+ @strict = strict
+ end
+
+ def ==(other)
+ other.is_a?(MySQL::TypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
+
+ def attributes_for_hash
+ [self.class, @type_metadata, extra, strict]
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 3944698910..c3c5b660fd 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -10,14 +10,18 @@ module ActiveRecord
config = config.symbolize_keys
config[:username] = 'root' if config[:username].nil?
+ config[:flags] ||= 0
if Mysql2::Client.const_defined? :FOUND_ROWS
- config[:flags] = Mysql2::Client::FOUND_ROWS
+ if config[:flags].kind_of? Array
+ config[:flags].push "FOUND_ROWS".freeze
+ else
+ config[:flags] |= Mysql2::Client::FOUND_ROWS
+ end
end
client = Mysql2::Client.new(config)
- options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
- ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
rescue Mysql2::Error => error
if error.message.include?("Unknown database")
raise ActiveRecord::NoDatabaseError
@@ -95,33 +99,15 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
#++
- # FIXME: re-enable the following once a "better" query_cache solution is in core
- #
- # The overrides below perform much better than the originals in AbstractAdapter
- # because we're able to take advantage of mysql2's lazy-loading capabilities
- #
- # # Returns a record hash with the column names as keys and column values
- # # as values.
- # def select_one(sql, name = nil)
- # result = execute(sql, name)
- # result.each(as: :hash) do |r|
- # return r
- # end
- # end
- #
- # # Returns a single value from a record
- # def select_value(sql, name = nil)
- # result = execute(sql, name)
- # if first = result.first
- # first.first
- # end
- # end
- #
- # # Returns an array of the values of the first column in a select:
- # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
- # def select_values(sql, name = nil)
- # execute(sql, name).map { |row| row.first }
- # end
+ # Returns a record hash with the column names as keys and column values
+ # as values.
+ def select_one(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation(arel, binds)
+ execute(to_sql(arel, binds), name).each(as: :hash) do |row|
+ @connection.next_result while @connection.more_results?
+ return row
+ end
+ end
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
@@ -150,12 +136,6 @@ module ActiveRecord
alias exec_without_stmt exec_query
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- super
- id_value || @connection.last_id
- end
- alias :create :insert_sql
-
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
execute to_sql(sql, binds), name
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
deleted file mode 100644
index f2d7b54105..0000000000
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ /dev/null
@@ -1,480 +0,0 @@
-require 'active_record/connection_adapters/abstract_mysql_adapter'
-require 'active_record/connection_adapters/statement_pool'
-require 'active_support/core_ext/hash/keys'
-
-gem 'mysql', '~> 2.9'
-require 'mysql'
-
-class Mysql # :nodoc: all
- class Time
- # Used for casting DateTime fields to a MySQL friendly Time.
- # This was documented in 48498da0dfed5239ea1eafb243ce47d7e3ce9e8e
- def to_date
- Date.new(year, month, day)
- end
- end
- class Stmt; include Enumerable end
- class Result; include Enumerable end
-end
-
-module ActiveRecord
- module ConnectionHandling # :nodoc:
- # Establishes a connection to the database that's used by all Active Record objects.
- def mysql_connection(config)
- config = config.symbolize_keys
- host = config[:host]
- port = config[:port]
- socket = config[:socket]
- username = config[:username] ? config[:username].to_s : 'root'
- password = config[:password].to_s
- database = config[:database]
-
- mysql = Mysql.init
- mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
-
- default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
- default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
- options = [host, username, password, database, port, socket, default_flags]
- ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
- rescue Mysql::Error => error
- if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError
- else
- raise
- end
- end
- end
-
- module ConnectionAdapters
- # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
- # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
- #
- # Options:
- #
- # * <tt>:host</tt> - Defaults to "localhost".
- # * <tt>:port</tt> - Defaults to 3306.
- # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
- # * <tt>:username</tt> - Defaults to "root"
- # * <tt>:password</tt> - Defaults to nothing.
- # * <tt>:database</tt> - The name of the database. No default, must be provided.
- # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
- # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.7/en/auto-reconnect.html).
- # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html)
- # * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.7/en/set-statement.html).
- # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
- #
- class MysqlAdapter < AbstractMysqlAdapter
- ADAPTER_NAME = 'MySQL'.freeze
-
- class StatementPool < ConnectionAdapters::StatementPool
- private
-
- def dealloc(stmt)
- stmt[:stmt].close
- end
- end
-
- def initialize(connection, logger, connection_options, config)
- super
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
- @client_encoding = nil
- connect
- end
-
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
- # HELPER METHODS ===========================================
-
- def each_hash(result) # :nodoc:
- if block_given?
- result.each_hash do |row|
- row.symbolize_keys!
- yield row
- end
- else
- to_enum(:each_hash, result)
- end
- end
-
- def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- field = set_field_encoding(field)
- super
- end
-
- def error_number(exception) # :nodoc:
- exception.errno if exception.respond_to?(:errno)
- end
-
- # QUOTING ==================================================
-
- def quote_string(string) #:nodoc:
- @connection.quote(string)
- end
-
- #--
- # CONNECTION MANAGEMENT ====================================
- #++
-
- def active?
- if @connection.respond_to?(:stat)
- @connection.stat
- else
- @connection.query 'select 1'
- end
-
- # mysql-ruby doesn't raise an exception when stat fails.
- if @connection.respond_to?(:errno)
- @connection.errno.zero?
- else
- true
- end
- rescue Mysql::Error
- false
- end
-
- def reconnect!
- super
- disconnect!
- connect
- end
-
- # Disconnects from the database if already connected. Otherwise, this
- # method does nothing.
- def disconnect!
- super
- @connection.close rescue nil
- end
-
- def reset!
- if @connection.respond_to?(:change_user)
- # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
- # reset the connection is to change the user to the same user.
- @connection.change_user(@config[:username], @config[:password], @config[:database])
- configure_connection
- end
- end
-
- #--
- # DATABASE STATEMENTS ======================================
- #++
-
- def select_all(arel, name = nil, binds = [])
- if ExplainRegistry.collect? && prepared_statements
- unprepared_statement { super }
- else
- super
- end
- end
-
- def select_rows(sql, name = nil, binds = [])
- @connection.query_with_result = true
- rows = exec_query(sql, name, binds).rows
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
- rows
- end
-
- # Clears the prepared statements cache.
- def clear_cache!
- super
- @statements.clear
- end
-
- # Taken from here:
- # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
- # Author: TOMITA Masahiro <tommy@tmtm.org>
- ENCODINGS = {
- "armscii8" => nil,
- "ascii" => Encoding::US_ASCII,
- "big5" => Encoding::Big5,
- "binary" => Encoding::ASCII_8BIT,
- "cp1250" => Encoding::Windows_1250,
- "cp1251" => Encoding::Windows_1251,
- "cp1256" => Encoding::Windows_1256,
- "cp1257" => Encoding::Windows_1257,
- "cp850" => Encoding::CP850,
- "cp852" => Encoding::CP852,
- "cp866" => Encoding::IBM866,
- "cp932" => Encoding::Windows_31J,
- "dec8" => nil,
- "eucjpms" => Encoding::EucJP_ms,
- "euckr" => Encoding::EUC_KR,
- "gb2312" => Encoding::EUC_CN,
- "gbk" => Encoding::GBK,
- "geostd8" => nil,
- "greek" => Encoding::ISO_8859_7,
- "hebrew" => Encoding::ISO_8859_8,
- "hp8" => nil,
- "keybcs2" => nil,
- "koi8r" => Encoding::KOI8_R,
- "koi8u" => Encoding::KOI8_U,
- "latin1" => Encoding::ISO_8859_1,
- "latin2" => Encoding::ISO_8859_2,
- "latin5" => Encoding::ISO_8859_9,
- "latin7" => Encoding::ISO_8859_13,
- "macce" => Encoding::MacCentEuro,
- "macroman" => Encoding::MacRoman,
- "sjis" => Encoding::SHIFT_JIS,
- "swe7" => nil,
- "tis620" => Encoding::TIS_620,
- "ucs2" => Encoding::UTF_16BE,
- "ujis" => Encoding::EucJP_ms,
- "utf8" => Encoding::UTF_8,
- "utf8mb4" => Encoding::UTF_8,
- }
-
- # Get the client encoding for this database
- def client_encoding
- return @client_encoding if @client_encoding
-
- result = exec_query(
- "select @@character_set_client",
- 'SCHEMA')
- @client_encoding = ENCODINGS[result.rows.last.last]
- end
-
- def exec_query(sql, name = 'SQL', binds = [], prepare: false)
- if without_prepared_statement?(binds)
- result_set, affected_rows = exec_without_stmt(sql, name)
- else
- result_set, affected_rows = exec_stmt(sql, name, binds, cache_stmt: prepare)
- end
-
- yield affected_rows if block_given?
-
- result_set
- end
-
- def last_inserted_id(result)
- @connection.insert_id
- end
-
- module Fields # :nodoc:
- class DateTime < Type::DateTime # :nodoc:
- def cast_value(value)
- if Mysql::Time === value
- new_time(
- value.year,
- value.month,
- value.day,
- value.hour,
- value.minute,
- value.second,
- value.second_part)
- else
- super
- end
- end
- end
-
- class Time < Type::Time # :nodoc:
- def cast_value(value)
- if Mysql::Time === value
- new_time(
- 2000,
- 01,
- 01,
- value.hour,
- value.minute,
- value.second,
- value.second_part)
- else
- super
- end
- end
- end
-
- class << self
- TYPES = Type::HashLookupTypeMap.new # :nodoc:
-
- delegate :register_type, :alias_type, to: :TYPES
-
- def find_type(field)
- if field.type == Mysql::Field::TYPE_TINY && field.length > 1
- TYPES.lookup(Mysql::Field::TYPE_LONG)
- else
- TYPES.lookup(field.type)
- end
- end
- end
-
- register_type Mysql::Field::TYPE_TINY, Type::Boolean.new
- register_type Mysql::Field::TYPE_LONG, Type::Integer.new
- alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
- alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
-
- register_type Mysql::Field::TYPE_DATE, Type::Date.new
- register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
- register_type Mysql::Field::TYPE_TIME, Fields::Time.new
- register_type Mysql::Field::TYPE_FLOAT, Type::Float.new
- end
-
- def initialize_type_map(m) # :nodoc:
- super
- register_class_with_precision m, %r(datetime)i, Fields::DateTime
- register_class_with_precision m, %r(time)i, Fields::Time
- end
-
- def exec_without_stmt(sql, name = 'SQL') # :nodoc:
- # Some queries, like SHOW CREATE TABLE don't work through the prepared
- # statement API. For those queries, we need to use this method. :'(
- log(sql, name) do
- result = @connection.query(sql)
- affected_rows = @connection.affected_rows
-
- if result
- types = {}
- fields = []
- result.fetch_fields.each { |field|
- field_name = field.name
- fields << field_name
-
- if field.decimals > 0
- types[field_name] = Type::Decimal.new
- else
- types[field_name] = Fields.find_type field
- end
- }
-
- result_set = ActiveRecord::Result.new(fields, result.to_a, types)
- result.free
- else
- result_set = ActiveRecord::Result.new([], [])
- end
-
- [result_set, affected_rows]
- end
- end
-
- def execute_and_free(sql, name = nil) # :nodoc:
- result = execute(sql, name)
- ret = yield result
- result.free
- ret
- end
-
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- super sql, name
- id_value || @connection.insert_id
- end
- alias :create :insert_sql
-
- def exec_delete(sql, name, binds) # :nodoc:
- affected_rows = 0
-
- exec_query(sql, name, binds) do |n|
- affected_rows = n
- end
-
- affected_rows
- end
- alias :exec_update :exec_delete
-
- def begin_db_transaction #:nodoc:
- exec_query "BEGIN"
- end
-
- private
-
- def exec_stmt(sql, name, binds, cache_stmt: false)
- cache = {}
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
-
- log(sql, name, binds) do
- if !cache_stmt
- stmt = @connection.prepare(sql)
- else
- cache = @statements[sql] ||= {
- :stmt => @connection.prepare(sql)
- }
- stmt = cache[:stmt]
- end
-
- begin
- stmt.execute(*type_casted_binds)
- rescue Mysql::Error => e
- # Older versions of MySQL leave the prepared statement in a bad
- # place when an error occurs. To support older MySQL versions, we
- # need to close the statement and delete the statement from the
- # cache.
- if !cache_stmt
- stmt.close
- else
- @statements.delete sql
- end
- raise e
- end
-
- cols = nil
- if metadata = stmt.result_metadata
- cols = cache[:cols] ||= metadata.fetch_fields.map(&:name)
- metadata.free
- end
-
- result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
- affected_rows = stmt.affected_rows
-
- stmt.free_result
- stmt.close if !cache_stmt
-
- [result_set, affected_rows]
- end
- end
-
- def connect
- encoding = @config[:encoding]
- if encoding
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
- end
-
- if @config[:sslca] || @config[:sslkey]
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
- end
-
- @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
- @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
- @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
-
- @connection.real_connect(*@connection_options)
-
- # reconnect must be set after real_connect is called, because real_connect sets it to false internally
- @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
-
- configure_connection
- end
-
- # Many Rails applications monkey-patch a replacement of the configure_connection method
- # and don't call 'super', so leave this here even though it looks superfluous.
- def configure_connection
- super
- end
-
- def select(sql, name = nil, binds = [])
- @connection.query_with_result = true
- rows = super
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
- rows
- end
-
- # Returns the full version of the connected MySQL server.
- def full_version
- @full_version ||= @connection.server_info
- end
-
- def set_field_encoding(field_name)
- field_name.force_encoding(client_encoding)
- if internal_enc = Encoding.default_internal
- field_name = field_name.encode!(internal_enc)
- end
- field_name
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 0e0c0e993a..6c15facf3b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -52,8 +52,8 @@ module ActiveRecord
end
end
- def select_values(arel, name = nil)
- arel, binds = binds_from_relation arel, []
+ def select_values(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
execute_and_clear(sql, name, binds) do |result|
if result.nfields > 0
@@ -72,28 +72,6 @@ module ActiveRecord
end
end
- # Executes an INSERT query and returns the new record's ID
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- unless pk
- # Extract the table from the insert sql. Yuck.
- table_ref = extract_table_ref_from_insert_sql(sql)
- pk = primary_key(table_ref) if table_ref
- end
-
- if pk && use_insert_returning?
- select_value("#{sql} RETURNING #{quote_column_name(pk)}")
- elsif pk
- super
- last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
- else
- super
- end
- end
-
- def create
- super.insert
- end
-
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# The internal PostgreSQL identifier of the BYTEA data type.
@@ -174,7 +152,7 @@ module ActiveRecord
end
alias :exec_update :exec_delete
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
unless pk
# Extract the table from the insert sql. Yuck.
table_ref = extract_table_ref_from_insert_sql(sql)
@@ -185,7 +163,7 @@ module ActiveRecord
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
end
- [sql, binds]
+ super
end
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
@@ -202,11 +180,6 @@ module ActiveRecord
end
end
- # Executes an UPDATE query and returns the number of affected tuples.
- def update_sql(sql, name = nil)
- super.cmd_tuples
- end
-
# Begins a transaction.
def begin_db_transaction
execute "BEGIN"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index 25961a9869..87593ef704 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -50,6 +50,10 @@ module ActiveRecord
"[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
end
+ def map(value, &block)
+ value.map(&block)
+ end
+
private
def type_cast_array(value, method)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index fc201f8fb9..a8d2310035 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -6,6 +6,7 @@ module ActiveRecord
module OID # :nodoc:
class Range < Type::Value # :nodoc:
attr_reader :subtype, :type
+ delegate :user_input_in_time_zone, to: :subtype
def initialize(subtype, type = :range)
@subtype = subtype
@@ -18,7 +19,7 @@ module ActiveRecord
def cast_value(value)
return if value == 'empty'
- return value if value.is_a?(::Range)
+ return value unless value.is_a?(::String)
extracted = extract_bounds(value)
from = type_cast_single extracted[:from]
@@ -46,6 +47,12 @@ module ActiveRecord
other.type == type
end
+ def map(value) # :nodoc:
+ new_begin = yield(value.begin)
+ new_end = yield(value.end)
+ ::Range.new(new_begin, new_end, value.exclude_end?)
+ end
+
private
def type_cast_single(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index d5879ea7df..c1c77a967e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -55,10 +55,11 @@ module ActiveRecord
end
end
- # Does not quote function default values for UUID columns
- def quote_default_expression(value, column) #:nodoc:
- if column.type == :uuid && value =~ /\(\)/
- value
+ def quote_default_expression(value, column) # :nodoc:
+ if value.is_a?(Proc)
+ value.call
+ elsif column.type == :uuid && value =~ /\(\)/
+ value # Does not quote function default values for UUID columns
elsif column.respond_to?(:array?)
value = type_cast_from_column(column, value)
quote(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index a4f0742516..cc7721ddd8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -9,7 +9,7 @@ module ActiveRecord
spec[:id] = ':bigserial'
elsif column.type == :uuid
spec[:id] = ':uuid'
- spec[:default] = column.default_function.inspect
+ spec[:default] = schema_default(column) || 'nil'
else
spec[:id] = column.type.inspect
spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
@@ -41,12 +41,8 @@ module ActiveRecord
end
end
- def schema_default(column)
- if column.default_function
- column.default_function.inspect unless column.serial?
- else
- super
- end
+ def schema_expression(column)
+ super unless column.serial?
end
end
end
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 a48d64f7bd..67e727d8ed 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -169,15 +169,18 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
- result = query(<<-SQL, 'SCHEMA')
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
- FROM pg_class t
- INNER JOIN pg_index d ON t.oid = d.indrelid
- INNER JOIN pg_class i ON d.indexrelid = i.oid
- WHERE i.relkind = 'i'
- AND d.indisprimary = 'f'
- AND t.relname = '#{table_name}'
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
+
+ result = query(<<-SQL, 'SCHEMA')
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+ FROM pg_class t
+ INNER JOIN pg_index d ON t.oid = d.indrelid
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
+ WHERE i.relkind = 'i'
+ AND d.indisprimary = 'f'
+ AND t.relname = '#{table.identifier}'
+ AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
ORDER BY i.relname
SQL
@@ -504,14 +507,27 @@ module ActiveRecord
end
def remove_index(table_name, options = {}) #:nodoc:
- index_name = index_name_for_remove(table_name, options)
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
+
+ if options.is_a?(Hash) && options.key?(:name)
+ provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
+
+ options[:name] = provided_index.identifier
+ table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
+
+ if provided_index.schema.present? && table.schema != provided_index.schema
+ raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
+ end
+ end
+
+ index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
algorithm =
- if Hash === options && options.key?(:algorithm)
+ if options.is_a?(Hash) && options.key?(:algorithm)
index_algorithms.fetch(options[:algorithm]) do
raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
end
end
- execute "DROP INDEX #{algorithm} #{quote_table_name(index_name)}"
+ execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
end
# Renames an index of a table. Raises error if length of new
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index f731da9e18..2de6fbfaf0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -192,7 +192,7 @@ module ActiveRecord
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
- super(connection, logger)
+ super(connection, logger, config)
@visitor = Arel::Visitors::PostgreSQL.new self
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@@ -202,7 +202,7 @@ module ActiveRecord
@prepared_statements = false
end
- @connection_parameters, @config = connection_parameters, config
+ @connection_parameters = connection_parameters
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
@@ -390,12 +390,12 @@ module ActiveRecord
"average" => "avg",
}
- protected
+ # Returns the version of the connected PostgreSQL server.
+ def postgresql_version
+ @connection.server_version
+ end
- # Returns the version of the connected PostgreSQL server.
- def postgresql_version
- @connection.server_version
- end
+ protected
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
FOREIGN_KEY_VIOLATION = "23503"
@@ -512,8 +512,13 @@ module ActiveRecord
def extract_value_from_default(default) # :nodoc:
case default
# Quoted types
- when /\A[\(B]?'(.*)'::/m
- $1.gsub("''".freeze, "'".freeze)
+ when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
+ # The default 'now'::date is CURRENT_DATE
+ if $1 == "now".freeze && $2 == "date".freeze
+ nil
+ else
+ $1.gsub("''".freeze, "'".freeze)
+ end
# Boolean types
when 'true'.freeze, 'false'.freeze
default
@@ -535,7 +540,7 @@ module ActiveRecord
end
def has_default_function?(default_value, default) # :nodoc:
- !default_value && (%r{\w+\(.*\)} === default)
+ !default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default)
end
def load_additional_types(type_map, oids = nil) # :nodoc:
@@ -690,15 +695,7 @@ module ActiveRecord
end
# Returns the current ID of a table's sequence.
- def last_insert_id(sequence_name) #:nodoc:
- Integer(last_insert_id_value(sequence_name))
- end
-
- def last_insert_id_value(sequence_name)
- last_insert_id_result(sequence_name).rows.first.first
- end
-
- def last_insert_id_result(sequence_name) #:nodoc:
+ def last_insert_id_result(sequence_name) # :nodoc:
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
end
@@ -816,9 +813,8 @@ module ActiveRecord
ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql)
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
- ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
+ ActiveRecord::Type.register(:point, OID::Rails51Point, adapter: :postgresql)
ActiveRecord::Type.register(:legacy_point, OID::Point, adapter: :postgresql)
- ActiveRecord::Type.register(:rails_5_1_point, OID::Rails51Point, adapter: :postgresql)
ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 90df9b8825..d1893f35f5 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -78,11 +78,10 @@ module ActiveRecord
end
def initialize(connection, logger, connection_options, config)
- super(connection, logger)
+ super(connection, logger, config)
@active = nil
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
- @config = config
@visitor = Arel::Visitors::SQLite.new self
@quoted_column_names = {}
@@ -130,6 +129,10 @@ module ActiveRecord
true
end
+ def supports_datetime_with_precision?
+ true
+ end
+
def active?
@active != false
end
@@ -277,22 +280,6 @@ module ActiveRecord
log(sql, name) { @connection.execute(sql) }
end
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.changes
- end
-
- def delete_sql(sql, name = nil) #:nodoc:
- sql += " WHERE 1=1" unless sql =~ /WHERE/i
- super sql, name
- end
-
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- super
- id_value || @connection.last_insert_row_id
- end
- alias :create :insert_sql
-
def select_rows(sql, name = nil, binds = [])
exec_query(sql, name, binds).rows
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index aedef54928..a8b3d03ba5 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -8,7 +8,7 @@ module ActiveRecord
# example for regular databases (MySQL, PostgreSQL, etc):
#
# ActiveRecord::Base.establish_connection(
- # adapter: "mysql",
+ # adapter: "mysql2",
# host: "localhost",
# username: "myuser",
# password: "mypass",
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1250f8a3c3..475a298467 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -275,7 +275,7 @@ module ActiveRecord
def relation # :nodoc:
relation = Relation.create(self, arel_table, predicate_builder)
- if finder_needs_type_condition?
+ if finder_needs_type_condition? && !ignore_default_scope?
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
else
relation
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 7ded96f8fb..8655f68308 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -124,7 +124,7 @@ module ActiveRecord
def deserialize(value)
return if value.nil?
- mapping.key(value.to_i)
+ mapping.key(value)
end
def serialize(value)
@@ -187,7 +187,7 @@ module ActiveRecord
# scope :active, -> { where status: 0 }
klass.send(:detect_enum_conflict!, name, value_method_name, true)
- klass.scope value_method_name, -> { klass.where name => value }
+ klass.scope value_method_name, -> { where(name => value) }
end
end
defined_enums[name.to_s] = enum_values
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 1cd2c2ef8c..e5906b6756 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -272,7 +272,7 @@ module ActiveRecord
# * 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.
+ # The mysql2 and postgresql adapters support setting the transaction isolation level.
class TransactionIsolationError < ActiveRecordError
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index deb7d7d06d..ed1bbf5dcd 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -401,7 +401,7 @@ module ActiveRecord
# It's possible to set the fixture's model class directly in the YAML file.
# This is helpful when fixtures are loaded outside tests and
# +set_fixture_class+ is not available (e.g.
- # when running <tt>rake db:fixtures:load</tt>).
+ # when running <tt>rails db:fixtures:load</tt>).
#
# _fixture:
# model_class: User
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index a388b529c9..ecf4046bff 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveRecord
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 8b719e0bcb..6259c4cd33 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -51,8 +51,8 @@ module ActiveRecord
end
attrs = args.first
- if subclass_from_attributes?(attrs)
- subclass = subclass_from_attributes(attrs)
+ if has_attribute?(inheritance_column)
+ subclass = subclass_from_attributes(attrs) || subclass_from_attributes(column_defaults)
end
if subclass && subclass != self
@@ -163,7 +163,7 @@ module ActiveRecord
end
def using_single_table_inheritance?(record)
- record[inheritance_column].present? && columns_hash.include?(inheritance_column)
+ record[inheritance_column].present? && has_attribute?(inheritance_column)
end
def find_sti_class(type_name)
@@ -195,18 +195,14 @@ module ActiveRecord
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
# is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
- # If this is a StrongParameters hash, and access to inheritance_column is not permitted,
- # this will ignore the inheritance column and return nil
- def subclass_from_attributes?(attrs)
- attribute_names.include?(inheritance_column) && (attrs.is_a?(Hash) || attrs.respond_to?(:permitted?))
- end
-
def subclass_from_attributes(attrs)
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
- subclass_name = attrs.with_indifferent_access[inheritance_column]
+ if attrs.is_a?(Hash)
+ subclass_name = attrs.with_indifferent_access[inheritance_column]
- if subclass_name.present?
- find_sti_class(subclass_name)
+ if subclass_name.present?
+ find_sti_class(subclass_name)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
new file mode 100644
index 0000000000..e5c6e5c885
--- /dev/null
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -0,0 +1,46 @@
+require 'active_record/scoping/default'
+require 'active_record/scoping/named'
+
+module ActiveRecord
+ # This class is used to create a table that keeps track of values and keys such
+ # as which environment migrations were run in.
+ class InternalMetadata < ActiveRecord::Base # :nodoc:
+ class << self
+ def primary_key
+ "key"
+ end
+
+ def table_name
+ "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
+ end
+
+ def index_name
+ "#{table_name_prefix}unique_#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
+ end
+
+ def []=(key, value)
+ first_or_initialize(key: key).update_attributes!(value: value)
+ end
+
+ def [](key)
+ where(key: key).pluck(:value).first
+ end
+
+ def table_exists?
+ ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
+ end
+
+ # Creates an internal metadata table with columns +key+ and +value+
+ def create_table
+ unless table_exists?
+ connection.create_table(table_name, id: false) do |t|
+ t.column :key, :string
+ t.column :value, :string
+ t.timestamps
+ t.index :key, unique: true, name: index_name
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index ca2537cdc3..f5b29c7f2e 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -13,7 +13,7 @@ module ActiveRecord
# For example the following migration is not reversible.
# Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error.
#
- # class IrreversibleMigrationExample < ActiveRecord::Migration
+ # class IrreversibleMigrationExample < ActiveRecord::Migration[5.0]
# def change
# create_table :distributors do |t|
# t.string :zipcode
@@ -31,7 +31,7 @@ module ActiveRecord
#
# 1. Define <tt>#up</tt> and <tt>#down</tt> methods instead of <tt>#change</tt>:
#
- # class ReversibleMigrationExample < ActiveRecord::Migration
+ # class ReversibleMigrationExample < ActiveRecord::Migration[5.0]
# def up
# create_table :distributors do |t|
# t.string :zipcode
@@ -56,7 +56,7 @@ module ActiveRecord
#
# 2. Use the #reversible method in <tt>#change</tt> method:
#
- # class ReversibleMigrationExample < ActiveRecord::Migration
+ # class ReversibleMigrationExample < ActiveRecord::Migration[5.0]
# def change
# create_table :distributors do |t|
# t.string :zipcode
@@ -126,9 +126,9 @@ module ActiveRecord
class PendingMigrationError < MigrationError#:nodoc:
def initialize(message = nil)
if !message && defined?(Rails.env)
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}")
elsif !message
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate")
else
super
end
@@ -143,6 +143,40 @@ module ActiveRecord
end
end
+ class NoEnvironmentInSchemaError < MigrationError #:nodoc:
+ def initialize
+ msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rake db:environment:set"
+ if defined?(Rails.env)
+ super("#{msg} RAILS_ENV=#{::Rails.env}")
+ else
+ super(msg)
+ end
+ end
+ end
+
+ class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
+ def initialize(env = "production")
+ msg = "You are attempting to run a destructive action against your '#{env}' database\n"
+ msg << "If you are sure you want to continue, run the same command with the environment variable\n"
+ msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
+ super(msg)
+ end
+ end
+
+ class EnvironmentMismatchError < ActiveRecordError
+ def initialize(current: nil, stored: nil)
+ msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
+ msg << "You are running in `#{ current }` environment."
+ msg << "If you are sure you want to continue, first set the environment using:\n\n"
+ msg << "\tbin/rake db:environment:set"
+ if defined?(Rails.env)
+ super("#{msg} RAILS_ENV=#{::Rails.env}")
+ else
+ super(msg)
+ end
+ end
+ end
+
# = Active Record Migrations
#
# Migrations can manage the evolution of a schema used by several physical
@@ -155,7 +189,7 @@ module ActiveRecord
#
# Example of a simple migration:
#
- # class AddSsl < ActiveRecord::Migration
+ # class AddSsl < ActiveRecord::Migration[5.0]
# def up
# add_column :accounts, :ssl_enabled, :boolean, default: true
# end
@@ -175,7 +209,7 @@ module ActiveRecord
#
# Example of a more complex migration that also needs to initialize data:
#
- # class AddSystemSettings < ActiveRecord::Migration
+ # class AddSystemSettings < ActiveRecord::Migration[5.0]
# def up
# create_table :system_settings do |t|
# t.string :name
@@ -301,23 +335,23 @@ module ActiveRecord
# rails generate migration add_fieldname_to_tablename fieldname:string
#
# This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
- # class AddFieldnameToTablename < ActiveRecord::Migration
+ # class AddFieldnameToTablename < ActiveRecord::Migration[5.0]
# def change
# add_column :tablenames, :fieldname, :string
# end
# end
#
# To run migrations against the currently configured database, use
- # <tt>rake db:migrate</tt>. This will update the database by running all of the
+ # <tt>rails db:migrate</tt>. This will update the database by running all of the
# pending migrations, creating the <tt>schema_migrations</tt> table
# (see "About the schema_migrations table" section below) if missing. It will also
# invoke the db:schema:dump task, which will update your db/schema.rb file
# to match the structure of your database.
#
# To roll the database back to a previous migration version, use
- # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
+ # <tt>rails db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
# you wish to downgrade. Alternatively, you can also use the STEP option if you
- # wish to rollback last few migrations. <tt>rake db:migrate STEP=2</tt> will rollback
+ # wish to rollback last few migrations. <tt>rails db:migrate STEP=2</tt> will rollback
# the latest two migrations.
#
# If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
@@ -332,7 +366,7 @@ module ActiveRecord
#
# Not all migrations change the schema. Some just fix the data:
#
- # class RemoveEmptyTags < ActiveRecord::Migration
+ # class RemoveEmptyTags < ActiveRecord::Migration[5.0]
# def up
# Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
# end
@@ -345,7 +379,7 @@ module ActiveRecord
#
# Others remove columns when they migrate up instead of down:
#
- # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
+ # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[5.0]
# def up
# remove_column :items, :incomplete_items_count
# remove_column :items, :completed_items_count
@@ -359,7 +393,7 @@ module ActiveRecord
#
# And sometimes you need to do something in SQL not abstracted directly by migrations:
#
- # class MakeJoinUnique < ActiveRecord::Migration
+ # class MakeJoinUnique < ActiveRecord::Migration[5.0]
# def up
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
# end
@@ -376,7 +410,7 @@ module ActiveRecord
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
# latest column data from after the new column was added. Example:
#
- # class AddPeopleSalary < ActiveRecord::Migration
+ # class AddPeopleSalary < ActiveRecord::Migration[5.0]
# def up
# add_column :people, :salary, :integer
# Person.reset_column_information
@@ -434,7 +468,7 @@ module ActiveRecord
# To define a reversible migration, define the +change+ method in your
# migration like this:
#
- # class TenderloveMigration < ActiveRecord::Migration
+ # class TenderloveMigration < ActiveRecord::Migration[5.0]
# def change
# create_table(:horses) do |t|
# t.column :content, :text
@@ -464,7 +498,7 @@ module ActiveRecord
# can't execute inside a transaction though, and for these situations
# you can turn the automatic transactions off.
#
- # class ChangeEnum < ActiveRecord::Migration
+ # class ChangeEnum < ActiveRecord::Migration[5.0]
# disable_ddl_transaction!
#
# def up
@@ -476,6 +510,32 @@ module ActiveRecord
# are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
class Migration
autoload :CommandRecorder, 'active_record/migration/command_recorder'
+ autoload :Compatibility, 'active_record/migration/compatibility'
+
+ # This must be defined before the inherited hook, below
+ class Current < Migration # :nodoc:
+ end
+
+ def self.inherited(subclass) # :nodoc:
+ super
+ if subclass.superclass == Migration
+ subclass.include Compatibility::Legacy
+ end
+ end
+
+ def self.[](version)
+ version = version.to_s
+ name = "V#{version.tr('.', '_')}"
+ unless Compatibility.const_defined?(name)
+ versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
+ raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
+ end
+ Compatibility.const_get(name)
+ end
+
+ def self.current_version
+ Rails.version.to_f
+ end
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
@@ -509,6 +569,10 @@ module ActiveRecord
attr_accessor :delegate # :nodoc:
attr_accessor :disable_ddl_transaction # :nodoc:
+ def nearest_delegate # :nodoc:
+ delegate || superclass.nearest_delegate
+ end
+
# Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
def check_pending!(connection = Base.connection)
raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
@@ -520,7 +584,7 @@ module ActiveRecord
FileUtils.cd Rails.root do
current_config = Base.connection_config
Base.clear_all_connections!
- system("bin/rake db:test:prepare")
+ system("bin/rails db:test:prepare")
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
Base.establish_connection(current_config)
end
@@ -535,7 +599,7 @@ module ActiveRecord
end
def method_missing(name, *args, &block) # :nodoc:
- (delegate || superclass.delegate).send(name, *args, &block)
+ nearest_delegate.send(name, *args, &block)
end
def migrate(direction)
@@ -575,7 +639,7 @@ module ActiveRecord
# and create the table 'apples' on the way up, and the reverse
# on the way down.
#
- # class FixTLMigration < ActiveRecord::Migration
+ # class FixTLMigration < ActiveRecord::Migration[5.0]
# def change
# revert do
# create_table(:horses) do |t|
@@ -594,7 +658,7 @@ module ActiveRecord
#
# require_relative '20121212123456_tenderlove_migration'
#
- # class FixupTLMigration < ActiveRecord::Migration
+ # class FixupTLMigration < ActiveRecord::Migration[5.0]
# def change
# revert TenderloveMigration
#
@@ -647,7 +711,7 @@ module ActiveRecord
# when the three columns 'first_name', 'last_name' and 'full_name' exist,
# even when migrating down:
#
- # class SplitNameMigration < ActiveRecord::Migration
+ # class SplitNameMigration < ActiveRecord::Migration[5.0]
# def change
# add_column :users, :first_name, :string
# add_column :users, :last_name, :string
@@ -1048,6 +1112,7 @@ module ActiveRecord
validate(@migrations)
Base.connection.initialize_schema_migrations_table
+ Base.connection.initialize_internal_metadata_table
end
def current_version
@@ -1105,45 +1170,58 @@ module ActiveRecord
private
+ # Used for running a specific migration.
def run_without_lock
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
+ execute_migration_in_transaction(migration, @direction)
+
+ record_environment
end
+ # Used for running multiple migrations up to or down to a certain value.
def migrate_without_lock
- if !target && @target_version && @target_version > 0
+ if invalid_target?
raise UnknownMigrationVersionError.new(@target_version)
end
runnable.each do |migration|
- Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
-
- begin
- 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
- end
+ execute_migration_in_transaction(migration, @direction)
end
+
+ record_environment
+ end
+
+ # Stores the current environment in the database.
+ def record_environment
+ return if down?
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
def ran?(migration)
migrated.include?(migration.version.to_i)
end
+ # Return true if a valid version is not provided.
+ def invalid_target?
+ !target && @target_version && @target_version > 0
+ end
+
def execute_migration_in_transaction(migration, direction)
+ return if down? && !migrated.include?(migration.version.to_i)
+ return if up? && migrated.include?(migration.version.to_i)
+
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
+
ddl_transaction(migration) do
migration.migrate(direction)
record_version_state_after_migrating(migration.version)
end
+ rescue => e
+ msg = "An error has occurred, "
+ msg << "this and " if use_transaction?(migration)
+ msg << "all later migrations canceled:\n\n#{e}"
+ raise StandardError, msg, e.backtrace
end
def target
@@ -1172,10 +1250,27 @@ module ActiveRecord
ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
else
migrated << version
- ActiveRecord::SchemaMigration.create!(:version => version.to_s)
+ ActiveRecord::SchemaMigration.create!(version: version.to_s)
end
end
+ def self.last_stored_environment
+ return nil if current_version == 0
+ raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
+
+ environment = ActiveRecord::InternalMetadata[:environment]
+ raise NoEnvironmentInSchemaError unless environment
+ environment
+ end
+
+ def self.current_environment
+ ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ end
+
+ def self.protected_environment?
+ ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
+ end
+
def up?
@direction == :up
end
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
new file mode 100644
index 0000000000..1b94573870
--- /dev/null
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -0,0 +1,91 @@
+module ActiveRecord
+ class Migration
+ module Compatibility # :nodoc: all
+ V5_0 = Current
+
+ module FourTwoShared
+ module TableDefinition
+ def timestamps(*, **options)
+ options[:null] = true if options[:null].nil?
+ super
+ end
+ end
+
+ def create_table(table_name, options = {})
+ if block_given?
+ super(table_name, options) do |t|
+ class << t
+ prepend TableDefinition
+ end
+ yield t
+ end
+ else
+ super
+ end
+ end
+
+ def add_timestamps(*, **options)
+ options[:null] = true if options[:null].nil?
+ super
+ end
+
+ def index_exists?(table_name, column_name, options = {})
+ column_names = Array(column_name).map(&:to_s)
+ options[:name] =
+ if options.key?(:name).present?
+ options[:name].to_s
+ else
+ index_name(table_name, column: column_names)
+ end
+ super
+ end
+
+ def remove_index(table_name, options = {})
+ options = { column: options } unless options.is_a?(Hash)
+ options[:name] = index_name_for_remove(table_name, options)
+ super(table_name, options)
+ end
+
+ private
+
+ def index_name_for_remove(table_name, options = {})
+ index_name = index_name(table_name, options)
+
+ unless index_name_exists?(table_name, index_name, true)
+ if options.is_a?(Hash) && options.has_key?(:name)
+ options_without_column = options.dup
+ options_without_column.delete :column
+ index_name_without_column = index_name(table_name, options_without_column)
+
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
+ end
+
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
+ end
+
+ index_name
+ end
+ end
+
+ class V4_2 < V5_0
+ # 4.2 is defined as a module because it needs to be shared with
+ # Legacy. When the time comes, V5_0 should be defined straight
+ # in its class.
+ include FourTwoShared
+ end
+
+ module Legacy
+ include FourTwoShared
+
+ def run(*)
+ ActiveSupport::Deprecation.warn \
+ "Directly inheriting from ActiveRecord::Migration is deprecated. " \
+ "Please specify the Rails release the migration was written for:\n" \
+ "\n" \
+ " class #{self.class.name} < ActiveRecord::Migration[4.2]"
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index e3f304b0af..f26c8471bc 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -44,6 +44,19 @@ module ActiveRecord
##
# :singleton-method:
+ # Accessor for the name of the internal metadata table. By default, the value is "active_record_internal_metadatas"
+ class_attribute :internal_metadata_table_name, instance_accessor: false
+ self.internal_metadata_table_name = "active_record_internal_metadatas"
+
+ ##
+ # :singleton-method:
+ # Accessor for an array of names of environments where destructive actions should be prohibited. By default,
+ # the value is ["production"]
+ class_attribute :protected_environments, instance_accessor: false
+ self.protected_environments = ["production"]
+
+ ##
+ # :singleton-method:
# Indicates whether table names should be the pluralized versions of the corresponding class names.
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
@@ -275,7 +288,7 @@ module ActiveRecord
# when just after creating a table you want to populate it with some default
# values, eg:
#
- # class CreateJobLevels < ActiveRecord::Migration
+ # class CreateJobLevels < ActiveRecord::Migration[5.0]
# def up
# create_table :job_levels do |t|
# t.integer :id
@@ -385,7 +398,7 @@ module ActiveRecord
If you'd like the new behavior today, you can add this line:
- attribute :#{column.name}, :rails_5_1_point#{array_arguments}
+ attribute :#{column.name}, :point#{array_arguments}
WARNING
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 46c6d8c293..522c35252f 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -215,7 +215,7 @@ module ActiveRecord
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
- became.instance_variable_set("@errors", errors)
+ became.errors.copy!(errors)
became
end
@@ -298,6 +298,7 @@ module ActiveRecord
# * \Validations are skipped.
# * \Callbacks are skipped.
# * +updated_at+/+updated_on+ are not updated.
+ # * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
#
# This method raises an ActiveRecord::ActiveRecordError when called on new
# objects, or when at least one of the attributes is marked as readonly.
@@ -566,5 +567,9 @@ module ActiveRecord
ensure
@_association_destroy_exception = nil
end
+
+ def belongs_to_touch_method
+ :touch
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 2744673c12..38916f7376 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -57,8 +57,10 @@ module ActiveRecord
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
require "active_record/base"
- console = ActiveSupport::Logger.new(STDERR)
- Rails.logger.extend ActiveSupport::Logger.broadcast console
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
+ console = ActiveSupport::Logger.new(STDERR)
+ Rails.logger.extend ActiveSupport::Logger.broadcast console
+ end
end
runner do
@@ -69,6 +71,7 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
+ self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types
end
end
@@ -134,8 +137,8 @@ Oops - You have a database configured, but it doesn't exist yet!
Here's how to get started:
1. Configure your database in config/database.yml.
- 2. Run `bin/rake db:create` to create the database.
- 3. Run `bin/rake db:setup` to load your database schema.
+ 2. Run `bin/rails db:create` to create the database.
+ 3. Run `bin/rails db:setup` to load your database schema.
end_warning
raise
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 5b1ea16f0b..d81d6b54b3 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -1,6 +1,16 @@
require 'active_record'
db_namespace = namespace :db do
+ desc "Set the environment value for the database"
+ task "environment:set" => [:environment, :load_config] do
+ ActiveRecord::InternalMetadata.create_table
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ end
+
+ task :check_protected_environments => [:environment, :load_config] do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+
task :load_config do
ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
@@ -18,24 +28,28 @@ db_namespace = namespace :db do
end
namespace :drop do
- task :all => :load_config do
+ task :all => [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.'
- task :drop => [:load_config] do
+ task :drop => [:load_config, :check_protected_environments] do
+ db_namespace["drop:_unsafe"].invoke
+ end
+
+ task "drop:_unsafe" => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.drop_current
end
namespace :purge do
- task :all => :load_config do
+ task :all => [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.purge_all
end
end
# desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
- task :purge => [:load_config] do
+ task :purge => [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.purge_current
end
@@ -169,7 +183,7 @@ db_namespace = namespace :db do
pending_migrations.each do |pending_migration|
puts ' %4d %s' % [pending_migration.version, pending_migration.name]
end
- abort %{Run `rake db:migrate` to update your database then try again.}
+ abort %{Run `rails db:migrate` to update your database then try again.}
end
end
@@ -351,7 +365,7 @@ db_namespace = namespace :db do
task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure)
# desc "Empty the test database"
- task :purge => %w(environment load_config) do
+ task :purge => %w(environment load_config check_protected_environments) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a549b28f16..37e18626b5 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -545,7 +545,7 @@ module ActiveRecord
end
end
- # returns either nil or the inverse association name that it finds.
+ # returns either false or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index f100476374..032b8d4c5d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -99,7 +99,7 @@ module ActiveRecord
end
substitutes = values.map do |(arel_attr, _)|
- [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])]
+ [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
@@ -132,7 +132,7 @@ module ActiveRecord
# ==== Examples
#
# users = User.where(name: 'Oscar')
- # users.create # => #<User id: 3, name: "oscar", ...>
+ # users.create # => #<User id: 3, name: "Oscar", ...>
#
# users.create(name: 'fxn')
# users.create # => #<User id: 4, name: "fxn", ...>
@@ -347,9 +347,8 @@ module ActiveRecord
# Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
# statement and sends it straight to the database. It does not instantiate the involved models and it does not
- # trigger Active Record callbacks or validations. Values passed to #update_all will not go through
- # Active Record's type-casting behavior. It should receive only values that can be passed as-is to the SQL
- # database.
+ # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
+ # Active Record's normal type casting and serialization.
#
# ==== Parameters
#
@@ -372,11 +371,11 @@ module ActiveRecord
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
stmt.table(table)
- stmt.key = table[primary_key]
if joins_values.any?
- @klass.connection.join_to_update(stmt, arel)
+ @klass.connection.join_to_update(stmt, arel, table[primary_key])
else
+ stmt.key = table[primary_key]
stmt.take(arel.limit)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index b1333f110c..e4e5d63006 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -37,7 +37,7 @@ module ActiveRecord
# for each different klass, and the delegations are compiled into that subclass only.
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
- :[], :&, :|, :+, :-, :sample, :reverse, :compact, to: :to_a
+ :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 435cef901b..3cbb12a09d 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -117,9 +117,9 @@ module ActiveRecord
#
def first(limit = nil)
if limit
- find_nth_with_limit(offset_index, limit)
+ find_nth_with_limit_and_offset(0, limit, offset: offset_index)
else
- find_nth(0, offset_index)
+ find_nth 0
end
end
@@ -169,7 +169,7 @@ module ActiveRecord
# Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
# Person.where(["user_name = :u", { u: user_name }]).second
def second
- find_nth(1, offset_index)
+ find_nth 1
end
# Same as #second but raises ActiveRecord::RecordNotFound if no record
@@ -185,7 +185,7 @@ module ActiveRecord
# Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
# Person.where(["user_name = :u", { u: user_name }]).third
def third
- find_nth(2, offset_index)
+ find_nth 2
end
# Same as #third but raises ActiveRecord::RecordNotFound if no record
@@ -201,7 +201,7 @@ module ActiveRecord
# Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
# Person.where(["user_name = :u", { u: user_name }]).fourth
def fourth
- find_nth(3, offset_index)
+ find_nth 3
end
# Same as #fourth but raises ActiveRecord::RecordNotFound if no record
@@ -217,7 +217,7 @@ module ActiveRecord
# Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
# Person.where(["user_name = :u", { u: user_name }]).fifth
def fifth
- find_nth(4, offset_index)
+ find_nth 4
end
# Same as #fifth but raises ActiveRecord::RecordNotFound if no record
@@ -233,7 +233,7 @@ module ActiveRecord
# Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
# Person.where(["user_name = :u", { u: user_name }]).forty_two
def forty_two
- find_nth(41, offset_index)
+ find_nth 41
end
# Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
@@ -442,6 +442,8 @@ module ActiveRecord
end
def find_some(ids)
+ return find_some_ordered(ids) unless order_values.present?
+
result = where(primary_key => ids).to_a
expected_size =
@@ -463,6 +465,21 @@ module ActiveRecord
end
end
+ def find_some_ordered(ids)
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
+
+ result = except(:limit, :offset).where(primary_key => ids).to_a
+
+ if result.size == ids.size
+ pk_type = @klass.type_for_attribute(primary_key)
+
+ records_by_id = result.index_by(&:id)
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
+ else
+ raise_record_not_found_exception!(ids, result.size, ids.size)
+ end
+ end
+
def find_take
if loaded?
@records.first
@@ -471,27 +488,39 @@ module ActiveRecord
end
end
- def find_nth(index, offset)
+ def find_nth(index, offset = nil)
if loaded?
@records[index]
else
- offset += index
- @offsets[offset] ||= find_nth_with_limit(offset, 1).first
+ # TODO: once the offset argument is removed we rely on offset_index
+ # within find_nth_with_limit, rather than pass it in via
+ # find_nth_with_limit_and_offset
+ if offset
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing an offset argument to find_nth is deprecated,
+ please use Relation#offset instead.
+ MSG
+ else
+ offset = offset_index
+ end
+ @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
end
end
def find_nth!(index)
- find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
- def find_nth_with_limit(offset, limit)
+ def find_nth_with_limit(index, limit)
+ # TODO: once the offset argument is removed from find_nth,
+ # find_nth_with_limit_and_offset can be merged into this method
relation = if order_values.empty? && primary_key
order(arel_table[primary_key].asc)
else
self
end
- relation = relation.offset(offset) unless offset.zero?
+ relation = relation.offset(index) unless index.zero?
relation.limit(limit).to_a
end
@@ -507,5 +536,16 @@ module ActiveRecord
end
end
end
+
+ private
+
+ def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
+ if loaded?
+ @records[index, limit]
+ else
+ index += offset
+ find_nth_with_limit(index, limit)
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb
index 92340216ed..8945cb0cc5 100644
--- a/activerecord/lib/active_record/relation/from_clause.rb
+++ b/activerecord/lib/active_record/relation/from_clause.rb
@@ -25,7 +25,7 @@ module ActiveRecord
end
def self.empty
- new(nil, nil)
+ @empty ||= new(nil, nil)
end
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index cb971eb255..396638d74d 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -141,6 +141,9 @@ module ActiveRecord
end
def merge_single_values
+ if relation.from_clause.empty?
+ relation.from_clause = other.from_clause
+ end
relation.lock_value ||= other.lock_value
unless other.create_with_value.blank?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 7a4bf5338d..716b1e8505 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -3,6 +3,7 @@ require "active_record/relation/query_attribute"
require "active_record/relation/where_clause"
require "active_record/relation/where_clause_factory"
require 'active_model/forbidden_attributes_protection'
+require 'active_support/core_ext/string/filters'
module ActiveRecord
module QueryMethods
@@ -53,16 +54,17 @@ module ActiveRecord
end
end
+ FROZEN_EMPTY_ARRAY = [].freeze
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- assert_mutability! # assert_mutability!
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values
+ @values[:#{name}] || FROZEN_EMPTY_ARRAY
+ end
+
+ def #{name}_values=(values)
+ assert_mutability!
+ @values[:#{name}] = values
+ end
CODE
end
@@ -97,11 +99,27 @@ module ActiveRecord
end
def bound_attributes
- from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
+ result = from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
+ if limit_value && !string_containing_comma?(limit_value)
+ result << Attribute.with_cast_value(
+ "LIMIT".freeze,
+ connection.sanitize_limit(limit_value),
+ Type::Value.new,
+ )
+ end
+ if offset_value
+ result << Attribute.with_cast_value(
+ "OFFSET".freeze,
+ offset_value.to_i,
+ Type::Value.new,
+ )
+ end
+ result
end
+ FROZEN_EMPTY_HASH = {}.freeze
def create_with_value # :nodoc:
- @values[:create_with] || {}
+ @values[:create_with] || FROZEN_EMPTY_HASH
end
alias extensions extending_values
@@ -411,10 +429,30 @@ module ActiveRecord
self
end
- # Performs a joins on +args+:
+ # Performs a joins on +args+. The given symbol(s) should match the name of
+ # the association(s).
#
# User.joins(:posts)
- # # SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
+ # # SELECT "users".*
+ # # FROM "users"
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
+ #
+ # Multiple joins:
+ #
+ # User.joins(:posts, :account)
+ # # SELECT "users".*
+ # # FROM "users"
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
+ # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
+ #
+ # Nested joins:
+ #
+ # User.joins(posts: [:comments])
+ # # SELECT "users".*
+ # # FROM "users"
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
+ # # INNER JOIN "comments" "comments_posts"
+ # # ON "comments_posts"."post_id" = "posts"."id"
#
# You can use strings in order to customize your joins:
#
@@ -613,16 +651,18 @@ module ActiveRecord
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
# present). Neither relation may have a #limit, #offset, or #distinct set.
#
- # Post.where("id = 1").or(Post.where("id = 2"))
- # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
+ # Post.where("id = 1").or(Post.where("author_id = 3"))
+ # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3'))
#
def or(other)
spawn.or!(other)
end
def or!(other) # :nodoc:
- unless structurally_compatible_for_or?(other)
- raise ArgumentError, 'Relation passed to #or must be structurally compatible'
+ incompatible_values = structurally_incompatible_values_for_or(other)
+
+ unless incompatible_values.empty?
+ raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
end
self.where_clause = self.where_clause.or(other.where_clause)
@@ -657,6 +697,13 @@ module ActiveRecord
end
def limit!(value) # :nodoc:
+ if string_containing_comma?(value)
+ # Remove `string_containing_comma?` when removing this deprecation
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
+ Passing a string to limit in the form "1,2" is deprecated and will be
+ removed in Rails 5.1. Please call `offset` explicitly instead.
+ WARNING
+ end
self.limit_value = value
self
end
@@ -907,8 +954,14 @@ module ActiveRecord
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
- arel.take(connection.sanitize_limit(limit_value)) if limit_value
- arel.skip(offset_value.to_i) if offset_value
+ if limit_value
+ if string_containing_comma?(limit_value)
+ arel.take(connection.sanitize_limit(limit_value))
+ else
+ arel.take(Arel::Nodes::BindParam.new)
+ end
+ end
+ arel.skip(Arel::Nodes::BindParam.new) if offset_value
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
build_order(arel)
@@ -1138,10 +1191,10 @@ module ActiveRecord
end
end
- def structurally_compatible_for_or?(other)
- Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } &&
- (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } &&
- (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") != other.send("#{m}_clause") }
+ def structurally_incompatible_values_for_or(other)
+ Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
+ (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
+ (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end
def new_where_clause
@@ -1157,5 +1210,9 @@ module ActiveRecord
def new_from_clause
Relation::FromClause.empty
end
+
+ def string_containing_comma?(value)
+ ::String === value && value.include?(",")
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/record_fetch_warning.rb b/activerecord/lib/active_record/relation/record_fetch_warning.rb
index 0a1814b3dd..dbd08811fa 100644
--- a/activerecord/lib/active_record/relation/record_fetch_warning.rb
+++ b/activerecord/lib/active_record/relation/record_fetch_warning.rb
@@ -24,9 +24,7 @@ module ActiveRecord
end
# :stopdoc:
- ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
- payload = args.last
-
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
QueryRegistry.queries << payload[:sql]
end
# :startdoc:
@@ -34,14 +32,14 @@ module ActiveRecord
class QueryRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
- attr_accessor :queries
+ attr_reader :queries
def initialize
- reset
+ @queries = []
end
def reset
- @queries = []
+ @queries.clear
end
end
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 1f000b3f0f..2c2d6cfa47 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -81,7 +81,7 @@ module ActiveRecord
end
def self.empty
- new([], [])
+ @empty ||= new([], [])
end
protected
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index a81ff98e49..dbf172a577 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -22,6 +22,7 @@ module ActiveRecord
parts = predicate_builder.build_from_hash(attributes)
when Arel::Nodes::Node
parts = [opts]
+ binds = other
else
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 0c15f45db9..2bfc5ff7ae 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -18,6 +18,9 @@ module ActiveRecord
# sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
# # => "name='foo''bar' and group_id=4"
#
+ # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
# sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
# # => "name='foo''bar' and group_id='4'"
#
@@ -40,6 +43,9 @@ module ActiveRecord
# sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
# # => "name=NULL and group_id=4"
#
+ # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
+ # # => "name=NULL and group_id=4"
+ #
# Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
#
@@ -140,6 +146,9 @@ module ActiveRecord
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
# # => "name='foo''bar' and group_id=4"
#
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
# # => "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
@@ -204,7 +213,7 @@ module ActiveRecord
end
# TODO: Deprecate this
- def quoted_id
+ def quoted_id # :nodoc:
self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
end
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 31dd584538..784a02d2c3 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -27,7 +27,7 @@ module ActiveRecord
#
# ActiveRecord::Schema is only supported by database adapters that also
# support migrations, the two features being very similar.
- class Schema < Migration
+ class Schema < Migration::Current
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (
@@ -51,6 +51,9 @@ module ActiveRecord
initialize_schema_migrations_table
connection.assume_migrated_upto_version(info[:version], migrations_paths)
end
+
+ ActiveRecord::InternalMetadata.create_table
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
private
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2362dae9fc..65005bd44b 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -254,7 +254,7 @@ HEADER
end
def ignored?(table_name)
- [ActiveRecord::Base.schema_migrations_table_name, ignore_tables].flatten.any? do |ignored|
+ [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored|
ignored === remove_prefix_and_suffix(table_name)
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 51b9b17395..ee4c71f304 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -9,7 +9,7 @@ module ActiveRecord
class SchemaMigration < ActiveRecord::Base # :nodoc:
class << self
def primary_key
- nil
+ "version"
end
def table_name
@@ -31,8 +31,8 @@ module ActiveRecord
connection.create_table(table_name, id: false) do |t|
t.column :version, :string, version_options
+ t.index :version, unique: true, name: index_name
end
- connection.add_index table_name, :version, unique: true, name: index_name
end
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index e395970dc6..7794af8ca4 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -11,11 +11,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- ScopeRegistry.value_for(:current_scope, self.to_s)
+ ScopeRegistry.value_for(:current_scope, self)
end
def current_scope=(scope) #:nodoc:
- ScopeRegistry.set_value_for(:current_scope, self.to_s, scope)
+ ScopeRegistry.set_value_for(:current_scope, self, scope)
end
# Collects attributes from scopes that should be applied when creating
@@ -53,18 +53,18 @@ module ActiveRecord
# following code:
#
# registry = ActiveRecord::Scoping::ScopeRegistry
- # registry.set_value_for(:current_scope, "Board", some_new_scope)
+ # registry.set_value_for(:current_scope, Board, some_new_scope)
#
# Now when you run:
#
- # registry.value_for(:current_scope, "Board")
+ # 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)
+ # Board, some_new_scope)
class ScopeRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
@@ -74,16 +74,22 @@ module ActiveRecord
@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)
+ # Obtains the value for a given +scope_type+ and +model+.
+ def value_for(scope_type, model)
raise_invalid_scope_type!(scope_type)
- @registry[scope_type][variable_name]
+ klass = model
+ base = model.base_class
+ while klass <= base
+ value = @registry[scope_type][klass.name]
+ return value if value
+ klass = klass.superclass
+ end
end
- # Sets the +value+ for a given +scope_type+ and +variable_name+.
- def set_value_for(scope_type, variable_name, value)
+ # Sets the +value+ for a given +scope_type+ and +model+.
+ def set_value_for(scope_type, model, value)
raise_invalid_scope_type!(scope_type)
- @registry[scope_type][variable_name] = value
+ @registry[scope_type][model.name] = value
end
private
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index cdcb73382f..8baf3b8044 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -122,11 +122,11 @@ module ActiveRecord
end
def ignore_default_scope? # :nodoc:
- ScopeRegistry.value_for(:ignore_default_scope, self)
+ ScopeRegistry.value_for(:ignore_default_scope, base_class)
end
def ignore_default_scope=(ignore) # :nodoc:
- ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
+ ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index c0c29a618c..8f52e9068a 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -42,6 +42,22 @@ module ActiveRecord
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
+ def check_protected_environments!
+ unless ENV['DISABLE_DATABASE_ENVIRONMENT_CHECK']
+ current = ActiveRecord::Migrator.current_environment
+ stored = ActiveRecord::Migrator.last_stored_environment
+
+ if ActiveRecord::Migrator.protected_environment?
+ raise ActiveRecord::ProtectedEnvironmentError.new(stored)
+ end
+
+ if stored && stored != current
+ raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
+ end
+ end
+ rescue ActiveRecord::NoDatabaseError
+ end
+
def register_task(pattern, task)
@tasks ||= {}
@tasks[pattern] = task
@@ -204,6 +220,8 @@ module ActiveRecord
else
raise ArgumentError, "unknown format #{format.inspect}"
end
+ ActiveRecord::InternalMetadata.create_table
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
def load_schema_for(*args)
@@ -232,7 +250,7 @@ module ActiveRecord
def check_schema_file(filename)
unless File.exist?(filename)
- message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
+ message = %{#{filename} doesn't exist yet. Run `rails 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
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 100df9c6c6..7a49322e06 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -94,8 +94,6 @@ module ActiveRecord
ArJdbcMySQL::Error
elsif defined?(Mysql2)
Mysql2::Error
- elsif defined?(Mysql)
- Mysql::Error
else
StandardError
end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index cd7d949239..8b4874044c 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -56,9 +56,9 @@ module ActiveRecord
args = ['-s', '-x', '-O', '-f', filename]
unless search_path.blank?
- args << search_path.split(',').map do |part|
+ args += search_path.split(',').map do |part|
"--schema=#{part.strip}"
- end.join(' ')
+ end
end
args << configuration['database']
run_cmd('pg_dump', args, 'dumping')
diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb
index 4352a0ffea..9a80a63e28 100644
--- a/activerecord/lib/active_record/touch_later.rb
+++ b/activerecord/lib/active_record/touch_later.rb
@@ -16,6 +16,13 @@ module ActiveRecord
surreptitiously_touch @_defer_touch_attrs
self.class.connection.add_transaction_record self
+
+ # touch the parents as we are not calling the after_save callbacks
+ self.class.reflect_on_all_associations(:belongs_to).each do |r|
+ if touch = r.options[:touch]
+ ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, r.foreign_key, r.name, touch, :touch_later)
+ end
+ end
end
def touch(*names, time: nil) # :nodoc:
@@ -26,6 +33,7 @@ module ActiveRecord
end
private
+
def surreptitiously_touch(attrs)
attrs.each { |attr| write_attribute attr, @_touch_time }
clear_attribute_changes attrs
@@ -33,9 +41,8 @@ module ActiveRecord
def touch_deferred_attributes
if has_defer_touch_attrs? && persisted?
- @_touching_delayed_records = true
touch(*@_defer_touch_attrs, time: @_touch_time)
- @_touching_delayed_records, @_defer_touch_attrs, @_touch_time = nil, nil, nil
+ @_defer_touch_attrs, @_touch_time = nil, nil
end
end
@@ -43,8 +50,9 @@ module ActiveRecord
defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
end
- def touching_delayed_records?
- defined?(@_touching_delayed_records) && @_touching_delayed_records
+ def belongs_to_touch_method
+ :touch_later
end
+
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 8de82feae3..38ab1f3fc6 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -233,6 +233,24 @@ module ActiveRecord
set_callback(:commit, :after, *args, &block)
end
+ # Shortcut for +after_commit :hook, on: :create+.
+ def after_create_commit(*args, &block)
+ set_options_for_callbacks!(args, on: :create)
+ set_callback(:commit, :after, *args, &block)
+ end
+
+ # Shortcut for +after_commit :hook, on: :update+.
+ def after_update_commit(*args, &block)
+ set_options_for_callbacks!(args, on: :update)
+ set_callback(:commit, :after, *args, &block)
+ end
+
+ # Shortcut for +after_commit :hook, on: :destroy+.
+ def after_destroy_commit(*args, &block)
+ set_options_for_callbacks!(args, on: :destroy)
+ set_callback(:commit, :after, *args, &block)
+ end
+
# This callback is called after a create, update, or destroy are rolled back.
#
# Please check the documentation of #after_commit for options.
@@ -268,9 +286,11 @@ module ActiveRecord
private
- def set_options_for_callbacks!(args)
- options = args.last
- if options.is_a?(Hash) && options[:on]
+ def set_options_for_callbacks!(args, enforced_options = {})
+ options = args.extract_options!.merge!(enforced_options)
+ args << options
+
+ if options[:on]
fire_on = Array(options[:on])
assert_valid_transaction_action(fire_on)
options[:if] = Array(options[:if])
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index aa2794f120..a376e2a17f 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -73,15 +73,18 @@ module ActiveRecord
value = value.to_s[0, column.limit]
end
- value = Arel::Nodes::Quoted.new(value)
-
comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
- klass.unscoped.where(comparison)
+ if value.nil?
+ klass.unscoped.where(comparison)
+ else
+ bind = Relation::QueryAttribute.new(attribute.to_s, value, Type::Value.new)
+ klass.unscoped.where(comparison, bind)
+ end
rescue RangeError
klass.none
end
@@ -231,7 +234,6 @@ module ActiveRecord
#
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
#
- # * ActiveRecord::ConnectionAdapters::MysqlAdapter.
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
index fadab2a1e6..5f7201cfe1 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
@@ -1,4 +1,4 @@
-class <%= migration_class_name %> < ActiveRecord::Migration
+class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
def change
create_table :<%= table_name %><%= primary_key_type %> do |t|
<% attributes.each do |attribute| -%>
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index 23a377db6a..107f107dc4 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -1,4 +1,4 @@
-class <%= migration_class_name %> < ActiveRecord::Migration
+class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
<%- if migration_action == 'add' -%>
def change
<% attributes.each do |attribute| -%>
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 395951ac9d..7395839fca 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -29,23 +29,36 @@ module ActiveRecord
template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
- def attributes_with_index
- attributes.select { |a| !a.reference? && a.has_index? }
- end
-
- def accessible_attributes
- attributes.reject(&:reference?)
- end
-
hook_for :test_framework
protected
+ def attributes_with_index
+ attributes.select { |a| !a.reference? && a.has_index? }
+ end
+
# Used by the migration template to determine the parent name of the model
def parent_class_name
- options[:parent] || "ActiveRecord::Base"
+ options[:parent] || determine_default_parent_class
end
+ def determine_default_parent_class
+ application_record = nil
+
+ in_root do
+ application_record = if mountable_engine?
+ File.exist?("app/models/#{namespaced_path}/application_record.rb")
+ else
+ File.exist?('app/models/application_record.rb')
+ end
+ end
+
+ if application_record
+ "ApplicationRecord"
+ else
+ "ActiveRecord::Base"
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index abe1ea7c90..0ee147cdba 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -85,7 +85,7 @@ module ActiveRecord
end
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_charset
assert_not_nil @connection.charset
assert_not_equal 'character_set_database', @connection.charset
@@ -254,7 +254,7 @@ module ActiveRecord
end
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :SQLite3Adapter)
+ if current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
def test_tables_returning_both_tables_and_views_is_deprecated
assert_deprecated { @connection.tables }
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
deleted file mode 100644
index 8a7a0bb25d..0000000000
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-require "cases/helper"
-require 'support/connection_helper'
-
-class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase
- include ConnectionHelper
-
- def setup
- ActiveRecord::Base.connection.singleton_class.class_eval do
- alias_method :execute_without_stub, :execute
- def execute(sql, name = nil) return sql end
- end
- end
-
- teardown do
- reset_connection
- end
-
- def test_add_index
- # add_index calls data_source_exists? and index_name_exists? which can't work since execute is stubbed
- def (ActiveRecord::Base.connection).data_source_exists?(*); true; 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)
-
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
- assert_equal expected, add_index(:people, :last_name, :length => 10)
-
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
-
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
-
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
-
- %w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
- assert_equal expected, add_index(:people, :last_name, :type => type)
- end
-
- %w(btree hash).each do |using|
- expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
- assert_equal expected, add_index(:people, :last_name, :using => using)
- end
-
- expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
- assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
-
- expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
- assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
-
- assert_raise ArgumentError do
- add_index(:people, :last_name, algorithm: :coyp)
- end
-
- 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)
- end
-
- def test_index_in_create
- def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
-
- %w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
- actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
- t.index :last_name, type: type
- end
- assert_equal expected, actual
- end
-
- expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
- actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
- t.index :last_name, length: 10, using: :btree
- end
- assert_equal expected, actual
- end
-
- def test_index_in_bulk_change
- def (ActiveRecord::Base.connection).data_source_exists?(*); true; end
- def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
-
- %w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
- actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
- t.index :last_name, type: type
- end
- assert_equal expected, actual
- end
-
- expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
- actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
- t.index :last_name, length: 10, using: :btree, algorithm: :copy
- end
- assert_equal expected, actual
- end
-
- def test_drop_table
- assert_equal "DROP TABLE `people`", drop_table(:people)
- end
-
- def test_create_mysql_database_with_encoding
- assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
- assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
- end
-
- def test_recreate_mysql_database_with_encoding
- create_database(:luca, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
- end
-
- def test_add_column
- assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
- end
-
- def test_add_column_with_limit
- assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
- end
-
- def test_drop_table_with_specific_database
- assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
- end
-
- def test_add_timestamps
- with_real_execute do
- begin
- ActiveRecord::Base.connection.create_table :delete_me
- ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
- assert column_present?('delete_me', 'updated_at', 'datetime')
- assert column_present?('delete_me', 'created_at', 'datetime')
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
- end
- end
- end
-
- def test_remove_timestamps
- with_real_execute do
- begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- t.timestamps null: true
- end
- ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true }
- assert !column_present?('delete_me', 'updated_at', 'datetime')
- assert !column_present?('delete_me', 'created_at', 'datetime')
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
- end
- end
- end
-
- def test_indexes_in_create
- ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
- ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
-
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
- actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
- t.index :zip
- end
-
- assert_equal expected, actual
- end
-
- private
- def with_real_execute
- 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
- 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
-
- def column_present?(table_name, column_name, type)
- results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
- results.first && results.first['Type'] == type
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
deleted file mode 100644
index 98d44315dd..0000000000
--- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require "cases/helper"
-
-class MysqlCaseSensitivityTest < ActiveRecord::MysqlTestCase
- class CollationTest < ActiveRecord::Base
- end
-
- repair_validations(CollationTest)
-
- def test_columns_include_collation_different_from_table
- assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
- assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
- end
-
- def test_case_sensitive
- assert !CollationTest.columns_hash['string_ci_column'].case_sensitive?
- assert CollationTest.columns_hash['string_cs_column'].case_sensitive?
- end
-
- def test_case_insensitive_comparison_for_ci_column
- CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false)
- CollationTest.create!(:string_ci_column => 'A')
- invalid = CollationTest.new(:string_ci_column => 'a')
- queries = assert_sql { invalid.save }
- ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
- assert_no_match(/lower/i, ci_uniqueness_query)
- end
-
- def test_case_insensitive_comparison_for_cs_column
- CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false)
- CollationTest.create!(:string_cs_column => 'A')
- invalid = CollationTest.new(:string_cs_column => 'a')
- queries = assert_sql { invalid.save }
- cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
- assert_match(/lower/i, cs_uniqueness_query)
- end
-
- def test_case_sensitive_comparison_for_ci_column
- CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true)
- CollationTest.create!(:string_ci_column => 'A')
- invalid = CollationTest.new(:string_ci_column => 'A')
- queries = assert_sql { invalid.save }
- ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
- assert_match(/binary/i, ci_uniqueness_query)
- end
-
- def test_case_sensitive_comparison_for_cs_column
- CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true)
- CollationTest.create!(:string_cs_column => 'A')
- invalid = CollationTest.new(:string_cs_column => 'A')
- queries = assert_sql { invalid.save }
- cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
- assert_no_match(/binary/i, cs_uniqueness_query)
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql/charset_collation_test.rb
deleted file mode 100644
index f2117a97e6..0000000000
--- a/activerecord/test/cases/adapters/mysql/charset_collation_test.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require "cases/helper"
-require 'support/schema_dumping_helper'
-
-class MysqlCharsetCollationTest < ActiveRecord::MysqlTestCase
- include SchemaDumpingHelper
- self.use_transactional_tests = false
-
- setup do
- @connection = ActiveRecord::Base.connection
- @connection.create_table :charset_collations, force: true do |t|
- t.string :string_ascii_bin, charset: 'ascii', collation: 'ascii_bin'
- t.text :text_ucs2_unicode_ci, charset: 'ucs2', collation: 'ucs2_unicode_ci'
- end
- end
-
- teardown do
- @connection.drop_table :charset_collations, if_exists: true
- end
-
- test "string column with charset and collation" do
- column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
- assert_equal :string, column.type
- assert_equal 'ascii_bin', column.collation
- end
-
- test "text column with charset and collation" do
- column = @connection.columns(:charset_collations).find { |c| c.name == 'text_ucs2_unicode_ci' }
- assert_equal :text, column.type
- assert_equal 'ucs2_unicode_ci', column.collation
- end
-
- test "add column with charset and collation" do
- @connection.add_column :charset_collations, :title, :string, charset: 'utf8', collation: 'utf8_bin'
-
- column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
- assert_equal :string, column.type
- assert_equal 'utf8_bin', column.collation
- end
-
- test "change column with charset and collation" do
- @connection.add_column :charset_collations, :description, :string, charset: 'utf8', collation: 'utf8_unicode_ci'
- @connection.change_column :charset_collations, :description, :text, charset: 'utf8', collation: 'utf8_general_ci'
-
- column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
- assert_equal :text, column.type
- assert_equal 'utf8_general_ci', column.collation
- end
-
- test "schema dump includes collation" do
- output = dump_table_schema("charset_collations")
- assert_match %r{t.string\s+"string_ascii_bin",\s+limit: 255,\s+collation: "ascii_bin"$}, output
- assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
deleted file mode 100644
index 390dd15b92..0000000000
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-require "cases/helper"
-require 'support/connection_helper'
-require 'support/ddl_helper'
-
-class MysqlConnectionTest < ActiveRecord::MysqlTestCase
- include ConnectionHelper
- include DdlHelper
-
- class Klass < ActiveRecord::Base
- end
-
- def setup
- super
- @connection = ActiveRecord::Base.connection
- end
-
- def test_mysql_reconnect_attribute_after_connection_with_reconnect_true
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true}))
- assert ActiveRecord::Base.connection.raw_connection.reconnect
- end
- end
-
- unless ARTest.connection_config['arunit']['socket']
- def test_connect_with_url
- run_without_connection do
- ar_config = ARTest.connection_config['arunit']
-
- url = "mysql://#{ar_config["username"]}:#{ar_config["password"]}@localhost/#{ar_config["database"]}"
- Klass.establish_connection(url)
- assert_equal ar_config['database'], Klass.connection.current_database
- end
- end
- end
-
- def test_mysql_reconnect_attribute_after_connection_with_reconnect_false
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false}))
- assert !ActiveRecord::Base.connection.raw_connection.reconnect
- end
- end
-
- def test_no_automatic_reconnection_after_timeout
- assert @connection.active?
- @connection.update('set @@wait_timeout=1')
- sleep 2
- assert !@connection.active?
-
- # Repair all fixture connections so other tests won't break.
- @fixture_connections.each(&:verify!)
- end
-
- def test_successful_reconnection_after_timeout_with_manual_reconnect
- assert @connection.active?
- @connection.update('set @@wait_timeout=1')
- sleep 2
- @connection.reconnect!
- assert @connection.active?
- end
-
- def test_successful_reconnection_after_timeout_with_verify
- assert @connection.active?
- @connection.update('set @@wait_timeout=1')
- sleep 2
- @connection.verify!
- assert @connection.active?
- end
-
- def test_bind_value_substitute
- bind_param = @connection.substitute_at('foo')
- assert_equal Arel.sql('?'), bind_param.to_sql
- end
-
- def test_exec_no_binds
- with_example_table do
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 0, result.rows.length
- assert_equal 2, result.columns.length
- assert_equal %w{ id data }, result.columns
-
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
-
- # if there are no bind parameters, it will return a string (due to
- # the libmysql api)
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
-
- assert_equal [['1', 'foo']], result.rows
- end
- end
-
- def test_exec_with_binds
- with_example_table do
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::Value.new)])
-
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
-
- assert_equal [[1, 'foo']], result.rows
- end
- end
-
- def test_exec_typecasts_bind_vals
- with_example_table do
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
-
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [bind])
-
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
-
- assert_equal [[1, 'foo']], result.rows
- end
- end
-
- def test_mysql_connection_collation_is_configured
- assert_equal 'utf8_unicode_ci', @connection.show_variable('collation_connection')
- assert_equal 'utf8_general_ci', ARUnit2Model.connection.show_variable('collation_connection')
- end
-
- def test_mysql_default_in_strict_mode
- result = @connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [["STRICT_ALL_TABLES"]], result.rows
- end
-
- def test_mysql_strict_mode_disabled
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false}))
- result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [['']], result.rows
- end
- end
-
- def test_mysql_strict_mode_specified_default
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
- global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
- session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal global_sql_mode.rows, session_sql_mode.rows
- end
- end
-
- def test_mysql_set_session_variable
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
- session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
- assert_equal 3, session_mode.rows.first.first.to_i
- end
- end
-
- def test_mysql_sql_mode_variable_overrides_strict_mode
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
- result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode'
- assert_not_equal [['STRICT_ALL_TABLES']], result.rows
- end
- end
-
- def test_mysql_set_session_variable_to_default
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
- global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
- session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
- assert_equal global_mode.rows, session_mode.rows
- end
- end
-
- def test_get_and_release_advisory_lock
- lock_name = "test_lock_name"
-
- got_lock = @connection.get_advisory_lock(lock_name)
- assert got_lock, "get_advisory_lock should have returned true but it didn't"
-
- assert_equal test_lock_free(lock_name), false,
- "expected the test advisory lock to be held but it wasn't"
-
- released_lock = @connection.release_advisory_lock(lock_name)
- assert released_lock, "expected release_advisory_lock to return true but it didn't"
-
- assert test_lock_free(lock_name), 'expected the test lock to be available after releasing'
- end
-
- def test_release_non_existent_advisory_lock
- lock_name = "fake_lock_name"
- released_non_existent_lock = @connection.release_advisory_lock(lock_name)
- assert_equal released_non_existent_lock, false,
- 'expected release_advisory_lock to return false when there was no lock to release'
- end
-
- protected
-
- def test_lock_free(lock_name)
- @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == '1'
- end
-
- private
-
- def with_example_table(&block)
- definition ||= <<-SQL
- `id` int auto_increment PRIMARY KEY,
- `data` varchar(255)
- SQL
- super(@connection, 'ex', definition, &block)
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb
deleted file mode 100644
index 743f6436e4..0000000000
--- a/activerecord/test/cases/adapters/mysql/consistency_test.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require "cases/helper"
-
-class MysqlConsistencyTest < ActiveRecord::MysqlTestCase
- self.use_transactional_tests = false
-
- class Consistency < ActiveRecord::Base
- self.table_name = "mysql_consistency"
- end
-
- setup do
- @old_emulate_booleans = ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans
- ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
-
- @connection = ActiveRecord::Base.connection
- @connection.clear_cache!
- @connection.create_table("mysql_consistency") do |t|
- t.boolean "a_bool"
- t.string "a_string"
- end
- Consistency.reset_column_information
- Consistency.create!
- end
-
- teardown do
- ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = @old_emulate_booleans
- @connection.drop_table "mysql_consistency"
- end
-
- test "boolean columns with random value type cast to 0 when emulate_booleans is false" do
- with_new = Consistency.new
- with_last = Consistency.last
- with_new.a_bool = 'wibble'
- with_last.a_bool = 'wibble'
-
- assert_equal 0, with_new.a_bool
- assert_equal 0, with_last.a_bool
- end
-
- test "string columns call #to_s" do
- with_new = Consistency.new
- with_last = Consistency.last
- thing = Object.new
- with_new.a_string = thing
- with_last.a_string = thing
-
- assert_equal thing.to_s, with_new.a_string
- assert_equal thing.to_s, with_last.a_string
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb
deleted file mode 100644
index ef8ee0a6e3..0000000000
--- a/activerecord/test/cases/adapters/mysql/enum_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require "cases/helper"
-
-class MysqlEnumTest < ActiveRecord::MysqlTestCase
- class EnumTest < ActiveRecord::Base
- end
-
- def test_enum_limit
- assert_equal 6, EnumTest.columns.first.limit
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/explain_test.rb b/activerecord/test/cases/adapters/mysql/explain_test.rb
deleted file mode 100644
index c44c1e6648..0000000000
--- a/activerecord/test/cases/adapters/mysql/explain_test.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require "cases/helper"
-require 'models/developer'
-require 'models/computer'
-
-class MysqlExplainTest < ActiveRecord::MysqlTestCase
- fixtures :developers
-
- def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- end
-
- def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
deleted file mode 100644
index d2ce48fc00..0000000000
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-require "cases/helper"
-require 'support/ddl_helper'
-
-module ActiveRecord
- module ConnectionAdapters
- class MysqlAdapterTest < ActiveRecord::MysqlTestCase
- include DdlHelper
-
- def setup
- @conn = ActiveRecord::Base.connection
- end
-
- def test_bad_connection_mysql
- assert_raise ActiveRecord::NoDatabaseError do
- configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
- connection = ActiveRecord::Base.mysql_connection(configuration)
- connection.drop_table 'ex', if_exists: true
- end
- end
-
- def test_valid_column
- with_example_table do
- column = @conn.columns('ex').find { |col| col.name == 'id' }
- assert @conn.valid_type?(column.type)
- end
- end
-
- def test_invalid_column
- assert_not @conn.valid_type?(:foobar)
- end
-
- def test_client_encoding
- assert_equal Encoding::UTF_8, @conn.client_encoding
- end
-
- def test_exec_insert_number
- with_example_table do
- insert(@conn, 'number' => 10)
-
- result = @conn.exec_query('SELECT number FROM ex WHERE number = 10')
-
- assert_equal 1, result.rows.length
- # if there are no bind parameters, it will return a string (due to
- # the libmysql api)
- assert_equal '10', result.rows.last.last
- end
- end
-
- def test_exec_insert_string
- with_example_table do
- str = 'ã„ãŸã ãã¾ã™ï¼'
- insert(@conn, 'number' => 10, 'data' => str)
-
- result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10')
-
- value = result.rows.last.last
-
- # FIXME: this should probably be inside the mysql AR adapter?
- value.force_encoding(@conn.client_encoding)
-
- # The strings in this file are utf-8, so transcode to utf-8
- value.encode!(Encoding::UTF_8)
-
- assert_equal str, value
- end
- end
-
- def test_composite_primary_key
- with_example_table '`id` INT, `number` INT, foo INT, PRIMARY KEY (`id`, `number`)' do
- assert_nil @conn.primary_key('ex')
- end
- end
-
- def test_tinyint_integer_typecasting
- with_example_table '`status` TINYINT(4)' do
- insert(@conn, { 'status' => 2 }, 'ex')
-
- result = @conn.exec_query('SELECT status FROM ex')
-
- assert_equal 2, result.column_types['status'].deserialize(result.last['status'])
- end
- end
-
- def test_supports_extensions
- assert_not @conn.supports_extensions?, 'does not support extensions'
- end
-
- def test_respond_to_enable_extension
- assert @conn.respond_to?(:enable_extension)
- end
-
- def test_respond_to_disable_extension
- assert @conn.respond_to?(:disable_extension)
- end
-
- private
- def insert(ctx, data, table='ex')
- binds = data.map { |name, value|
- Relation::QueryAttribute.new(name, value, Type::Value.new)
- }
- columns = binds.map(&:name)
-
- sql = "INSERT INTO #{table} (#{columns.join(", ")})
- VALUES (#{(['?'] * columns.length).join(', ')})"
-
- ctx.exec_insert(sql, 'SQL', binds)
- end
-
- def with_example_table(definition = nil, &block)
- definition ||= <<-SQL
- `id` int auto_increment PRIMARY KEY,
- `number` integer,
- `data` varchar(255)
- SQL
- super(@conn, 'ex', definition, &block)
- end
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
deleted file mode 100644
index 2024aa36ab..0000000000
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require "cases/helper"
-
-class MysqlQuotingTest < ActiveRecord::MysqlTestCase
- def setup
- @conn = ActiveRecord::Base.connection
- end
-
- def test_type_cast_true
- assert_equal 1, @conn.type_cast(true)
- end
-
- def test_type_cast_false
- assert_equal 0, @conn.type_cast(false)
- end
-
- def test_quoted_date_precision_for_gte_564
- @conn.stubs(:full_version).returns('5.6.4')
- @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_match(/\.000001\z/, @conn.quoted_date(t))
- end
-
- def test_quoted_date_precision_for_lt_564
- @conn.stubs(:full_version).returns('5.6.3')
- @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_no_match(/\.000001\z/, @conn.quoted_date(t))
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
deleted file mode 100644
index 4ea1d9ad36..0000000000
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-require "cases/helper"
-
-# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
-# reserved word names (ie: group, order, values, etc...)
-class MysqlReservedWordTest < ActiveRecord::MysqlTestCase
- class Group < ActiveRecord::Base
- Group.table_name = 'group'
- belongs_to :select
- has_one :values
- end
-
- class Select < ActiveRecord::Base
- Select.table_name = 'select'
- has_many :groups
- end
-
- class Values < ActiveRecord::Base
- Values.table_name = 'values'
- end
-
- class Distinct < ActiveRecord::Base
- Distinct.table_name = 'distinct'
- has_and_belongs_to_many :selects
- has_many :values, :through => :groups
- end
-
- def setup
- @connection = ActiveRecord::Base.connection
-
- # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
- # will fail with these table names if these test cases fail
-
- create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int',
- 'select'=>'id int auto_increment primary key',
- 'values'=>'id int auto_increment primary key, group_id int',
- 'distinct'=>'id int auto_increment primary key',
- 'distinct_select'=>'distinct_id int, select_id int'
- end
-
- teardown do
- drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
- end
-
- # create tables with reserved-word names and columns
- def test_create_tables
- assert_nothing_raised {
- @connection.create_table :order do |t|
- t.column :group, :string
- end
- }
- end
-
- # rename tables with reserved-word names
- def test_rename_tables
- assert_nothing_raised { @connection.rename_table(:group, :order) }
- end
-
- # alter column with a reserved-word name in a table with a reserved-word name
- def test_change_columns
- assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') }
- #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
- assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) }
- assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
- end
-
- # introspect table with reserved word name
- def test_introspect
- assert_nothing_raised { @connection.columns(:group) }
- assert_nothing_raised { @connection.indexes(:group) }
- end
-
- #fixtures
- self.use_instantiated_fixtures = true
- self.use_transactional_tests = false
-
- #activerecord model class with reserved-word table name
- def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- x = nil
- assert_nothing_raised { x = Group.new }
- x.order = 'x'
- assert_nothing_raised { x.save }
- x.order = 'y'
- assert_nothing_raised { x.save }
- assert_nothing_raised { Group.find_by_order('y') }
- assert_nothing_raised { Group.find(1) }
- Group.find(1)
- end
-
- # has_one association with reserved-word table name
- def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- v = nil
- assert_nothing_raised { v = Group.find(1).values }
- assert_equal 2, v.id
- end
-
- # belongs_to association with reserved-word table name
- def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- gs = nil
- assert_nothing_raised { gs = Select.find(2).groups }
- assert_equal gs.length, 2
- assert(gs.collect(&:id).sort == [2, 3])
- end
-
- # has_and_belongs_to_many with reserved-word table name
- def test_has_and_belongs_to_many
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- s = nil
- assert_nothing_raised { s = Distinct.find(1).selects }
- assert_equal s.length, 2
- assert(s.collect(&:id).sort == [1, 2])
- end
-
- # activerecord model introspection with reserved-word table and column names
- def test_activerecord_introspection
- assert_nothing_raised { Group.table_exists? }
- assert_nothing_raised { Group.columns }
- end
-
- # Calculations
- def test_calculations_work_with_reserved_words
- assert_nothing_raised { Group.count }
- end
-
- def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.all.merge!(:includes => [:groups]).to_a }
- end
-
- #the following functions were added to DRY test cases
-
- private
- # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
- def create_test_fixtures(*fixture_names)
- ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
- end
-
- # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
- def drop_tables_directly(table_names, connection = @connection)
- table_names.each do |name|
- connection.drop_table name, if_exists: true
- end
- end
-
- # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
- def create_tables_directly (tables, connection = @connection)
- tables.each do |table_name, column_properties|
- connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
- end
- end
-
-end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
deleted file mode 100644
index 14dbdd375b..0000000000
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-require "cases/helper"
-require 'models/post'
-require 'models/comment'
-
-module ActiveRecord
- module ConnectionAdapters
- class MysqlSchemaTest < ActiveRecord::MysqlTestCase
- fixtures :posts
-
- def setup
- @connection = ActiveRecord::Base.connection
- db = Post.connection_pool.spec.config[:database]
- table = Post.table_name
- @db_name = db
-
- @omgpost = Class.new(ActiveRecord::Base) do
- self.inheritance_column = :disabled
- self.table_name = "#{db}.#{table}"
- def self.name; 'Post'; end
- end
- end
-
- def test_float_limits
- @connection.create_table :mysql_doubles do |t|
- t.float :float_no_limit
- t.float :float_short, limit: 5
- t.float :float_long, limit: 53
-
- t.float :float_23, limit: 23
- t.float :float_24, limit: 24
- t.float :float_25, limit: 25
- end
-
- column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_no_limit' }
- column_short = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_short' }
- column_long = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_long' }
-
- column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_23' }
- column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_24' }
- column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_25' }
-
- # Mysql floats are precision 0..24, Mysql doubles are precision 25..53
- assert_equal 24, column_no_limit.limit
- assert_equal 24, column_short.limit
- assert_equal 53, column_long.limit
-
- assert_equal 24, column_23.limit
- assert_equal 24, column_24.limit
- assert_equal 53, column_25.limit
- ensure
- @connection.drop_table "mysql_doubles", if_exists: true
- end
-
- def test_schema
- assert @omgpost.first
- end
-
- def test_primary_key
- assert_equal 'id', @omgpost.primary_key
- end
-
- def test_data_source_exists?
- name = @omgpost.table_name
- assert @connection.data_source_exists?(name), "#{name} data_source should exist"
- end
-
- def test_data_source_exists_wrong_schema
- assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
- end
-
- def test_dump_indexes
- index_a_name = 'index_key_tests_on_snack'
- index_b_name = 'index_key_tests_on_pizza'
- index_c_name = 'index_key_tests_on_awesome'
-
- table = 'key_tests'
-
- indexes = @connection.indexes(table).sort_by(&:name)
- assert_equal 3,indexes.size
-
- index_a = indexes.select{|i| i.name == index_a_name}[0]
- index_b = indexes.select{|i| i.name == index_b_name}[0]
- index_c = indexes.select{|i| i.name == index_c_name}[0]
- assert_equal :btree, index_a.using
- assert_nil index_a.type
- assert_equal :btree, index_b.using
- assert_nil index_b.type
-
- assert_nil index_c.using
- assert_equal :fulltext, index_c.type
- end
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb
deleted file mode 100644
index 7849248dcc..0000000000
--- a/activerecord/test/cases/adapters/mysql/sp_test.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require "cases/helper"
-require 'models/topic'
-require 'models/reply'
-
-class MysqlStoredProcedureTest < ActiveRecord::MysqlTestCase
- fixtures :topics
-
- def setup
- @connection = ActiveRecord::Base.connection
- unless ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
- skip("no stored procedure support")
- end
- end
-
- # Test that MySQL allows multiple results for stored procedures
- #
- # In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default.
- # http://dev.mysql.com/doc/refman/5.6/en/call.html
- def test_multi_results
- rows = @connection.select_rows('CALL ten();')
- assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
- assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
- end
-
- def test_multi_results_from_find_by_sql
- topics = Topic.find_by_sql 'CALL topics(3);'
- assert_equal 3, topics.size
- assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'"
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/sql_types_test.rb b/activerecord/test/cases/adapters/mysql/sql_types_test.rb
deleted file mode 100644
index d18579f242..0000000000
--- a/activerecord/test/cases/adapters/mysql/sql_types_test.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require "cases/helper"
-
-class MysqlSqlTypesTest < ActiveRecord::MysqlTestCase
- def test_binary_types
- assert_equal 'varbinary(64)', type_to_sql(:binary, 64)
- assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095)
- assert_equal 'blob', type_to_sql(:binary, 4096)
- assert_equal 'blob', type_to_sql(:binary)
- end
-
- def type_to_sql(*args)
- ActiveRecord::Base.connection.type_to_sql(*args)
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
deleted file mode 100644
index 0d1f968022..0000000000
--- a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'cases/helper'
-
-class MysqlStatementPoolTest < ActiveRecord::MysqlTestCase
- if Process.respond_to?(:fork)
- def test_cache_is_per_pid
- cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new(10)
- cache['foo'] = 'bar'
- assert_equal 'bar', cache['foo']
-
- pid = fork {
- lookup = cache['foo'];
- exit!(!lookup)
- }
-
- Process.waitpid pid
- assert $?.success?, 'process should exit successfully'
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/table_options_test.rb b/activerecord/test/cases/adapters/mysql/table_options_test.rb
deleted file mode 100644
index 99df6d6cba..0000000000
--- a/activerecord/test/cases/adapters/mysql/table_options_test.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require "cases/helper"
-require 'support/schema_dumping_helper'
-
-class MysqlTableOptionsTest < ActiveRecord::MysqlTestCase
- include SchemaDumpingHelper
-
- def setup
- @connection = ActiveRecord::Base.connection
- end
-
- def teardown
- @connection.drop_table "mysql_table_options", if_exists: true
- end
-
- test "table options with ENGINE" do
- @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
- output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
- assert_match %r{ENGINE=MyISAM}, options
- end
-
- test "table options with ROW_FORMAT" do
- @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
- output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
- assert_match %r{ROW_FORMAT=REDUNDANT}, options
- end
-
- test "table options with CHARSET" do
- @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
- output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
- assert_match %r{CHARSET=utf8mb4}, options
- end
-
- test "table options with COLLATE" do
- @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
- output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
- assert_match %r{COLLATE=utf8mb4_bin}, options
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
deleted file mode 100644
index 84c5394c2e..0000000000
--- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require "cases/helper"
-require "support/schema_dumping_helper"
-
-class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase
- include SchemaDumpingHelper
- self.use_transactional_tests = false
-
- class UnsignedType < ActiveRecord::Base
- end
-
- setup do
- @connection = ActiveRecord::Base.connection
- @connection.create_table("unsigned_types", force: true) do |t|
- t.integer :unsigned_integer, unsigned: true
- t.bigint :unsigned_bigint, unsigned: true
- t.float :unsigned_float, unsigned: true
- t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2
- end
- end
-
- teardown do
- @connection.drop_table "unsigned_types", if_exists: true
- end
-
- test "unsigned int max value is in range" do
- assert expected = UnsignedType.create(unsigned_integer: 4294967295)
- assert_equal expected, UnsignedType.find_by(unsigned_integer: 4294967295)
- end
-
- test "minus value is out of range" do
- assert_raise(RangeError) do
- UnsignedType.create(unsigned_integer: -10)
- end
- assert_raise(RangeError) do
- UnsignedType.create(unsigned_bigint: -10)
- end
- assert_raise(ActiveRecord::StatementInvalid) do
- UnsignedType.create(unsigned_float: -10.0)
- end
- assert_raise(ActiveRecord::StatementInvalid) do
- UnsignedType.create(unsigned_decimal: -10.0)
- end
- end
-
- test "schema definition can use unsigned as the type" do
- @connection.change_table("unsigned_types") do |t|
- t.unsigned_integer :unsigned_integer_t
- t.unsigned_bigint :unsigned_bigint_t
- t.unsigned_float :unsigned_float_t
- t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2
- end
-
- @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column|
- assert column.unsigned?
- end
- end
-
- test "schema dump includes unsigned option" do
- schema = dump_table_schema "unsigned_types"
- assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema
- assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema
- assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
- assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index 4fd34def15..668c07dacb 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -48,7 +48,7 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
test "schema dump includes collation" do
output = dump_table_schema("charset_collations")
- assert_match %r{t.string\s+"string_ascii_bin",\s+limit: 255,\s+collation: "ascii_bin"$}, output
+ assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 507d024bb6..575138eb2a 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -68,9 +68,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
assert_equal 'utf8_general_ci', ARUnit2Model.connection.show_variable('collation_connection')
end
- # TODO: Below is a straight up copy/paste from mysql/connection_test.rb
- # I'm not sure what the correct way is to share these tests between
- # adapters in minitest.
def test_mysql_default_in_strict_mode
result = @connection.exec_query "SELECT @@SESSION.sql_mode"
assert_equal [["STRICT_ALL_TABLES"]], result.rows
@@ -84,6 +81,20 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
end
+ def test_passing_arbitary_flags_to_adapter
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({flags: Mysql2::Client::COMPRESS}))
+ assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
+ end
+ end
+
+ def test_passing_flags_by_array_to_adapter
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] }))
+ assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
+ end
+ end
+
def test_mysql_strict_mode_specified_default
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index bd732b5eca..bb89e893e0 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -5,6 +5,17 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
end
def test_enum_limit
- assert_equal 6, EnumTest.columns.first.limit
+ column = EnumTest.columns_hash['enum_column']
+ assert_equal 8, column.limit
+ end
+
+ def test_should_not_be_blob_or_text_column
+ column = EnumTest.columns_hash['enum_column']
+ assert_not column.blob_or_text_column?
+ end
+
+ def test_should_not_be_unsigned
+ column = EnumTest.columns_hash['enum_column']
+ assert_not column.unsigned?
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
new file mode 100644
index 0000000000..4efd728754
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -0,0 +1,44 @@
+require "cases/helper"
+
+class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_columns_for_distinct_zero_orders
+ assert_equal "posts.id",
+ @conn.columns_for_distinct("posts.id", [])
+ end
+
+ def test_columns_for_distinct_one_order
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @conn.columns_for_distinct("posts.id", ["posts.created_at desc"])
+ end
+
+ def test_columns_for_distinct_few_orders
+ assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
+ end
+
+ def test_columns_for_distinct_with_case
+ assert_equal(
+ 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0',
+ @conn.columns_for_distinct('posts.id',
+ ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
+ )
+ end
+
+ def test_columns_for_distinct_blank_not_nil_orders
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
+ 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",
+ @conn.columns_for_distinct("posts.id", [order])
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb
index cdaa2cca44..4197ba45f1 100644
--- a/activerecord/test/cases/adapters/mysql2/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb
@@ -22,6 +22,12 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'"
end
+ def test_multi_results_from_select_one
+ row = @connection.select_one('CALL topics(1);')
+ assert_equal 'David', row['author_name']
+ assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_one'"
+ end
+
def test_multi_results_from_find_by_sql
topics = Topic.find_by_sql 'CALL topics(3);'
assert_equal 3, topics.size
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index a6f6dd21bb..c95a64cc16 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -57,7 +57,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
test "schema dump includes unsigned option" do
schema = dump_table_schema "unsigned_types"
- assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema
+ assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema
assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 24def31e36..ed44bf7362 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -54,8 +54,10 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
end
def test_remove_index
- # remove_index calls index_name_exists? which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| true }
+ # remove_index calls index_name_for_remove which can't work since execute is stubbed
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_for_remove) do |*|
+ 'index_people_on_last_name'
+ end
expected = %(DROP INDEX CONCURRENTLY "index_people_on_last_name")
assert_equal expected, remove_index(:people, name: "index_people_on_last_name", algorithm: :concurrently)
@@ -64,7 +66,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
add_index(:people, :last_name, algorithm: :copy)
end
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists?
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_for_remove
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index 9cfc133308..b2a805333c 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -3,13 +3,13 @@ require "cases/helper"
class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
- class EnableHstore < ActiveRecord::Migration
+ class EnableHstore < ActiveRecord::Migration::Current
def change
enable_extension "hstore"
end
end
- class DisableHstore < ActiveRecord::Migration
+ class DisableHstore < ActiveRecord::Migration::Current
def change
disable_extension "hstore"
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 8d0c5bf23f..9e250c2b7c 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -7,10 +7,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class PostgresqlPoint < ActiveRecord::Base
- attribute :x, :rails_5_1_point
- attribute :y, :rails_5_1_point
- attribute :z, :rails_5_1_point
- attribute :array_of_points, :rails_5_1_point, array: true
+ attribute :x, :point
+ attribute :y, :point
+ attribute :z, :point
+ attribute :array_of_points, :point, array: true
attribute :legacy_x, :legacy_point
attribute :legacy_y, :legacy_point
attribute :legacy_z, :legacy_point
@@ -262,7 +262,9 @@ class PostgreSQLGeometricLineTest < ActiveRecord::PostgreSQLTestCase
end
teardown do
- @connection.drop_table 'postgresql_lines', if_exists: true
+ if defined?(@connection)
+ @connection.drop_table 'postgresql_lines', if_exists: true
+ end
end
def test_geometric_line_type
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 6a2d501646..27cc65a643 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -86,7 +86,7 @@ if ActiveRecord::Base.connection.supports_extensions?
end
def test_hstore_migration
- hstore_migration = Class.new(ActiveRecord::Migration) do
+ hstore_migration = Class.new(ActiveRecord::Migration::Current) do
def change
change_table("hstores") do |t|
t.hstore :keys
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index e361521155..f1995b243a 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -139,8 +139,8 @@ module ActiveRecord
def test_sql_for_insert_with_returning_disabled
connection = connection_without_insert_returning
- result = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
- assert_equal ['sql', 'binds'], result
+ sql, binds = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
+ assert_equal ['sql', 'binds'], [sql, binds]
end
def test_serial_sequence
@@ -322,11 +322,6 @@ module ActiveRecord
end
end
- def test_substitute_at
- bind = @connection.substitute_at(nil)
- assert_equal Arel.sql('$1'), bind.to_sql
- end
-
def test_partial_index
with_example_table do
@connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 02b1083430..0edfa4ed9d 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -4,11 +4,13 @@ require 'support/connection_helper'
if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord::Base.connection.supports_ranges?
class PostgresqlRange < ActiveRecord::Base
self.table_name = "postgresql_ranges"
+ self.time_zone_aware_types += [:tsrange, :tstzrange]
end
class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
include ConnectionHelper
+ include InTimeZone
def setup
@connection = PostgresqlRange.connection
@@ -160,6 +162,26 @@ _SQL
assert_nil @empty_range.float_range
end
+ def test_timezone_awareness_tzrange
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
+
+ record = PostgresqlRange.new(tstz_range: time_string..time_string)
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
+ end
+ end
+
def test_create_tstzrange
tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT')
round_trip(@new_range, :tstz_range, tstzrange)
@@ -188,6 +210,26 @@ _SQL
Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0))
end
+ def test_timezone_awareness_tsrange
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
+
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ end
+ end
+
def test_create_numrange
assert_equal_round_trip(@new_range, :num_range,
BigDecimal.new('0.5')...BigDecimal.new('1'))
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 7c9169f6e2..4aeca4d709 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -321,16 +321,33 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
+ def test_dump_indexes_for_table_with_scheme_specified_in_name
+ indexes = @connection.indexes("#{SCHEMA_NAME}.#{TABLE_NAME}")
+ assert_equal 4, indexes.size
+ end
+
def test_with_uppercase_index_name
@connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
- assert_nothing_raised { @connection.remove_index "things", name: "#{SCHEMA_NAME}.things_Index"}
- @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
with_schema_search_path SCHEMA_NAME do
assert_nothing_raised { @connection.remove_index "things", name: "things_Index"}
end
end
+ def test_remove_index_when_schema_specified
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
+ assert_nothing_raised { @connection.remove_index "things", name: "#{SCHEMA_NAME}.things_Index" }
+
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
+ assert_nothing_raised { @connection.remove_index "#{SCHEMA_NAME}.things", name: "things_Index" }
+
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
+ assert_nothing_raised { @connection.remove_index "#{SCHEMA_NAME}.things", name: "#{SCHEMA_NAME}.things_Index" }
+
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
+ assert_raises(ArgumentError) { @connection.remove_index "#{SCHEMA2_NAME}.things", name: "#{SCHEMA_NAME}.things_Index" }
+ end
+
def test_primary_key_with_schema_specified
[
%("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 7127d69e9e..7628075ad2 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -20,6 +20,8 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
end
setup do
+ enable_extension!('uuid-ossp', connection)
+
connection.create_table "uuid_data_type" do |t|
t.uuid 'guid'
end
@@ -144,8 +146,6 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
end
setup do
- enable_extension!('uuid-ossp', connection)
-
connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
t.string 'name'
t.uuid 'other_uuid', default: 'uuid_generate_v4()'
@@ -170,7 +170,6 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
drop_table "pg_uuids"
drop_table 'pg_uuids_2'
connection.execute 'DROP FUNCTION IF EXISTS my_uuid_generator();'
- disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -198,14 +197,14 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
def test_schema_dumper_for_uuid_primary_key
schema = dump_table_schema "pg_uuids"
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema)
- assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema)
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
end
def test_schema_dumper_for_uuid_primary_key_with_custom_default
schema = dump_table_schema "pg_uuids_2"
- assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema)
- assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema)
+ assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
end
end
end
@@ -215,8 +214,6 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
setup do
- enable_extension!('uuid-ossp', connection)
-
connection.create_table('pg_uuids', id: false) do |t|
t.primary_key :id, :uuid, default: nil
t.string 'name'
@@ -225,7 +222,6 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
teardown do
drop_table "pg_uuids"
- disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -258,8 +254,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
end
setup do
- enable_extension!('uuid-ossp', connection)
-
connection.transaction do
connection.create_table('pg_uuid_posts', id: :uuid) do |t|
t.string 'title'
@@ -274,7 +268,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
teardown do
drop_table "pg_uuid_comments"
drop_table "pg_uuid_posts"
- disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index a2fd1177a6..038d9e2d0f 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -130,11 +130,6 @@ module ActiveRecord
assert_equal 'UTF-8', @conn.encoding
end
- def test_bind_value_substitute
- bind_param = @conn.substitute_at('foo')
- assert_equal Arel.sql('?'), bind_param.to_sql
- end
-
def test_exec_no_binds
with_example_table 'id int, data string' do
result = @conn.exec_query('SELECT id, data FROM ex')
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
index 887dcfc96c..9b675b804b 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
@@ -6,13 +6,17 @@ module ActiveRecord
class SQLite3CreateFolder < ActiveRecord::SQLite3TestCase
def test_sqlite_creates_directory
Dir.mktmpdir do |dir|
- dir = Pathname.new(dir)
- @conn = Base.sqlite3_connection :database => dir.join("db/foo.sqlite3"),
- :adapter => 'sqlite3',
- :timeout => 100
+ begin
+ dir = Pathname.new(dir)
+ @conn = Base.sqlite3_connection :database => dir.join("db/foo.sqlite3"),
+ :adapter => 'sqlite3',
+ :timeout => 100
- assert Dir.exist? dir.join('db')
- assert File.exist? dir.join('db/foo.sqlite3')
+ assert Dir.exist? dir.join('db')
+ assert File.exist? dir.join('db/foo.sqlite3')
+ ensure
+ @conn.disconnect! if @conn
+ end
end
end
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 9d5327bf35..1f32c48b95 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -21,10 +21,10 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Migration.verbose = @original_verbose
end
- def test_has_no_primary_key
+ def test_has_has_primary_key
old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
- assert_nil ActiveRecord::SchemaMigration.primary_key
+ assert_equal "version", ActiveRecord::SchemaMigration.primary_key
ActiveRecord::SchemaMigration.create_table
assert_difference "ActiveRecord::SchemaMigration.count", 1 do
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 938350627f..4f99c57c3c 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -53,7 +53,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_with_primary_key_joins_on_correct_column
sql = Client.joins(:firm_with_primary_key).to_sql
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
elsif current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 0c09713971..874d53c51f 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1402,4 +1402,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
post = Post.eager_load(:tags).where('tags.name = ?', 'General').first
assert_equal posts(:welcome), post
end
+
+ # CollectionProxy#reader is expensive, so the preloader avoids calling it.
+ test "preloading has_many_through association avoids calling association.reader" do
+ ActiveRecord::Associations::HasManyAssociation.any_instance.expects(:reader).never
+ Author.preload(:readonly_comments).first!
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 50ca6537cc..ad157582a4 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -2348,6 +2348,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [first_bulb, second_bulb], car.bulbs
end
+ test 'double insertion of new object to association when same association used in the after create callback of a new object' do
+ car = Car.create!
+ car.bulbs << TrickyBulb.new
+ assert_equal 1, car.bulbs.size
+ end
+
def test_association_force_reload_with_only_true_is_deprecated
company = Company.find(1)
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 52d197718e..94dfbc3346 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -175,7 +175,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal category_attrs , category.attributes_before_type_cast
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_read_attributes_before_type_cast_on_boolean
bool = Boolean.create!({ "value" => false })
if RUBY_PLATFORM =~ /java/
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 0df8f1f798..3608063b01 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -433,6 +433,53 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
end
+ def test_errors_details_should_be_set
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.valid?
+ assert_equal [{error: :blank}], molecule.errors.details["electrons.name"]
+ end
+
+ def test_errors_details_should_be_indexed_when_passed_as_array
+ guitar = Guitar.new
+ tuning_peg_valid = TuningPeg.new
+ tuning_peg_valid.pitch = 440.0
+ tuning_peg_invalid = TuningPeg.new
+
+ guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
+
+ assert_not tuning_peg_invalid.valid?
+ assert tuning_peg_valid.valid?
+ assert_not guitar.valid?
+ assert_equal [{error: :not_a_number, value: nil}] , guitar.errors.details["tuning_pegs[1].pitch"]
+ assert_equal [], guitar.errors.details["tuning_pegs.pitch"]
+ end
+
+ def test_errors_details_should_be_indexed_when_global_flag_is_set
+ old_attribute_config = ActiveRecord::Base.index_nested_attribute_errors
+ ActiveRecord::Base.index_nested_attribute_errors = true
+
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.valid?
+ assert_equal [{error: :blank}], molecule.errors.details["electrons[1].name"]
+ assert_equal [], molecule.errors.details["electrons.name"]
+ ensure
+ ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
+ end
+
def test_valid_adding_with_nested_attributes
molecule = Molecule.new
valid_electron = Electron.new(name: 'electron')
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index d961f4710e..ecdf508e3e 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -82,7 +82,6 @@ class BasicsTest < ActiveRecord::TestCase
classname = conn.class.name[/[^:]*$/]
badchar = {
'SQLite3Adapter' => '"',
- 'MysqlAdapter' => '`',
'Mysql2Adapter' => '`',
'PostgreSQLAdapter' => '"',
'OracleAdapter' => '"',
@@ -112,7 +111,9 @@ class BasicsTest < ActiveRecord::TestCase
unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter, :FbAdapter)
def test_limit_with_comma
- assert Topic.limit("1,2").to_a
+ assert_deprecated do
+ assert Topic.limit("1,2").to_a
+ end
end
end
@@ -138,14 +139,10 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_limit_should_sanitize_sql_injection_for_limit_with_commas
- assert_raises(ArgumentError) do
- Topic.limit("1, 7 procedure help()").to_a
- end
- end
-
- unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
- def test_limit_should_allow_sql_literal
- assert_equal 1, Topic.limit(Arel.sql('2-1')).to_a.length
+ assert_deprecated do
+ assert_raises(ArgumentError) do
+ Topic.limit("1, 7 procedure help()").to_a
+ end
end
end
@@ -213,7 +210,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc
- with_env_tz 'America/New_York' do
+ with_env_tz eastern_time_zone do
with_timezone_config default: :utc do
time = Time.local(2000)
topic = Topic.create('written_on' => time)
@@ -226,7 +223,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc
- with_env_tz 'America/New_York' do
+ with_env_tz eastern_time_zone do
with_timezone_config default: :utc do
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
@@ -241,7 +238,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local
- with_env_tz 'America/New_York' do
+ with_env_tz eastern_time_zone do
with_timezone_config default: :local do
time = Time.utc(2000)
topic = Topic.create('written_on' => time)
@@ -254,7 +251,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local
- with_env_tz 'America/New_York' do
+ with_env_tz eastern_time_zone do
with_timezone_config default: :local do
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
@@ -268,6 +265,14 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def eastern_time_zone
+ if Gem.win_platform?
+ "EST5EDT"
+ else
+ "America/New_York"
+ end
+ end
+
def test_custom_mutator
topic = Topic.find(1)
# This mutator is protected in the class definition
@@ -438,7 +443,7 @@ class BasicsTest < ActiveRecord::TestCase
Post.reset_table_name
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_update_all_with_order_and_limit
assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!')
end
@@ -519,7 +524,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_find_by_slug_with_array
- assert_equal Topic.find(['1-meowmeow', '2-hello']), Topic.find([1, 2])
+ assert_equal Topic.find([1, 2]), Topic.find(['1-meowmeow', '2-hello'])
+ assert_equal 'The Second Topic of the day', Topic.find(['2-hello', '1-meowmeow']).first.title
end
def test_find_by_slug_with_range
@@ -1246,6 +1252,7 @@ class BasicsTest < ActiveRecord::TestCase
original_logger = ActiveRecord::Base.logger
log = StringIO.new
ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::DEBUG
ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" }
assert_match(/Quiet/, log.string)
ensure
@@ -1268,9 +1275,10 @@ class BasicsTest < ActiveRecord::TestCase
UnloadablePost.send(:current_scope=, UnloadablePost.all)
UnloadablePost.unloadable
- assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
+ klass = UnloadablePost
+ assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
+ assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
@@ -1345,6 +1353,19 @@ class BasicsTest < ActiveRecord::TestCase
Company.attribute_names
end
+ def test_has_attribute
+ assert Company.has_attribute?('id')
+ assert Company.has_attribute?('type')
+ assert Company.has_attribute?('name')
+ assert_not Company.has_attribute?('lastname')
+ assert_not Company.has_attribute?('age')
+ end
+
+ def test_has_attribute_with_symbol
+ assert Company.has_attribute?(:id)
+ assert_not Company.has_attribute?(:age)
+ end
+
def test_attribute_names_on_table_not_exists
assert_equal [], NonExistentTable.attribute_names
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 86dee929bf..9eb5352150 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -20,10 +20,6 @@ unless current_adapter?(:DB2Adapter)
name = binary.name
- # MySQL adapter doesn't properly encode things, so we have to do it
- if current_adapter?(:MysqlAdapter)
- name.force_encoding(Encoding::UTF_8)
- end
assert_equal 'ã„ãŸã ãã¾ã™ï¼', name
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 1e38b97c4a..cd9c76f1f0 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def test_binds_are_logged
- sub = @connection.substitute_at(@pk)
+ sub = Arel::Nodes::BindParam.new
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 4a0e6f497f..c922a8d1c2 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -545,8 +545,8 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 7, Company.includes(:contracts).sum(:developer_id)
end
- def test_from_option_with_specified_index
- if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
+ if current_adapter?(:Mysql2Adapter)
+ def test_from_option_with_specified_index
assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all)
assert_equal Edge.where('sink_id < 5').count(:all),
Edge.from('edges USE INDEX(unique_edge_index)').where('sink_id < 5').count(:all)
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 73ac30e547..4f70ae3a1d 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -33,7 +33,7 @@ class CallbackDeveloper < ActiveRecord::Base
ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
next if callback_method.to_s =~ /^around_/
define_callback_method(callback_method)
- send(callback_method, callback_string(callback_method))
+ ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method)) }
send(callback_method, callback_proc(callback_method))
send(callback_method, callback_object(callback_method))
send(callback_method) { |model| model.history << [callback_method, :block] }
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 14b95ecab1..81162b7e98 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -38,42 +38,52 @@ module ActiveRecord
assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def)
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types
type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
- binary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
+ binary_column = MySQL::Column.new("title", "a", type)
assert_equal "a", binary_column.default
type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
- varbinary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
+ varbinary_column = MySQL::Column.new("title", "a", type)
assert_equal "a", varbinary_column.default
end
+ def test_should_be_empty_string_default_for_mysql_binary_data_types
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
+ binary_column = MySQL::Column.new("title", "", type, false)
+ assert_equal "", binary_column.default
+
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
+ varbinary_column = MySQL::Column.new("title", "", type, false)
+ assert_equal "", varbinary_column.default
+ end
+
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- AbstractMysqlAdapter::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob"))
+ MySQL::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob"))
end
- text_type = AbstractMysqlAdapter::MysqlTypeMetadata.new(
+ text_type = MySQL::TypeMetadata.new(
SqlTypeMetadata.new(type: :text))
assert_raise ArgumentError do
- AbstractMysqlAdapter::Column.new("title", "Hello", text_type)
+ MySQL::Column.new("title", "Hello", text_type)
end
- text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
+ text_column = MySQL::Column.new("title", nil, text_type)
assert_equal nil, text_column.default
- not_null_text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type, false)
+ not_null_text_column = MySQL::Column.new("title", nil, text_type, false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
binary_type = SqlTypeMetadata.new(sql_type: "blob")
- blob_column = AbstractMysqlAdapter::Column.new("title", nil, binary_type)
+ blob_column = MySQL::Column.new("title", nil, binary_type)
assert !blob_column.has_default?
text_type = SqlTypeMetadata.new(type: :text)
- text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
+ text_column = MySQL::Column.new("title", nil, text_type)
assert !text_column.has_default?
end
end
diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
index 2749273884..f2b1d9e4e7 100644
--- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+if current_adapter?(:Mysql2Adapter)
module ActiveRecord
module ConnectionAdapters
class MysqlTypeLookupTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb
index e8290297e3..26d015bf71 100644
--- a/activerecord/test/cases/custom_locking_test.rb
+++ b/activerecord/test/cases/custom_locking_test.rb
@@ -6,7 +6,7 @@ module ActiveRecord
fixtures :people
def test_custom_lock
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
assert_match 'SHARE MODE', Person.lock('LOCK IN SHARE MODE').to_sql
assert_sql(/LOCK IN SHARE MODE/) do
Person.all.merge!(:lock => 'LOCK IN SHARE MODE').find(1)
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index c689e97d83..ba085991e0 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -6,14 +6,23 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
end
def test_insert_should_return_the_inserted_id
+ assert_not_nil return_the_inserted_id(method: :insert)
+ end
+
+ def test_create_should_return_the_inserted_id
+ assert_not_nil return_the_inserted_id(method: :create)
+ end
+
+ private
+
+ def return_the_inserted_id(method:)
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if current_adapter?(:OracleAdapter)
sequence_name = "accounts_seq"
id_value = @connection.next_sequence_value(sequence_name)
- id = @connection.insert("INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name)
+ @connection.send(method, "INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name)
else
- id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
+ @connection.send(method, "INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
end
- assert_not_nil id
end
end
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index 698f1b852e..e996d142a2 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -10,6 +10,7 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
+ Foo.reset_column_information
end
teardown do
@@ -20,24 +21,24 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
@connection.create_table(:foos, force: true)
@connection.add_column :foos, :created_at, :datetime, precision: 0
@connection.add_column :foos, :updated_at, :datetime, precision: 5
- assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
+ assert_equal 0, Foo.columns_hash['created_at'].precision
+ assert_equal 5, Foo.columns_hash['updated_at'].precision
end
def test_timestamps_helper_with_custom_precision
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 4
end
- assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
+ assert_equal 4, Foo.columns_hash['created_at'].precision
+ assert_equal 4, Foo.columns_hash['updated_at'].precision
end
def test_passing_precision_to_datetime_does_not_set_limit
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 4
end
- assert_nil activerecord_column_option('foos', 'created_at', 'limit')
- assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
+ assert_nil Foo.columns_hash['created_at'].limit
+ assert_nil Foo.columns_hash['updated_at'].limit
end
def test_invalid_datetime_precision_raises_error
@@ -48,14 +49,6 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
end
end
- def test_database_agrees_with_activerecord_about_precision
- @connection.create_table(:foos, force: true) do |t|
- t.timestamps precision: 4
- end
- assert_equal 4, database_datetime_precision('foos', 'created_at')
- assert_equal 4, database_datetime_precision('foos', 'updated_at')
- end
-
def test_formatting_datetime_according_to_precision
@connection.create_table(:foos, force: true) do |t|
t.datetime :created_at, precision: 0
@@ -91,21 +84,5 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
end
end
- private
-
- def database_datetime_precision(table_name, column_name)
- results = @connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
- result = results.find do |result_hash|
- result_hash["column_name"] == column_name
- end
- result && result["datetime_precision"].to_i
- end
-
- def activerecord_column_option(tablename, column_name, option)
- result = @connection.columns(tablename).find do |column|
- column.name == column_name
- end
- result && result.send(option)
- end
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 67fddebf45..69b0487dd8 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -1,20 +1,14 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
require 'models/default'
require 'models/entrant'
class DefaultTest < ActiveRecord::TestCase
def test_nil_defaults_for_not_null_columns
- column_defaults =
- if current_adapter?(:MysqlAdapter) && (Mysql.client_version < 50051 || (50100..50122).include?(Mysql.client_version))
- { 'id' => nil, 'name' => '', 'course_id' => nil }
- else
- { 'id' => nil, 'name' => nil, 'course_id' => nil }
- end
-
- column_defaults.each do |name, default|
+ %w(id name course_id).each do |name|
column = Entrant.columns_hash[name]
assert !column.null, "#{name} column should be NOT NULL"
- assert_equal default, column.default, "#{name} column should be DEFAULT #{default.inspect}"
+ assert_not column.default, "#{name} column should be DEFAULT 'nil'"
end
end
@@ -87,7 +81,32 @@ class DefaultStringsTest < ActiveRecord::TestCase
end
end
-if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+if current_adapter?(:PostgreSQLAdapter)
+ class PostgresqlDefaultExpressionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ test "schema dump includes default expression" do
+ output = dump_table_schema("defaults")
+ assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
+ assert_match %r/t\.date\s+"modified_date_function",\s+default: -> { "now\(\)" }/, output
+ assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
+ assert_match %r/t\.datetime\s+"modified_time_function",\s+default: -> { "now\(\)" }/, output
+ end
+ end
+end
+
+if current_adapter?(:Mysql2Adapter)
+ class MysqlDefaultExpressionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ if ActiveRecord::Base.connection.version >= '5.6.0'
+ test "schema dump includes default expression" do
+ output = dump_table_schema("datetime_defaults")
+ assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
+ end
+ end
+ end
+
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional tests mode, this will
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 6686ce012d..75a74c052d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -19,7 +19,7 @@ require 'models/car'
require 'models/tyre'
class FinderTest < ActiveRecord::TestCase
- fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations, :cars
+ fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
def test_find_by_id_with_hash
assert_raises(ActiveRecord::StatementInvalid) do
@@ -48,6 +48,75 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_find_with_ids_returning_ordered
+ records = Topic.find([4,2,5])
+ assert_equal 'The Fourth Topic of the day', records[0].title
+ assert_equal 'The Second Topic of the day', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+
+ records = Topic.find(4,2,5)
+ assert_equal 'The Fourth Topic of the day', records[0].title
+ assert_equal 'The Second Topic of the day', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+
+ records = Topic.find(['4','2','5'])
+ assert_equal 'The Fourth Topic of the day', records[0].title
+ assert_equal 'The Second Topic of the day', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+
+ records = Topic.find('4','2','5')
+ assert_equal 'The Fourth Topic of the day', records[0].title
+ assert_equal 'The Second Topic of the day', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+ end
+
+ def test_find_with_ids_and_order_clause
+ # The order clause takes precedence over the informed ids
+ records = Topic.order(:author_name).find([5,3,1])
+ assert_equal 'The Third Topic of the day', records[0].title
+ assert_equal 'The First Topic', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+
+ records = Topic.order(:id).find([5,3,1])
+ assert_equal 'The First Topic', records[0].title
+ assert_equal 'The Third Topic of the day', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+ end
+
+ def test_find_with_ids_with_limit_and_order_clause
+ # The order clause takes precedence over the informed ids
+ records = Topic.limit(2).order(:id).find([5,3,1])
+ assert_equal 2, records.size
+ assert_equal 'The First Topic', records[0].title
+ assert_equal 'The Third Topic of the day', records[1].title
+ end
+
+ def test_find_with_ids_and_limit
+ records = Topic.limit(3).find([3,2,5,1,4])
+ assert_equal 3, records.size
+ assert_equal 'The Third Topic of the day', records[0].title
+ assert_equal 'The Second Topic of the day', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+ end
+
+ def test_find_with_ids_where_and_limit
+ # Please note that Topic 1 is the only not approved so
+ # if it were among the first 3 it would raise a ActiveRecord::RecordNotFound
+ records = Topic.where(approved: true).limit(3).find([3,2,5,1,4])
+ assert_equal 3, records.size
+ assert_equal 'The Third Topic of the day', records[0].title
+ assert_equal 'The Second Topic of the day', records[1].title
+ assert_equal 'The Fifth Topic of the day', records[2].title
+ end
+
+ def test_find_with_ids_and_offset
+ records = Topic.offset(2).find([3,2,5,1,4])
+ assert_equal 3, records.size
+ assert_equal 'The Fifth Topic of the day', records[0].title
+ assert_equal 'The First Topic', records[1].title
+ assert_equal 'The Fourth Topic of the day', records[2].title
+ end
+
def test_find_passing_active_record_object_is_deprecated
assert_deprecated do
Topic.find(Topic.last)
@@ -195,7 +264,9 @@ class FinderTest < ActiveRecord::TestCase
def test_find_by_ids_with_limit_and_offset
assert_equal 2, Entrant.limit(2).find([1,3,2]).size
- assert_equal 1, Entrant.limit(3).offset(2).find([1,3,2]).size
+ entrants = Entrant.limit(3).offset(2).find([1,3,2])
+ assert_equal 1, entrants.size
+ assert_equal 'Ruby Guru', entrants.first.name
# Also test an edge case: If you have 11 results, and you set a
# limit of 3 and offset of 9, then you should find that there
@@ -203,6 +274,8 @@ class FinderTest < ActiveRecord::TestCase
devs = Developer.all
last_devs = Developer.limit(3).offset(9).find devs.map(&:id)
assert_equal 2, last_devs.size
+ assert_equal 'fixture_10', last_devs[0].name
+ assert_equal 'Jamis', last_devs[1].name
end
def test_find_with_large_number
@@ -434,9 +507,9 @@ class FinderTest < ActiveRecord::TestCase
end
def test_take_and_first_and_last_with_integer_should_use_sql_limit
- assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
- assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
- assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
+ assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries }
+ assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries }
+ assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries }
end
def test_last_with_integer_and_order_should_keep_the_order
@@ -706,96 +779,13 @@ class FinderTest < ActiveRecord::TestCase
assert Company.where(["name = :name", {name: "37signals' go'es agains"}]).first
end
- def test_bind_arity
- assert_nothing_raised { bind '' }
- assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
-
- assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
- assert_nothing_raised { bind '?', 1 }
- assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
- end
-
def test_named_bind_variables
- assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
- assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
-
- assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
-
assert_kind_of Firm, Company.where(["name = :name", { name: "37signals" }]).first
assert_nil Company.where(["name = :name", { name: "37signals!" }]).first
assert_nil Company.where(["name = :name", { name: "37signals!' OR 1=1" }]).first
assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on
end
- def test_named_bind_arity
- assert_nothing_raised { bind "name = :name", { name: "37signals" } }
- assert_nothing_raised { bind "name = :name", { name: "37signals", id: 1 } }
- assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } }
- end
-
- class SimpleEnumerable
- include Enumerable
-
- def initialize(ary)
- @ary = ary
- end
-
- def each(&b)
- @ary.each(&b)
- end
- end
-
- def test_bind_enumerable
- quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
-
- assert_equal '1,2,3', bind('?', [1, 2, 3])
- assert_equal quoted_abc, bind('?', %w(a b c))
-
- assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
- assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
-
- assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
- assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
-
- assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3]))
- assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # '
- end
-
- def test_bind_empty_enumerable
- quoted_nil = ActiveRecord::Base.connection.quote(nil)
- assert_equal quoted_nil, bind('?', [])
- assert_equal " in (#{quoted_nil})", bind(' in (?)', [])
- assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
- end
-
- def test_bind_empty_string
- quoted_empty = ActiveRecord::Base.connection.quote('')
- assert_equal quoted_empty, bind('?', '')
- end
-
- def test_bind_chars
- quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
- quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
- assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi")
- assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper")
- assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
- assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
- end
-
- def test_bind_record
- o = Struct.new(:quoted_id).new(1)
- assert_equal '1', bind('?', o)
-
- os = [o] * 3
- assert_equal '1,1,1', bind('?', os)
- end
-
- def test_named_bind_with_postgresql_type_casts
- l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
- assert_nothing_raised(&l)
- assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
- end
-
def test_string_sanitation
assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
@@ -1003,10 +993,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
- assert_equal 2, Post.includes(authors: :author_address).order('author_addresses.id DESC ').limit(2).to_a.size
+ assert_equal 2, Post.includes(authors: :author_address).
+ where.not(author_addresses: { id: nil }).
+ order('author_addresses.id DESC').limit(2).to_a.size
assert_equal 3, Post.includes(author: :author_address, authors: :author_address).
- order('author_addresses_authors.id DESC ').limit(3).to_a.size
+ where.not(author_addresses_authors: { id: nil }).
+ order('author_addresses_authors.id DESC').limit(3).to_a.size
end
def test_find_with_nil_inside_set_passed_for_one_attribute
@@ -1136,14 +1129,6 @@ class FinderTest < ActiveRecord::TestCase
end
protected
- def bind(statement, *vars)
- if vars.first.is_a?(Hash)
- ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
- else
- ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
- end
- end
-
def table_with_custom_primary_key
yield(Class.new(Toy) do
def self.name
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index f30ed4fcc8..da934ab8fe 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -184,7 +184,6 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_fixtures_from_root_yml_with_instantiation
- # assert_equal 2, @accounts.size
assert_equal 50, @unknown.credit_limit
end
@@ -224,6 +223,10 @@ class FixturesTest < ActiveRecord::TestCase
assert_equal(%(table "parrots" has no column named "arrr".), e.message)
end
+ def test_yaml_file_with_symbol_columns
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "trees")
+ end
+
def test_omap_fixtures
assert_nothing_raised do
fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered")
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index d82a3040fc..95f8706d73 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -47,11 +47,11 @@ def in_memory_db?
end
def subsecond_precision_supported?
- !current_adapter?(:MysqlAdapter, :Mysql2Adapter) || ActiveRecord::Base.connection.version >= '5.6.4'
+ ActiveRecord::Base.connection.supports_datetime_with_precision?
end
def mysql_enforcing_gtid_consistency?
- current_adapter?(:MysqlAdapter, :Mysql2Adapter) && 'ON' == ActiveRecord::Base.connection.show_variable('enforce_gtid_consistency')
+ current_adapter?(:Mysql2Adapter) && 'ON' == ActiveRecord::Base.connection.show_variable('enforce_gtid_consistency')
end
def supports_savepoints?
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 52e3734dd0..03bce547da 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -478,4 +478,49 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
product = Shop::Product.new(:type => phone)
assert product.save
end
+
+ def test_inheritance_new_with_subclass_as_default
+ original_type = Company.columns_hash["type"].default
+ ActiveRecord::Base.connection.change_column_default :companies, :type, 'Firm'
+ Company.reset_column_information
+
+ firm = Company.new # without arguments
+ assert_equal 'Firm', firm.type
+ assert_instance_of Firm, firm
+
+ firm = Company.new(firm_name: 'Shri Hans Plastic') # with arguments
+ assert_equal 'Firm', firm.type
+ assert_instance_of Firm, firm
+
+ firm = Company.new(type: 'Client') # overwrite the default type
+ assert_equal 'Client', firm.type
+ assert_instance_of Client, firm
+ ensure
+ ActiveRecord::Base.connection.change_column_default :companies, :type, original_type
+ Company.reset_column_information
+ end
+end
+
+class InheritanceAttributeTest < ActiveRecord::TestCase
+
+ class Company < ActiveRecord::Base
+ self.table_name = 'companies'
+ attribute :type, :string, default: "InheritanceAttributeTest::Startup"
+ end
+
+ class Startup < Company
+ end
+
+ class Empire < Company
+ end
+
+ def test_inheritance_new_with_subclass_as_default
+ startup = Company.new # without arguments
+ assert_equal 'InheritanceAttributeTest::Startup', startup.type
+ assert_instance_of Startup, startup
+
+ empire = Company.new(type: 'InheritanceAttributeTest::Empire') # without arguments
+ assert_equal 'InheritanceAttributeTest::Empire', empire.type
+ assert_instance_of Empire, empire
+ end
end
diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb
index 6523fc29fd..a16b52751a 100644
--- a/activerecord/test/cases/invalid_connection_test.rb
+++ b/activerecord/test/cases/invalid_connection_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
+if current_adapter?(:Mysql2Adapter)
class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
self.use_transactional_tests = false
@@ -9,7 +10,7 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
def setup
# Can't just use current adapter; sqlite3 will create a database
# file on the fly.
- Bird.establish_connection adapter: 'mysql', database: 'i_do_not_exist'
+ Bird.establish_connection adapter: 'mysql2', database: 'i_do_not_exist'
end
teardown do
@@ -20,3 +21,4 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
assert_equal "#{Bird.name} (call '#{Bird.name}.connection' to establish a connection)", Bird.inspect
end
end
+end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 0da58040c8..e030f6c588 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -5,7 +5,7 @@ end
module ActiveRecord
class InvertibleMigrationTest < ActiveRecord::TestCase
- class SilentMigration < ActiveRecord::Migration
+ class SilentMigration < ActiveRecord::Migration::Current
def write(text = '')
# sssshhhhh!!
end
@@ -105,7 +105,7 @@ module ActiveRecord
end
end
- class LegacyMigration < ActiveRecord::Migration
+ class LegacyMigration < ActiveRecord::Migration::Current
def self.up
create_table("horses") do |t|
t.column :content, :text
@@ -354,7 +354,7 @@ module ActiveRecord
end
# MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns
- unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :OracleAdapter)
+ unless current_adapter?(:Mysql2Adapter, :OracleAdapter)
def test_migrate_revert_add_index_with_name
RevertNamedIndexMigration1.new.migrate(:up)
RevertNamedIndexMigration2.new.migrate(:up)
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 2e1363334d..4fe76e563a 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -441,7 +441,7 @@ unless in_memory_db?
def test_lock_sending_custom_lock_statement
Person.transaction do
person = Person.find(1)
- assert_sql(/LIMIT 1 FOR SHARE NOWAIT/) do
+ assert_sql(/LIMIT \$\d FOR SHARE NOWAIT/) do
person.lock!('FOR SHARE NOWAIT')
end
end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 2ff9cf8cf5..d6963b48d7 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -50,7 +50,7 @@ module ActiveRecord
def test_create_table_with_defaults
# MySQL doesn't allow defaults on TEXT or BLOB columns.
- mysql = current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ mysql = current_adapter?(:Mysql2Adapter)
connection.create_table :testings do |t|
t.column :one, :string, :default => "hello"
@@ -141,7 +141,7 @@ module ActiveRecord
assert_equal 'smallint', one.sql_type
assert_equal 'integer', four.sql_type
assert_equal 'bigint', eight.sql_type
- elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ elsif current_adapter?(:Mysql2Adapter)
assert_match 'int(11)', default.sql_type
assert_match 'tinyint', one.sql_type
assert_match 'int', four.sql_type
@@ -339,7 +339,7 @@ module ActiveRecord
def test_change_column_null
testing_table_with_only_foo_attribute do
- notnull_migration = Class.new(ActiveRecord::Migration) do
+ notnull_migration = Class.new(ActiveRecord::Migration::Current) do
def change
change_column_null :testings, :foo, false
end
@@ -442,7 +442,7 @@ module ActiveRecord
end
def test_create_table_with_force_cascade_drops_dependent_objects
- skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:Mysql2Adapter)
# can't re-create table referenced by foreign key
assert_raises(ActiveRecord::StatementInvalid) do
@connection.create_table :trains, force: true
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 8d8e661aa5..c7a1b81a75 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -37,13 +37,13 @@ module ActiveRecord
def test_add_column_without_limit
# TODO: limit: nil should work with all adapters.
- skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:Mysql2Adapter)
add_column :test_models, :description, :string, limit: nil
TestModel.reset_column_information
assert_nil TestModel.columns_hash["description"].limit
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_unabstracted_database_dependent_types
add_column :test_models, :intelligence_quotient, :tinyint
TestModel.reset_column_information
@@ -63,8 +63,6 @@ module ActiveRecord
# Do a manual insertion
if current_adapter?(:OracleAdapter)
connection.execute "insert into test_models (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
- elsif current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003 #before MySQL 5.0.3 decimals stored as strings
- connection.execute "insert into test_models (wealth) values ('12345678901234567890.0123456789')"
elsif current_adapter?(:PostgreSQLAdapter)
connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)"
else
@@ -171,7 +169,7 @@ module ActiveRecord
end
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
def test_out_of_range_limit_should_raise
assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 }
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
index 4637970ce0..8294da0373 100644
--- a/activerecord/test/cases/migration/column_positioning_test.rb
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -23,7 +23,7 @@ module ActiveRecord
ActiveRecord::Base.primary_key_prefix_type = nil
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_column_positioning
assert_equal %w(first second third), conn.columns(:testings).map(&:name)
end
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index ab3f584350..fca1cb7e97 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -62,7 +62,7 @@ module ActiveRecord
assert_equal '70000', default_after
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_mysql_rename_column_preserves_auto_increment
rename_column "test_models", "id", "id_test"
assert connection.columns("test_models").find { |c| c.name == "id_test" }.auto_increment?
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
new file mode 100644
index 0000000000..b1e1d72944
--- /dev/null
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -0,0 +1,58 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class CompatibilityTest < ActiveRecord::TestCase
+ attr_reader :connection
+ self.use_transactional_tests = false
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :string, :limit => 100
+ end
+ end
+
+ teardown do
+ connection.drop_table :testings rescue nil
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::SchemaMigration.delete_all
+ end
+
+ def test_migration_doesnt_remove_named_index
+ connection.add_index :testings, :foo, :name => "custom_index_name"
+
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
+ def version; 101 end
+ def migrate(x)
+ remove_index :testings, :foo
+ end
+ }.new
+
+ assert connection.index_exists?(:testings, :foo, name: "custom_index_name")
+ assert_raise(StandardError) { ActiveRecord::Migrator.new(:up, [migration]).migrate }
+ assert connection.index_exists?(:testings, :foo, name: "custom_index_name")
+ end
+
+ def test_migration_does_remove_unnamed_index
+ connection.add_index :testings, :bar
+
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
+ def version; 101 end
+ def migrate(x)
+ remove_index :testings, :bar
+ end
+ }.new
+
+ assert connection.index_exists?(:testings, :bar)
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+ assert_not connection.index_exists?(:testings, :bar)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 72f2fa95f1..01162dcefe 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -99,7 +99,7 @@ module ActiveRecord
assert_equal 1, foreign_keys.size
fk = foreign_keys.first
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
# ON DELETE RESTRICT is the default on MySQL
assert_equal nil, fk.on_delete
else
@@ -224,7 +224,7 @@ module ActiveRecord
assert_match %r{\s+add_foreign_key "astronauts",.+on_update: :cascade,.+on_delete: :nullify$}, output
end
- class CreateCitiesAndHousesMigration < ActiveRecord::Migration
+ class CreateCitiesAndHousesMigration < ActiveRecord::Migration::Current
def change
create_table("cities") { |t| }
@@ -243,7 +243,7 @@ module ActiveRecord
silence_stream($stdout) { migration.migrate(:down) }
end
- class CreateSchoolsAndClassesMigration < ActiveRecord::Migration
+ class CreateSchoolsAndClassesMigration < ActiveRecord::Migration::Current
def change
create_table(:schools)
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index b23b9a679f..5abd37bfa2 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -130,7 +130,17 @@ module ActiveRecord
def test_named_index_exists
connection.add_index :testings, :foo, :name => "custom_index_name"
+ assert connection.index_exists?(:testings, :foo)
assert connection.index_exists?(:testings, :foo, :name => "custom_index_name")
+ assert !connection.index_exists?(:testings, :foo, :name => "other_index_name")
+ end
+
+ def test_remove_named_index
+ connection.add_index :testings, :foo, :name => "custom_index_name"
+
+ assert connection.index_exists?(:testings, :foo)
+ connection.remove_index :testings, :foo
+ assert !connection.index_exists?(:testings, :foo)
end
def test_add_index_attribute_length_limit
@@ -176,7 +186,7 @@ module ActiveRecord
connection.remove_index("testings", :name => "named_admin")
# Selected adapters support index sort order
- if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+ if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
connection.add_index("testings", ["last_name"], :order => {:last_name => :desc})
connection.remove_index("testings", ["last_name"])
connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc})
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 8eb027d53f..b926a92849 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -80,12 +80,10 @@ module ActiveRecord
end
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
- enable_extension!('uuid-ossp', connection)
connection.create_table :cats, id: :uuid
assert_nothing_raised { rename_table :cats, :felines }
ActiveSupport::Deprecation.silence { assert connection.table_exists? :felines }
ensure
- disable_extension!('uuid-ossp', connection)
connection.drop_table :cats, if_exists: true
connection.drop_table :felines, if_exists: true
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index c3c204cf9f..f51e366b1d 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -132,12 +132,12 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_migration_instance_has_connection
- migration = Class.new(ActiveRecord::Migration).new
+ migration = Class.new(ActiveRecord::Migration::Current).new
assert_equal ActiveRecord::Base.connection, migration.connection
end
def test_method_missing_delegates_to_connection
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration::Current) {
def connection
Class.new {
def create_table; "hi mom!"; end
@@ -226,7 +226,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
end
- class MockMigration < ActiveRecord::Migration
+ class MockMigration < ActiveRecord::Migration::Current
attr_reader :went_up, :went_down
def initialize
@went_up = false
@@ -268,7 +268,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_one_up_with_exception_and_rollback
assert_no_column Person, :last_name
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration::Current) {
def version; 100 end
def migrate(x)
add_column "people", "last_name", :string
@@ -289,7 +289,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_one_up_with_exception_and_rollback_using_run
assert_no_column Person, :last_name
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration::Current) {
def version; 100 end
def migrate(x)
add_column "people", "last_name", :string
@@ -301,7 +301,7 @@ class MigrationTest < ActiveRecord::TestCase
e = assert_raise(StandardError) { migrator.run }
- assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message
+ assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
assert_no_column Person, :last_name,
"On error, the Migrator should revert schema changes but it did not."
@@ -310,7 +310,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_migration_without_transaction
assert_no_column Person, :last_name
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration::Current) {
self.disable_ddl_transaction!
def version; 101 end
@@ -354,6 +354,78 @@ class MigrationTest < ActiveRecord::TestCase
Reminder.reset_table_name
end
+ def test_internal_metadata_table_name
+ original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name
+
+ assert_equal "active_record_internal_metadatas", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_active_record_internal_metadatas_suffix", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.internal_metadata_table_name = "changed"
+ Reminder.reset_table_name
+ assert_equal "prefix_changed_suffix", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ assert_equal "changed", ActiveRecord::InternalMetadata.table_name
+ ensure
+ ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
+ Reminder.reset_table_name
+ end
+
+ def test_internal_metadata_stores_environment
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ old_path = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
+
+ original_rails_env = ENV["RAILS_ENV"]
+ original_rack_env = ENV["RACK_ENV"]
+ ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
+ new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+
+ refute_equal current_env, new_env
+
+ sleep 1 # mysql by default does not store fractional seconds in the database
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
+ ensure
+ ActiveRecord::Migrator.migrations_paths = old_path
+ ENV["RAILS_ENV"] = original_rails_env
+ ENV["RACK_ENV"] = original_rack_env
+ end
+
+
+ def test_migration_sets_internal_metadata_even_when_fully_migrated
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ old_path = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
+
+ original_rails_env = ENV["RAILS_ENV"]
+ original_rack_env = ENV["RACK_ENV"]
+ ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
+ new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+
+ refute_equal current_env, new_env
+
+ sleep 1 # mysql by default does not store fractional seconds in the database
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
+ ensure
+ ActiveRecord::Migrator.migrations_paths = old_path
+ ENV["RAILS_ENV"] = original_rails_env
+ ENV["RACK_ENV"] = original_rack_env
+ end
+
def test_proper_table_name_on_migration
reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
@@ -500,7 +572,7 @@ class MigrationTest < ActiveRecord::TestCase
end
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
def test_out_of_range_limit_should_raise
Person.connection.drop_table :test_limits rescue nil
e = assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do
@@ -525,7 +597,7 @@ class MigrationTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.supports_advisory_locks?
def test_migrator_generates_valid_lock_id
- migration = Class.new(ActiveRecord::Migration).new
+ migration = Class.new(ActiveRecord::Migration::Current).new
migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
lock_id = migrator.send(:generate_migrator_advisory_lock_id)
@@ -539,7 +611,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_generate_migrator_advisory_lock_id
# It is important we are consistent with how we generate this so that
# exclusive locking works across migrator versions
- migration = Class.new(ActiveRecord::Migration).new
+ migration = Class.new(ActiveRecord::Migration::Current).new
migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
lock_id = migrator.send(:generate_migrator_advisory_lock_id)
@@ -549,14 +621,13 @@ class MigrationTest < ActiveRecord::TestCase
expected_id = Zlib.crc32(current_database) * salt
assert lock_id == expected_id, "expected lock id generated by the migrator to be #{expected_id}, but it was #{lock_id} instead"
- assert lock_id.is_a?(Fixnum), "expected lock id to be a Fixnum, but it wasn't"
assert lock_id.bit_length <= 63, "lock id must be a signed integer of max 63 bits magnitude"
end
def test_migrator_one_up_with_unavailable_lock
assert_no_column Person, :last_name
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration::Current) {
def version; 100 end
def migrate(x)
add_column "people", "last_name", :string
@@ -577,7 +648,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_one_up_with_unavailable_lock_using_run
assert_no_column Person, :last_name
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration::Current) {
def version; 100 end
def migrate(x)
add_column "people", "last_name", :string
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index dbf088f455..86eca53141 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -6,7 +6,7 @@ class MigratorTest < ActiveRecord::TestCase
# Use this class to sense if migrations have gone
# up or down.
- class Sensor < ActiveRecord::Migration
+ class Sensor < ActiveRecord::Migration::Current
attr_reader :went_up, :went_down
def initialize name = self.class.name, version = nil
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b2e59e3970..af15e63d9c 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -19,6 +19,8 @@ require 'models/person'
require 'models/pet'
require 'models/ship'
require 'models/toy'
+require 'models/admin'
+require 'models/admin/user'
require 'rexml/document'
class PersistenceTest < ActiveRecord::TestCase
@@ -161,7 +163,24 @@ class PersistenceTest < ActiveRecord::TestCase
assert !company.valid?
original_errors = company.errors
client = company.becomes(Client)
- assert_equal original_errors, client.errors
+ assert_equal original_errors.keys, client.errors.keys
+ end
+
+ def test_becomes_errors_base
+ child_class = Class.new(Admin::User) do
+ store_accessor :settings, :foo
+
+ def self.name; 'Admin::ChildUser'; end
+ end
+
+ admin = Admin::User.new
+ admin.errors.add :token, :invalid
+ child = admin.becomes(child_class)
+
+ assert_equal [:token], child.errors.keys
+ assert_nothing_raised do
+ child.errors.add :foo, :invalid
+ end
end
def test_dupd_becomes_persists_changes_from_the_original
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index d883784553..7e18313c00 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -268,7 +268,7 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
end
end
-if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+if current_adapter?(:Mysql2Adapter)
class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
self.use_transactional_tests = false
@@ -308,7 +308,7 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
end
end
-if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
+if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
class PrimaryKeyBigSerialTest < ActiveRecord::TestCase
include SchemaDumpingHelper
@@ -351,7 +351,7 @@ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
end
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
test "primary key column type with options" do
@connection.create_table(:widgets, id: :primary_key, limit: 8, unsigned: true, force: true)
column = @connection.columns(:widgets).find { |c| c.name == 'id' }
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index b4269bd56d..f0e07e0731 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -27,7 +27,7 @@ module ActiveRecord
module DelegationWhitelistBlacklistTests
ARRAY_DELEGATES = [
- :+, :-, :|, :&, :[],
+ :+, :-, :|, :&, :[], :shuffle,
:all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
:exclude?, :find_all, :flat_map, :group_by, :include?, :length,
:map, :none?, :one?, :partition, :reject, :reverse,
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 0a2e874e4f..60a806c05a 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -104,6 +104,13 @@ class RelationMergingTest < ActiveRecord::TestCase
post = PostThatLoadsCommentsInAnAfterSaveHook.create!(title: "First Post", body: "Blah blah blah.")
assert_equal "First comment!", post.comments.where(body: "First comment!").first_or_create.body
end
+
+ def test_merging_with_from_clause
+ relation = Post.all
+ assert relation.from_clause.empty?
+ relation = relation.merge(Post.from("posts"))
+ refute relation.from_clause.empty?
+ end
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 2006fc9611..28a0862f91 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -52,9 +52,11 @@ module ActiveRecord
end
def test_or_with_incompatible_relations
- assert_raises ArgumentError do
+ error = assert_raises ArgumentError do
Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a
end
+
+ assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
end
def test_or_when_grouping
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index f46d414b95..03583344a8 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -43,13 +43,17 @@ module ActiveRecord
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
- assert_equal({}, relation.create_with_value)
+ value = relation.create_with_value
+ assert_equal({}, value)
+ assert_predicate value, :frozen?
end
def test_multi_value_initialize
relation = Relation.new(FakeKlass, :b, nil)
Relation::MULTI_VALUE_METHODS.each do |method|
- assert_equal [], relation.send("#{method}_values"), method.to_s
+ values = relation.send("#{method}_values")
+ assert_equal [], values, method.to_s
+ assert_predicate values, :frozen?, method.to_s
end
end
@@ -77,7 +81,6 @@ module ActiveRecord
def test_tree_is_not_traversed
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
@@ -104,7 +107,6 @@ module ActiveRecord
def test_create_with_value_with_wheres
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
@@ -115,7 +117,6 @@ module ActiveRecord
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
assert_equal({}, relation.scope_for_create)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index cd23c1b3e1..0638edacbd 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -18,6 +18,7 @@ require 'models/minivan'
require 'models/aircraft'
require "models/possession"
require "models/reader"
+require "models/categorization"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@@ -110,15 +111,38 @@ class RelationTest < ActiveRecord::TestCase
def test_loaded_first
topics = Topic.all.order('id ASC')
+ topics.to_a # force load
- assert_queries(1) do
- topics.to_a # force load
- 2.times { assert_equal "The First Topic", topics.first.title }
+ assert_no_queries do
+ assert_equal "The First Topic", topics.first.title
+ end
+
+ assert topics.loaded?
+ end
+
+ def test_loaded_first_with_limit
+ topics = Topic.all.order('id ASC')
+ topics.to_a # force load
+
+ assert_no_queries do
+ assert_equal ["The First Topic",
+ "The Second Topic of the day"], topics.first(2).map(&:title)
end
assert topics.loaded?
end
+ def test_first_get_more_than_available
+ topics = Topic.all.order('id ASC')
+ unloaded_first = topics.first(10)
+ topics.to_a # force load
+
+ assert_no_queries do
+ loaded_first = topics.first(10)
+ assert_equal unloaded_first, loaded_first
+ end
+ end
+
def test_reload
topics = Topic.all
@@ -918,6 +942,12 @@ class RelationTest < ActiveRecord::TestCase
assert authors.exists?(authors(:david).id)
end
+ def test_any_with_scope_on_hash_includes
+ post = authors(:david).posts.first
+ categories = Categorization.includes(author: :posts).where(posts: { id: post.id })
+ assert categories.exists?
+ end
+
def test_last
authors = Author.all
assert_equal authors(:bob), authors.last
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 14e392ac30..239f63d27b 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -25,6 +25,16 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars])
end
+ def test_sanitize_sql_array_handles_named_bind_variables
+ quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
+ assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi"])
+ assert_equal "name=#{quoted_bambi} AND id=1", Binary.send(:sanitize_sql_array, ["name=:name AND id=:id", name: "Bambi", id: 1])
+
+ quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name AND name2=:name", name: "Bambi\nand\nThumper"])
+ end
+
def test_sanitize_sql_array_handles_relations
david = Author.create!(name: 'David')
david_posts = david.posts.select(:id)
@@ -69,4 +79,98 @@ class SanitizeTest < ActiveRecord::TestCase
searchable_post.search("20% _reduction_!").to_a
end
end
+
+ def test_bind_arity
+ assert_nothing_raised { bind '' }
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
+
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
+ assert_nothing_raised { bind '?', 1 }
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
+ end
+
+ def test_named_bind_variables
+ assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
+ assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
+
+ assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
+ end
+
+ def test_named_bind_arity
+ assert_nothing_raised { bind "name = :name", { name: "37signals" } }
+ assert_nothing_raised { bind "name = :name", { name: "37signals", id: 1 } }
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } }
+ end
+
+ class SimpleEnumerable
+ include Enumerable
+
+ def initialize(ary)
+ @ary = ary
+ end
+
+ def each(&b)
+ @ary.each(&b)
+ end
+ end
+
+ def test_bind_enumerable
+ quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
+
+ assert_equal '1,2,3', bind('?', [1, 2, 3])
+ assert_equal quoted_abc, bind('?', %w(a b c))
+
+ assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
+ assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
+
+ assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
+ assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
+
+ assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3]))
+ assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # '
+ end
+
+ def test_bind_empty_enumerable
+ quoted_nil = ActiveRecord::Base.connection.quote(nil)
+ assert_equal quoted_nil, bind('?', [])
+ assert_equal " in (#{quoted_nil})", bind(' in (?)', [])
+ assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
+ end
+
+ def test_bind_empty_string
+ quoted_empty = ActiveRecord::Base.connection.quote('')
+ assert_equal quoted_empty, bind('?', '')
+ end
+
+ def test_bind_chars
+ quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
+ quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
+ assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi")
+ assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper")
+ assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
+ assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
+ end
+
+ def test_bind_record
+ o = Struct.new(:quoted_id).new(1)
+ assert_equal '1', bind('?', o)
+
+ os = [o] * 3
+ assert_equal '1,1,1', bind('?', os)
+ end
+
+ def test_named_bind_with_postgresql_type_casts
+ l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
+ assert_nothing_raised(&l)
+ assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
+ end
+
+ private
+ def bind(statement, *vars)
+ if vars.first.is_a?(Hash)
+ ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
+ else
+ ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
+ end
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 43f133b12d..7b93d20e05 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -38,6 +38,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
end
def test_schema_dump_uses_force_cascade_on_create_table
@@ -117,8 +118,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*}, output
assert_no_match %r{c_int_4.*limit:}, output
- elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
- assert_match %r{c_int_without_limit.*limit: 4}, output
+ elsif current_adapter?(:Mysql2Adapter)
+ assert_match %r{c_int_without_limit"$}, output
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
@@ -158,6 +159,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
end
def test_schema_dump_with_regexp_ignored_table
@@ -165,11 +167,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
end
def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
@@ -180,7 +183,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
- elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ elsif current_adapter?(:Mysql2Adapter)
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
@@ -201,7 +204,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
def test_schema_dump_should_add_default_value_for_mysql_text_field
output = standard_dump
assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output
@@ -312,7 +315,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
- class CreateDogMigration < ActiveRecord::Migration
+ class CreateDogMigration < ActiveRecord::Migration::Current
def up
create_table("dog_owners") do |t|
end
@@ -342,6 +345,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "foo_.+_bar"}, output
assert_no_match %r{add_index "foo_.+_bar"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
if ActiveRecord::Base.connection.supports_foreign_keys?
assert_no_match %r{add_foreign_key "foo_.+_bar"}, output
@@ -357,7 +361,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_with_table_name_prefix_and_ignoring_tables
original, $stdout = $stdout, StringIO.new
- create_cat_migration = Class.new(ActiveRecord::Migration) do
+ create_cat_migration = Class.new(ActiveRecord::Migration::Current) do
def change
create_table("cats") do |t|
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 86316ab476..ad5ca70f36 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -459,4 +459,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
scope = Bus.all
assert_equal scope.where_clause.ast.children.length, 1
end
+
+ def test_sti_conditions_are_not_carried_in_default_scope
+ ConditionalStiPost.create! body: ''
+ SubConditionalStiPost.create! body: ''
+ SubConditionalStiPost.create! title: 'Hello world', body: ''
+
+ assert_equal 2, ConditionalStiPost.count
+ assert_equal 2, ConditionalStiPost.all.to_a.size
+ assert_equal 3, ConditionalStiPost.unscope(where: :title).to_a.size
+
+ assert_equal 1, SubConditionalStiPost.count
+ assert_equal 1, SubConditionalStiPost.all.to_a.size
+ assert_equal 2, SubConditionalStiPost.unscope(where: :title).to_a.size
+ end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 4bfffbe9c6..c15d57460b 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -209,9 +209,23 @@ class RelationScopingTest < ActiveRecord::TestCase
assert_not_equal [], Developer.all
end
- def test_current_scope_does_not_pollute_other_subclasses
- Post.none.scoping do
- assert StiPost.all.any?
+ def test_current_scope_does_not_pollute_sibling_subclasses
+ Comment.none.scoping do
+ assert_not SpecialComment.all.any?
+ assert_not VerySpecialComment.all.any?
+ assert_not SubSpecialComment.all.any?
+ end
+
+ SpecialComment.none.scoping do
+ assert Comment.all.any?
+ assert VerySpecialComment.all.any?
+ assert_not SubSpecialComment.all.any?
+ end
+
+ SubSpecialComment.none.scoping do
+ assert Comment.all.any?
+ assert VerySpecialComment.all.any?
+ assert SpecialComment.all.any?
end
end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index c8f4179313..49df6628eb 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -12,12 +12,39 @@ module ActiveRecord
end
ADAPTERS_TASKS = {
- mysql: :mysql_tasks,
mysql2: :mysql_tasks,
postgresql: :postgresql_tasks,
sqlite3: :sqlite_tasks
}
+ class DatabaseTasksUtilsTask< ActiveRecord::TestCase
+ def test_raises_an_error_when_called_with_protected_environment
+ ActiveRecord::Migrator.stubs(:current_version).returns(1)
+
+ protected_environments = ActiveRecord::Base.protected_environments.dup
+ current_env = ActiveRecord::Migrator.current_environment
+ assert !protected_environments.include?(current_env)
+ # Assert no error
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments << current_env
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+ ensure
+ ActiveRecord::Base.protected_environments = protected_environments
+ end
+
+ def test_raises_an_error_if_no_migrations_have_been_made
+ ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false)
+ ActiveRecord::Migrator.stubs(:current_version).returns(1)
+
+ assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+ end
+ end
+
class DatabaseTasksRegisterTask < ActiveRecord::TestCase
def test_register_task
klazz = Class.new do
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 723a7618ba..1632f04854 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -1,12 +1,12 @@
require 'cases/helper'
-if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+if current_adapter?(:Mysql2Adapter)
module ActiveRecord
class MysqlDBCreateTest < ActiveRecord::TestCase
def setup
@connection = stub(:create_database => true)
@configuration = {
- 'adapter' => 'mysql',
+ 'adapter' => 'mysql2',
'database' => 'my-app-db'
}
@@ -16,7 +16,7 @@ module ActiveRecord
def test_establishes_connection_without_database
ActiveRecord::Base.expects(:establish_connection).
- with('adapter' => 'mysql', 'database' => nil)
+ with('adapter' => 'mysql2', 'database' => nil)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
@@ -59,97 +59,94 @@ module ActiveRecord
end
end
- if current_adapter?(:MysqlAdapter)
- class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
- def setup
- @connection = stub("Connection", create_database: true)
- @error = Mysql::Error.new "Invalid permissions"
- @configuration = {
- 'adapter' => 'mysql',
- 'database' => 'my-app-db',
- 'username' => 'pat',
- 'password' => 'wossname'
- }
-
- $stdin.stubs(:gets).returns("secret\n")
- $stdout.stubs(:print).returns(nil)
- @error.stubs(:errno).returns(1045)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).
- raises(@error).
- then.returns(true)
- end
+ class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub("Connection", create_database: true)
+ @error = Mysql2::Error.new("Invalid permissions")
+ @configuration = {
+ 'adapter' => 'mysql2',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'wossname'
+ }
- if defined?(::Mysql)
- def test_root_password_is_requested
- assert_permissions_granted_for "pat"
- $stdin.expects(:gets).returns("secret\n")
+ $stdin.stubs(:gets).returns("secret\n")
+ $stdout.stubs(:print).returns(nil)
+ @error.stubs(:errno).returns(1045)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).
+ raises(@error).
+ then.returns(true)
+ end
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
- end
+ def test_root_password_is_requested
+ assert_permissions_granted_for("pat")
+ $stdin.expects(:gets).returns("secret\n")
- def test_connection_established_as_root
- assert_permissions_granted_for "pat"
- ActiveRecord::Base.expects(:establish_connection).with(
- 'adapter' => 'mysql',
- 'database' => nil,
- 'username' => 'root',
- 'password' => 'secret'
- )
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
+ def test_connection_established_as_root
+ assert_permissions_granted_for("pat")
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'mysql2',
+ 'database' => nil,
+ 'username' => 'root',
+ 'password' => 'secret'
+ )
- def test_database_created_by_root
- assert_permissions_granted_for "pat"
- @connection.expects(:create_database).
- with('my-app-db', {})
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
+ def test_database_created_by_root
+ assert_permissions_granted_for("pat")
+ @connection.expects(:create_database).
+ with('my-app-db', {})
- def test_grant_privileges_for_normal_user
- assert_permissions_granted_for "pat"
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
- def test_do_not_grant_privileges_for_root_user
- @configuration['username'] = 'root'
- @configuration['password'] = ''
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
+ def test_grant_privileges_for_normal_user
+ assert_permissions_granted_for("pat")
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
- def test_connection_established_as_normal_user
- assert_permissions_granted_for "pat"
- ActiveRecord::Base.expects(:establish_connection).returns do
- ActiveRecord::Base.expects(:establish_connection).with(
- 'adapter' => 'mysql',
- 'database' => 'my-app-db',
- 'username' => 'pat',
- 'password' => 'secret'
- )
+ def test_do_not_grant_privileges_for_root_user
+ @configuration['username'] = 'root'
+ @configuration['password'] = ''
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
- raise @error
- end
+ def test_connection_established_as_normal_user
+ assert_permissions_granted_for("pat")
+ ActiveRecord::Base.expects(:establish_connection).returns do
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'mysql2',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'secret'
+ )
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ raise @error
end
- def test_sends_output_to_stderr_when_other_errors
- @error.stubs(:errno).returns(42)
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_sends_output_to_stderr_when_other_errors
+ @error.stubs(:errno).returns(42)
- $stderr.expects(:puts).at_least_once.returns(nil)
+ $stderr.expects(:puts).at_least_once.returns(nil)
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ private
- private
- def assert_permissions_granted_for(db_user)
- db_name = @configuration['database']
- db_password = @configuration['password']
- @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;")
- end
+ def assert_permissions_granted_for(db_user)
+ db_name = @configuration['database']
+ db_password = @configuration['password']
+ @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;")
end
end
@@ -157,7 +154,7 @@ module ActiveRecord
def setup
@connection = stub(:drop_database => true)
@configuration = {
- 'adapter' => 'mysql',
+ 'adapter' => 'mysql2',
'database' => 'my-app-db'
}
@@ -182,7 +179,7 @@ module ActiveRecord
def setup
@connection = stub(:recreate_database => true)
@configuration = {
- 'adapter' => 'mysql',
+ 'adapter' => 'mysql2',
'database' => 'test-db'
}
@@ -216,7 +213,7 @@ module ActiveRecord
def setup
@connection = stub(:create_database => true)
@configuration = {
- 'adapter' => 'mysql',
+ 'adapter' => 'mysql2',
'database' => 'my-app-db'
}
@@ -234,7 +231,7 @@ module ActiveRecord
def setup
@connection = stub(:create_database => true)
@configuration = {
- 'adapter' => 'mysql',
+ 'adapter' => 'mysql2',
'database' => 'my-app-db'
}
@@ -251,7 +248,7 @@ module ActiveRecord
class MySQLStructureDumpTest < ActiveRecord::TestCase
def setup
@configuration = {
- 'adapter' => 'mysql',
+ 'adapter' => 'mysql2',
'database' => 'test-db'
}
end
@@ -297,7 +294,7 @@ module ActiveRecord
class MySQLStructureLoadTest < ActiveRecord::TestCase
def setup
@configuration = {
- 'adapter' => 'mysql',
+ 'adapter' => 'mysql2',
'database' => 'test-db'
}
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index c31f94b2f2..ba53f340ae 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -212,7 +212,7 @@ module ActiveRecord
def test_structure_dump_with_schema_search_path
@configuration['schema_search_path'] = 'foo,bar'
- Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', 'my-app-db').returns(true)
+ Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, '--schema=foo', '--schema=bar', 'my-app-db').returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
end
@@ -228,7 +228,7 @@ module ActiveRecord
end
def test_structure_dump_with_dump_schemas_string
- Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', "my-app-db").returns(true)
+ Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename, '--schema=foo', '--schema=bar', "my-app-db").returns(true)
with_dump_schemas('foo,bar') do
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 47e664f4e7..87299c0dab 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -77,12 +77,6 @@ module ActiveRecord
end
end
- class MysqlTestCase < TestCase
- def self.run(*args)
- super if current_adapter?(:MysqlAdapter)
- end
- end
-
class SQLite3TestCase < TestCase
def self.run(*args)
super if current_adapter?(:SQLite3Adapter)
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index ff7a81fe60..3b6e4dcc2b 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -10,6 +10,7 @@ class TimePrecisionTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
+ Foo.reset_column_information
end
teardown do
@@ -20,8 +21,8 @@ class TimePrecisionTest < ActiveRecord::TestCase
@connection.create_table(:foos, force: true)
@connection.add_column :foos, :start, :time, precision: 3
@connection.add_column :foos, :finish, :time, precision: 6
- assert_equal 3, activerecord_column_option('foos', 'start', 'precision')
- assert_equal 6, activerecord_column_option('foos', 'finish', 'precision')
+ assert_equal 3, Foo.columns_hash['start'].precision
+ assert_equal 6, Foo.columns_hash['finish'].precision
end
def test_passing_precision_to_time_does_not_set_limit
@@ -29,8 +30,8 @@ class TimePrecisionTest < ActiveRecord::TestCase
t.time :start, precision: 3
t.time :finish, precision: 6
end
- assert_nil activerecord_column_option('foos', 'start', 'limit')
- assert_nil activerecord_column_option('foos', 'finish', 'limit')
+ assert_nil Foo.columns_hash['start'].limit
+ assert_nil Foo.columns_hash['finish'].limit
end
def test_invalid_time_precision_raises_error
@@ -42,15 +43,6 @@ class TimePrecisionTest < ActiveRecord::TestCase
end
end
- def test_database_agrees_with_activerecord_about_precision
- @connection.create_table(:foos, force: true) do |t|
- t.time :start, precision: 2
- t.time :finish, precision: 4
- end
- assert_equal 2, database_datetime_precision('foos', 'start')
- assert_equal 4, database_datetime_precision('foos', 'finish')
- end
-
def test_formatting_time_according_to_precision
@connection.create_table(:foos, force: true) do |t|
t.time :start, precision: 0
@@ -88,21 +80,5 @@ class TimePrecisionTest < ActiveRecord::TestCase
end
end
- private
-
- def database_datetime_precision(table_name, column_name)
- results = @connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
- result = results.find do |result_hash|
- result_hash["column_name"] == column_name
- end
- result && result["datetime_precision"].to_i
- end
-
- def activerecord_column_option(tablename, column_name, option)
- result = @connection.columns(tablename).find do |column|
- column.name == column_name
- end
- result && result.send(option)
- end
end
end
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index 7058f4fbe2..b47769eed7 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -95,16 +95,14 @@ class TouchLaterTest < ActiveRecord::TestCase
end
def test_touching_three_deep
- skip "Pending from #19324"
-
previous_tree_updated_at = trees(:root).updated_at
previous_grandparent_updated_at = nodes(:grandparent).updated_at
previous_parent_updated_at = nodes(:parent_a).updated_at
previous_child_updated_at = nodes(:child_one_of_a).updated_at
- travel 5.seconds
-
- Node.create! parent: nodes(:child_one_of_a), tree: trees(:root)
+ travel 5.seconds do
+ Node.create! parent: nodes(:child_one_of_a), tree: trees(:root)
+ end
assert_not_equal nodes(:child_one_of_a).reload.updated_at, previous_child_updated_at
assert_not_equal nodes(:parent_a).reload.updated_at, previous_parent_updated_at
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index f2229939c8..637f89196e 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -35,9 +35,9 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id"
after_commit { |record| record.do_after_commit(nil) }
- after_commit(on: :create) { |record| record.do_after_commit(:create) }
- after_commit(on: :update) { |record| record.do_after_commit(:update) }
- after_commit(on: :destroy) { |record| record.do_after_commit(:destroy) }
+ after_create_commit { |record| record.do_after_commit(:create) }
+ after_update_commit { |record| record.do_after_commit(:update) }
+ after_destroy_commit { |record| record.do_after_commit(:destroy) }
after_rollback { |record| record.do_after_rollback(nil) }
after_rollback(on: :create) { |record| record.do_after_rollback(:create) }
after_rollback(on: :update) { |record| record.do_after_rollback(:update) }
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index ec5bdfd725..791b895d02 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -58,6 +58,11 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_add_to_null_transaction
+ topic = Topic.new
+ topic.add_to_transaction
+ end
+
def test_successful_with_return
committed = false
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index d50ae74e35..f3c2d2f30e 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -150,7 +150,7 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
-if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :books
@@ -196,7 +196,7 @@ class UpdateableViewTest < ActiveRecord::TestCase
end
end
end
-end # end fo `if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)`
+end # end fo `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)`
end # end fo `if ActiveRecord::Base.connection.supports_views?`
if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index e3b55d640e..58e2d45748 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -51,15 +51,6 @@ connections:
password: arunit
database: arunit2
- mysql:
- arunit:
- username: rails
- encoding: utf8
- collation: utf8_unicode_ci
- arunit2:
- username: rails
- encoding: utf8
-
mysql2:
arunit:
username: rails
diff --git a/activerecord/test/fixtures/author_addresses.yml b/activerecord/test/fixtures/author_addresses.yml
index 7b90572187..cf75e5998d 100644
--- a/activerecord/test/fixtures/author_addresses.yml
+++ b/activerecord/test/fixtures/author_addresses.yml
@@ -3,3 +3,9 @@ david_address:
david_address_extra:
id: 2
+
+mary_address:
+ id: 3
+
+bob_address:
+ id: 4
diff --git a/activerecord/test/fixtures/authors.yml b/activerecord/test/fixtures/authors.yml
index 832236a486..41c124179e 100644
--- a/activerecord/test/fixtures/authors.yml
+++ b/activerecord/test/fixtures/authors.yml
@@ -9,7 +9,9 @@ david:
mary:
id: 2
name: Mary
+ author_address_id: 3
bob:
id: 3
name: Bob
+ author_address_id: 4
diff --git a/activerecord/test/fixtures/naked/yml/trees.yml b/activerecord/test/fixtures/naked/yml/trees.yml
new file mode 100644
index 0000000000..d163b98f21
--- /dev/null
+++ b/activerecord/test/fixtures/naked/yml/trees.yml
@@ -0,0 +1,3 @@
+root:
+ :id: 1
+ :name: The Root
diff --git a/activerecord/test/migrations/10_urban/9_add_expressions.rb b/activerecord/test/migrations/10_urban/9_add_expressions.rb
index 79a342e574..e908c9eabc 100644
--- a/activerecord/test/migrations/10_urban/9_add_expressions.rb
+++ b/activerecord/test/migrations/10_urban/9_add_expressions.rb
@@ -1,4 +1,4 @@
-class AddExpressions < ActiveRecord::Migration
+class AddExpressions < ActiveRecord::Migration::Current
def self.up
create_table("expressions") do |t|
t.column :expression, :string
diff --git a/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb b/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb
index 0aed7cbd84..549647de86 100644
--- a/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb
+++ b/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb
@@ -1,4 +1,4 @@
-class GiveMeBigNumbers < ActiveRecord::Migration
+class GiveMeBigNumbers < ActiveRecord::Migration::Current
def self.up
create_table :big_numbers do |table|
table.column :bank_balance, :decimal, :precision => 10, :scale => 2
diff --git a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
index c066c068c2..53b263bf55 100644
--- a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
+++ b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
@@ -1,6 +1,6 @@
# coding: ISO-8859-15
-class CurrenciesHaveSymbols < ActiveRecord::Migration
+class CurrenciesHaveSymbols < ActiveRecord::Migration::Current
def self.up
# We use ¤ for default currency symbol
add_column "currencies", "symbol", :string, :default => "¤"
diff --git a/activerecord/test/migrations/missing/1000_people_have_middle_names.rb b/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
index 4b83d61beb..e046944e31 100644
--- a/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
+++ b/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
@@ -1,4 +1,4 @@
-class PeopleHaveMiddleNames < ActiveRecord::Migration
+class PeopleHaveMiddleNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "middle_name", :string
end
diff --git a/activerecord/test/migrations/missing/1_people_have_last_names.rb b/activerecord/test/migrations/missing/1_people_have_last_names.rb
index 68209f3ce9..50fe2a9c8e 100644
--- a/activerecord/test/migrations/missing/1_people_have_last_names.rb
+++ b/activerecord/test/migrations/missing/1_people_have_last_names.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
+class PeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "last_name", :string
end
diff --git a/activerecord/test/migrations/missing/3_we_need_reminders.rb b/activerecord/test/migrations/missing/3_we_need_reminders.rb
index 25bb49cb32..d7c63ac892 100644
--- a/activerecord/test/migrations/missing/3_we_need_reminders.rb
+++ b/activerecord/test/migrations/missing/3_we_need_reminders.rb
@@ -1,4 +1,4 @@
-class WeNeedReminders < ActiveRecord::Migration
+class WeNeedReminders < ActiveRecord::Migration::Current
def self.up
create_table("reminders") do |t|
t.column :content, :text
diff --git a/activerecord/test/migrations/missing/4_innocent_jointable.rb b/activerecord/test/migrations/missing/4_innocent_jointable.rb
index 002a1bf2a6..20fe183777 100644
--- a/activerecord/test/migrations/missing/4_innocent_jointable.rb
+++ b/activerecord/test/migrations/missing/4_innocent_jointable.rb
@@ -1,4 +1,4 @@
-class InnocentJointable < ActiveRecord::Migration
+class InnocentJointable < ActiveRecord::Migration::Current
def self.up
create_table("people_reminders", :id => false) do |t|
t.column :reminder_id, :integer
diff --git a/activerecord/test/migrations/rename/1_we_need_things.rb b/activerecord/test/migrations/rename/1_we_need_things.rb
index f5484ac54f..9dce01acfd 100644
--- a/activerecord/test/migrations/rename/1_we_need_things.rb
+++ b/activerecord/test/migrations/rename/1_we_need_things.rb
@@ -1,4 +1,4 @@
-class WeNeedThings < ActiveRecord::Migration
+class WeNeedThings < ActiveRecord::Migration::Current
def self.up
create_table("things") do |t|
t.column :content, :text
diff --git a/activerecord/test/migrations/rename/2_rename_things.rb b/activerecord/test/migrations/rename/2_rename_things.rb
index 533a113ea8..cb8484e7dc 100644
--- a/activerecord/test/migrations/rename/2_rename_things.rb
+++ b/activerecord/test/migrations/rename/2_rename_things.rb
@@ -1,4 +1,4 @@
-class RenameThings < ActiveRecord::Migration
+class RenameThings < ActiveRecord::Migration::Current
def self.up
rename_table "things", "awesome_things"
end
diff --git a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
index 639841f663..607113b091 100644
--- a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
+class PeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
index b3d0b30640..d4cbddab50 100644
--- a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
+class PeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/migrations/to_copy2/1_create_articles.rb b/activerecord/test/migrations/to_copy2/1_create_articles.rb
index 0f048d90f7..2e9f5ec6bc 100644
--- a/activerecord/test/migrations/to_copy2/1_create_articles.rb
+++ b/activerecord/test/migrations/to_copy2/1_create_articles.rb
@@ -1,4 +1,4 @@
-class CreateArticles < ActiveRecord::Migration
+class CreateArticles < ActiveRecord::Migration::Current
def self.up
end
diff --git a/activerecord/test/migrations/to_copy2/2_create_comments.rb b/activerecord/test/migrations/to_copy2/2_create_comments.rb
index 0f048d90f7..2e9f5ec6bc 100644
--- a/activerecord/test/migrations/to_copy2/2_create_comments.rb
+++ b/activerecord/test/migrations/to_copy2/2_create_comments.rb
@@ -1,4 +1,4 @@
-class CreateArticles < ActiveRecord::Migration
+class CreateArticles < ActiveRecord::Migration::Current
def self.up
end
diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
index e438cf5999..8f81805fe1 100644
--- a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
+class PeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :string
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
index 639841f663..607113b091 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
+class PeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
index b3d0b30640..d4cbddab50 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
+class PeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps2/20090101010101_create_articles.rb b/activerecord/test/migrations/to_copy_with_timestamps2/20090101010101_create_articles.rb
index 0f048d90f7..2e9f5ec6bc 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps2/20090101010101_create_articles.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps2/20090101010101_create_articles.rb
@@ -1,4 +1,4 @@
-class CreateArticles < ActiveRecord::Migration
+class CreateArticles < ActiveRecord::Migration::Current
def self.up
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps2/20090101010202_create_comments.rb b/activerecord/test/migrations/to_copy_with_timestamps2/20090101010202_create_comments.rb
index 2b048edbb5..d361847d4b 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps2/20090101010202_create_comments.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps2/20090101010202_create_comments.rb
@@ -1,4 +1,4 @@
-class CreateComments < ActiveRecord::Migration
+class CreateComments < ActiveRecord::Migration::Current
def self.up
end
diff --git a/activerecord/test/migrations/valid/1_valid_people_have_last_names.rb b/activerecord/test/migrations/valid/1_valid_people_have_last_names.rb
index 06cb911117..c450211d8c 100644
--- a/activerecord/test/migrations/valid/1_valid_people_have_last_names.rb
+++ b/activerecord/test/migrations/valid/1_valid_people_have_last_names.rb
@@ -1,4 +1,4 @@
-class ValidPeopleHaveLastNames < ActiveRecord::Migration
+class ValidPeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "last_name", :string
end
diff --git a/activerecord/test/migrations/valid/2_we_need_reminders.rb b/activerecord/test/migrations/valid/2_we_need_reminders.rb
index 25bb49cb32..d7c63ac892 100644
--- a/activerecord/test/migrations/valid/2_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid/2_we_need_reminders.rb
@@ -1,4 +1,4 @@
-class WeNeedReminders < ActiveRecord::Migration
+class WeNeedReminders < ActiveRecord::Migration::Current
def self.up
create_table("reminders") do |t|
t.column :content, :text
diff --git a/activerecord/test/migrations/valid/3_innocent_jointable.rb b/activerecord/test/migrations/valid/3_innocent_jointable.rb
index 002a1bf2a6..20fe183777 100644
--- a/activerecord/test/migrations/valid/3_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid/3_innocent_jointable.rb
@@ -1,4 +1,4 @@
-class InnocentJointable < ActiveRecord::Migration
+class InnocentJointable < ActiveRecord::Migration::Current
def self.up
create_table("people_reminders", :id => false) do |t|
t.column :reminder_id, :integer
diff --git a/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb b/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb
index 06cb911117..c450211d8c 100644
--- a/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb
@@ -1,4 +1,4 @@
-class ValidPeopleHaveLastNames < ActiveRecord::Migration
+class ValidPeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "last_name", :string
end
diff --git a/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
index 25bb49cb32..d7c63ac892 100644
--- a/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
@@ -1,4 +1,4 @@
-class WeNeedReminders < ActiveRecord::Migration
+class WeNeedReminders < ActiveRecord::Migration::Current
def self.up
create_table("reminders") do |t|
t.column :content, :text
diff --git a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
index 002a1bf2a6..20fe183777 100644
--- a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
@@ -1,4 +1,4 @@
-class InnocentJointable < ActiveRecord::Migration
+class InnocentJointable < ActiveRecord::Migration::Current
def self.up
create_table("people_reminders", :id => false) do |t|
t.column :reminder_id, :integer
diff --git a/activerecord/test/migrations/valid_with_timestamps/20100101010101_valid_with_timestamps_people_have_last_names.rb b/activerecord/test/migrations/valid_with_timestamps/20100101010101_valid_with_timestamps_people_have_last_names.rb
index 1da99ceaba..9fd27593f0 100644
--- a/activerecord/test/migrations/valid_with_timestamps/20100101010101_valid_with_timestamps_people_have_last_names.rb
+++ b/activerecord/test/migrations/valid_with_timestamps/20100101010101_valid_with_timestamps_people_have_last_names.rb
@@ -1,4 +1,4 @@
-class ValidWithTimestampsPeopleHaveLastNames < ActiveRecord::Migration
+class ValidWithTimestampsPeopleHaveLastNames < ActiveRecord::Migration::Current
def self.up
add_column "people", "last_name", :string
end
diff --git a/activerecord/test/migrations/valid_with_timestamps/20100201010101_valid_with_timestamps_we_need_reminders.rb b/activerecord/test/migrations/valid_with_timestamps/20100201010101_valid_with_timestamps_we_need_reminders.rb
index cb6d735c8b..4a59921136 100644
--- a/activerecord/test/migrations/valid_with_timestamps/20100201010101_valid_with_timestamps_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid_with_timestamps/20100201010101_valid_with_timestamps_we_need_reminders.rb
@@ -1,4 +1,4 @@
-class ValidWithTimestampsWeNeedReminders < ActiveRecord::Migration
+class ValidWithTimestampsWeNeedReminders < ActiveRecord::Migration::Current
def self.up
create_table("reminders") do |t|
t.column :content, :text
diff --git a/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb b/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb
index 4bd4b4714d..bf934576c9 100644
--- a/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb
@@ -1,4 +1,4 @@
-class ValidWithTimestampsInnocentJointable < ActiveRecord::Migration
+class ValidWithTimestampsInnocentJointable < ActiveRecord::Migration::Current
def self.up
create_table("people_reminders", :id => false) do |t|
t.column :reminder_id, :integer
diff --git a/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb b/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb
index 9d46485a31..6f314c881c 100644
--- a/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb
+++ b/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb
@@ -1,4 +1,4 @@
-class MigrationVersionCheck < ActiveRecord::Migration
+class MigrationVersionCheck < ActiveRecord::Migration::Current
def self.up
raise "incorrect migration version" unless version == 20131219224947
end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index c1e491e5c5..dc0296305a 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -50,3 +50,9 @@ class FailedBulb < Bulb
throw(:abort)
end
end
+
+class TrickyBulb < Bulb
+ after_create do |record|
+ record.car.bulbs.to_a
+ end
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 23cebe2602..bf3079a1df 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -263,3 +263,10 @@ end
class SerializedPost < ActiveRecord::Base
serialize :title
end
+
+class ConditionalStiPost < Post
+ default_scope { where(title: 'Untitled') }
+end
+
+class SubConditionalStiPost < ConditionalStiPost
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 92e0b197a7..701e6f45b3 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -1,18 +1,25 @@
ActiveRecord::Schema.define do
+
+ if ActiveRecord::Base.connection.version >= '5.6.0'
+ create_table :datetime_defaults, force: true do |t|
+ t.datetime :modified_datetime, default: -> { 'CURRENT_TIMESTAMP' }
+ end
+ end
+
create_table :binary_fields, force: true do |t|
t.binary :var_binary, limit: 255
t.binary :var_binary_large, limit: 4095
- t.blob :tiny_blob, limit: 255
- t.binary :normal_blob, limit: 65535
- t.binary :medium_blob, limit: 16777215
- t.binary :long_blob, limit: 2147483647
- t.text :tiny_text, limit: 255
- t.text :normal_text, limit: 65535
- t.text :medium_text, limit: 16777215
- t.text :long_text, limit: 2147483647
- end
+ t.tinyblob :tiny_blob
+ t.blob :normal_blob
+ t.mediumblob :medium_blob
+ t.longblob :long_blob
+ t.tinytext :tiny_text
+ t.text :normal_text
+ t.mediumtext :medium_text
+ t.longtext :long_text
- add_index :binary_fields, :var_binary
+ t.index :var_binary
+ end
create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
t.string :awesome
@@ -55,7 +62,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('text','blob','tiny','medium','long')
+ enum_column ENUM('text','blob','tiny','medium','long','unsigned')
)
SQL
end
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
deleted file mode 100644
index 553cb56103..0000000000
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-ActiveRecord::Schema.define do
- create_table :binary_fields, force: true do |t|
- t.binary :var_binary, limit: 255
- t.binary :var_binary_large, limit: 4095
- t.blob :tiny_blob, limit: 255
- t.binary :normal_blob, limit: 65535
- t.binary :medium_blob, limit: 16777215
- t.binary :long_blob, limit: 2147483647
- t.text :tiny_text, limit: 255
- t.text :normal_text, limit: 65535
- t.text :medium_text, limit: 16777215
- t.text :long_text, limit: 2147483647
- end
-
- add_index :binary_fields, :var_binary
-
- create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
- t.string :awesome
- t.string :pizza
- t.string :snacks
- end
-
- add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
- add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
- add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
-
- create_table :collation_tests, id: false, force: true do |t|
- t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
- t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
- end
-
- ActiveRecord::Base.connection.execute <<-SQL
-DROP PROCEDURE IF EXISTS ten;
-SQL
-
- ActiveRecord::Base.connection.execute <<-SQL
-CREATE PROCEDURE ten() SQL SECURITY INVOKER
-BEGIN
- select 10;
-END
-SQL
-
- ActiveRecord::Base.connection.execute <<-SQL
-DROP PROCEDURE IF EXISTS topics;
-SQL
-
- ActiveRecord::Base.connection.execute <<-SQL
-CREATE PROCEDURE topics(IN num INT) SQL SECURITY INVOKER
-BEGIN
- select * from topics limit num;
-END
-SQL
-
- ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
-
- ActiveRecord::Base.connection.execute <<-SQL
-CREATE TABLE enum_tests (
- enum_column ENUM('text','blob','tiny','medium','long')
-)
-SQL
-
-end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index df0362573b..3a5d73a0ed 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -11,7 +11,23 @@ ActiveRecord::Schema.define do
t.uuid :uuid_parent_id
end
- %w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
+ create_table :defaults, force: true do |t|
+ t.date :modified_date, default: -> { 'CURRENT_DATE' }
+ t.date :modified_date_function, default: -> { 'now()' }
+ t.date :fixed_date, default: '2004-01-01'
+ t.datetime :modified_time, default: -> { 'CURRENT_TIMESTAMP' }
+ t.datetime :modified_time_function, default: -> { 'now()' }
+ t.datetime :fixed_time, default: '2004-01-01 00:00:00.000000-00'
+ t.column :char1, 'char(1)', default: 'Y'
+ t.string :char2, limit: 50, default: 'a varchar field'
+ t.text :char3, default: 'a text field'
+ t.bigint :bigint_default, default: -> { '0::bigint' }
+ t.text :multiline_default, default: '--- []
+
+'
+ end
+
+ %w(postgresql_times postgresql_oids postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
drop_table table_name, if_exists: true
end
@@ -28,25 +44,6 @@ ActiveRecord::Schema.define do
end
execute <<_SQL
- CREATE TABLE defaults (
- id serial primary key,
- modified_date date default CURRENT_DATE,
- modified_date_function date default now(),
- fixed_date date default '2004-01-01',
- modified_time timestamp default CURRENT_TIMESTAMP,
- modified_time_function timestamp default now(),
- fixed_time timestamp default '2004-01-01 00:00:00.000000-00',
- char1 char(1) default 'Y',
- char2 character varying(50) default 'a varchar field',
- char3 text default 'a text field',
- bigint_default bigint default 0::bigint,
- multiline_default text DEFAULT '--- []
-
-'::text
-);
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_times (
id SERIAL PRIMARY KEY,
time_interval INTERVAL,
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 99098017d7..025184f63a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -356,7 +356,7 @@ ActiveRecord::Schema.define do
t.column :key, :string
end
- create_table :guitar, force: true do |t|
+ create_table :guitars, force: true do |t|
t.string :color
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index d946fc1bbd..21c79949ca 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,19 +1,104 @@
-* `ActiveSupport::Cache::Store#namespaced_key`,
- `ActiveSupport::Cache::MemCachedStore#escape_key`, and
- `ActiveSupport::Cache::FileStore#key_file_path`
+* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`.
+
+ *David Cornu*
+
+* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter`
+ payload when an exception is raised.
+
+ Adds new key/value pair to payload when an exception is raised:
+ e.g. `:exception_object => #<RuntimeError: FAIL>`.
+
+ *Ryan T. Hosford*
+
+* Support extended grapheme clusters and UAX 29.
+
+ *Adam Roben*
+
+* Add petabyte and exabyte numeric conversion.
+
+ *Akshay Vishnoi*
+
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
+* Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread.
+ This makes it easy to declare per-thread globals that are encapsulated. Note: This is a sharp edge. A wild proliferation
+ of globals is A Bad Thing. But like other sharp tools, when it's right, it's right.
+
+ Here's an example of a simple event tracking system where the object being tracked needs not pass a creator that it
+ doesn't need itself along:
+
+ module Current
+ thread_mattr_accessor :account
+ thread_mattr_accessor :user
+
+ def self.reset() self.account = self.user = nil end
+ end
+
+ class ApplicationController < ActionController::Base
+ before_action :set_current
+ after_action { Current.reset }
+
+ private
+ def set_current
+ Current.account = Account.find(params[:account_id])
+ Current.user = Current.account.users.find(params[:user_id])
+ end
+ end
+
+ class MessagesController < ApplicationController
+ def create
+ @message = Message.create!(message_params)
+ end
+ end
+
+ class Message < ApplicationRecord
+ has_many :events
+ after_create :track_created
+
+ private
+ def track_created
+ events.create! origin: self, action: :create
+ end
+ end
+
+ class Event < ApplicationRecord
+ belongs_to :creator, class_name: 'User'
+ before_validation { self.creator ||= Current.user }
+ end
+
+ *DHH*
+
+
+* Deprecated `Module#qualified_const_` in favour of the builtin Module#const_
+ methods.
+
+ *Genadi Samokovarov*
+
+* Deprecate passing string to define callback.
+
+ *Yuichiro Kaneko*
+
+* `ActiveSupport::Cache::Store#namespaced_key`,
+ `ActiveSupport::Cache::MemCachedStore#escape_key`, and
+ `ActiveSupport::Cache::FileStore#key_file_path`
are deprecated and replaced with `normalize_key` that now calls `super`.
-
+
`ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`.
-
+
*Michael Grosser*
-* Implements an evented file system monitor to asynchronously detect changes
- in the application source code, routes, locales, etc.
+* Implements an evented file watcher to asynchronously detect changes in the
+ application source code, routes, locales, etc.
+
+ This watcher is disabled by default, applications my enable it in the configuration:
+
+ # config/environments/development.rb
+ config.file_watcher = ActiveSupport::EventedFileUpdateChecker
- To opt-in load the [listen](https://github.com/guard/listen) gem in `Gemfile`:
+ This feature depends on the [listen](https://github.com/guard/listen) gem:
group :development do
- gem 'listen', '~> 3.0.4'
+ gem 'listen', '~> 3.0.5'
end
*Puneet Agarwal* and *Xavier Noria*
@@ -54,7 +139,7 @@
*Konstantinos Rousis*
-* Handle invalid UTF-8 strings when HTML escaping
+* Handle invalid UTF-8 strings when HTML escaping.
Use `ActiveSupport::Multibyte::Unicode.tidy_bytes` to handle invalid UTF-8
strings in `ERB::Util.unwrapped_html_escape` and `ERB::Util.html_escape_once`.
@@ -95,7 +180,7 @@
* Short-circuit `blank?` on date and time values since they are never blank.
- Fixes #21657
+ Fixes #21657.
*Andrew White*
@@ -133,7 +218,7 @@
* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return
enumerator if called without block.
- Fixes #20095
+ Fixes #20095.
*Bernard Potocki*
@@ -147,11 +232,11 @@
*Simon Eskildsen*
-* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`
+* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`.
*Simon Eskildsen*
-* Fix a range of values for parameters of the Time#change
+* Fix a range of values for parameters of the Time#change.
*Nikolay Kondratyev*
@@ -163,7 +248,7 @@
*Kevin Deisz*
* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
- an `KeyError` if the value is `.blank?`
+ an `KeyError` if the value is `.blank?`.
Before:
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 7bffebb076..40235833ba 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2015 David Heinemeier Hansson
+Copyright (c) 2005-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index cd72f53821..14ce204303 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -10,7 +10,7 @@ outside of Rails.
The latest version of Active Support can be installed with RubyGems:
- % gem install activesupport
+ $ gem install activesupport
Source code can be downloaded as part of the Rails project on GitHub:
@@ -37,4 +37,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 71a6b78652..2193533588 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -50,16 +50,11 @@ module ActiveSupport
([0-9A-F]*); # simple lowercase mapping
([0-9A-F]*)$/ix # simple titlecase mapping
codepoint.code = $1.hex
- #codepoint.name = $2
- #codepoint.category = $3
codepoint.combining_class = Integer($4)
- #codepoint.bidi_class = $5
codepoint.decomp_type = $7
codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect(&:hex)
- #codepoint.bidi_mirrored = ($13=='Y') ? true : false
codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
- #codepoint.titlecase_mapping = ($18=='') ? nil : $18.hex
@ucd.codepoints[codepoint.code] = codepoint
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 3a2a7d28cb..94fe893149 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2015 David Heinemeier Hansson
+# Copyright (c) 2005-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -34,7 +34,7 @@ module ActiveSupport
autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
- autoload :FileEventedUpdateChecker
+ autoload :EventedFileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 5011014e96..610105f41c 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -255,10 +255,11 @@ module ActiveSupport
# end
# end
#
- # # val_1 => "new value 1"
- # # val_2 => "original value"
- # # sleep 10 # First thread extend the life of cache by another 10 seconds
- # # cache.fetch('foo') => "new value 1"
+ # cache.fetch('foo') # => "original value"
+ # sleep 10 # First thread extended the life of cache by another 10 seconds
+ # cache.fetch('foo') # => "new value 1"
+ # val_1 # => "new value 1"
+ # val_2 # => "original value"
#
# Other options will be handled by the specific cache store implementation.
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache
@@ -278,18 +279,18 @@ module ActiveSupport
options = merged_options(options)
key = normalize_key(name, options)
+ entry = nil
instrument(:read, name, options) do |payload|
cached_entry = read_entry(key, options) unless options[:force]
- payload[:super_operation] = :fetch if payload
entry = handle_expired_entry(cached_entry, key, options)
+ payload[:super_operation] = :fetch if payload
+ payload[:hit] = !!entry if payload
+ end
- if entry
- payload[:hit] = true if payload
- get_entry_value(entry, name, options)
- else
- payload[:hit] = false if payload
- save_block_result_to_cache(name, options) { |_name| yield _name }
- end
+ if entry
+ get_entry_value(entry, name, options)
+ else
+ save_block_result_to_cache(name, options) { |_name| yield _name }
end
else
read(name, options)
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index dff2443bc8..99c55b1aa4 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -17,6 +17,7 @@ module ActiveSupport
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
EXCLUDED_DIRS = ['.', '..'].freeze
+ GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
def initialize(cache_path, options = nil)
super(options)
@@ -24,10 +25,10 @@ module ActiveSupport
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
+ root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
rescue Errno::ENOENT
end
@@ -154,7 +155,7 @@ module ActiveSupport
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if File.realpath(dir) == File.realpath(cache_path)
- if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty?
+ if exclude_from(dir, EXCLUDED_DIRS).empty?
Dir.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
@@ -193,6 +194,11 @@ module ActiveSupport
end
end
end
+
+ # Exclude entries from source directory
+ def exclude_from(source, excludes)
+ Dir.entries(source).reject { |f| excludes.include?(f) }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 4b0ad37586..174913365a 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -36,13 +36,13 @@ module ActiveSupport
end
def write_entry(key, entry, options) # :nodoc:
- retval = super
- if options[:raw] && local_cache && retval
+ if options[:raw] && local_cache
raw_entry = Entry.new(entry.value.to_s)
raw_entry.expires_at = entry.expires_at
- local_cache.write_entry(key, raw_entry, options)
+ super(key, raw_entry, options)
+ else
+ super
end
- retval
end
end
@@ -115,11 +115,10 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
instrument(:increment, name, :amount => amount) do
- @data.incr(normalize_key(name, options), amount)
+ rescue_error_with nil do
+ @data.incr(normalize_key(name, options), amount)
+ end
end
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
end
# Decrement a cached value. This method uses the memcached decr atomic
@@ -129,20 +128,16 @@ module ActiveSupport
def decrement(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
instrument(:decrement, name, :amount => amount) do
- @data.decr(normalize_key(name, options), amount)
+ rescue_error_with nil do
+ @data.decr(normalize_key(name, options), amount)
+ end
end
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
end
# Clear the entire cache on all memcached servers. This method should
# be used with care when shared cache is being used.
def clear(options = nil)
- @data.flush_all
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
+ rescue_error_with(nil) { @data.flush_all }
end
# Get the statistics from the memcached servers.
@@ -153,10 +148,7 @@ module ActiveSupport
protected
# Read an entry from the cache.
def read_entry(key, options) # :nodoc:
- deserialize_entry(@data.get(key, options))
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
+ rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
end
# Write an entry to the cache.
@@ -168,18 +160,14 @@ module ActiveSupport
# Set the memcache expire a few minutes in the future to support race condition ttls on read
expires_in += 5.minutes
end
- @data.send(method, key, value, expires_in, options)
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- false
+ rescue_error_with false do
+ @data.send(method, key, value, expires_in, options)
+ end
end
# Delete an entry from the cache.
def delete_entry(key, options) # :nodoc:
- @data.delete(key)
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- false
+ rescue_error_with(false) { @data.delete(key) }
end
private
@@ -207,10 +195,15 @@ module ActiveSupport
if raw_value
entry = Marshal.load(raw_value) rescue raw_value
entry.is_a?(Entry) ? entry : Entry.new(entry)
- else
- nil
end
end
+
+ def rescue_error_with(fallback)
+ yield
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
+ fallback
+ end
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index d43fde03a9..bf560ec1fa 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -295,6 +295,13 @@ module ActiveSupport
class Callback #:nodoc:#
def self.build(chain, filter, kind, options)
+ if filter.is_a?(String)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing string to define callback is deprecated and will be removed
+ in Rails 5.1 without replacement.
+ MSG
+ end
+
new chain.name, filter, kind, options, chain.config
end
@@ -575,7 +582,7 @@ module ActiveSupport
# set_callback :save, :before_meth
#
# The callback can be specified as a symbol naming an instance method; as a
- # proc, lambda, or block; as a string to be instance evaluated; or as an
+ # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
# object that responds to a certain method determined by the <tt>:scope</tt>
# argument to +define_callbacks+.
#
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index b4efff8b24..ef038331c2 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/concerning'
require 'active_support/core_ext/module/delegation'
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 124f90dc0f..76825862d7 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -49,7 +49,7 @@ class Module
# include HairColors
# end
#
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
@@ -105,7 +105,7 @@ class Module
#
# Also, you can pass a block to set up the attribute with a default value.
#
- # class HairColors
+ # module HairColors
# mattr_writer :hair_colors do
# [:brown, :black, :blonde, :red]
# end
@@ -150,8 +150,8 @@ class Module
# include HairColors
# end
#
- # Person.hair_colors = [:brown, :black, :blonde, :red]
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors = [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors # => [:brown, :black, :blonde, :red]
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
#
# If a subclass changes the value then that would also change the value for
@@ -161,8 +161,8 @@ class Module
# class Male < Person
# end
#
- # Male.hair_colors << :blue
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
+ # Male.new.hair_colors << :blue
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
new file mode 100644
index 0000000000..8a7e6776da
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -0,0 +1,141 @@
+require 'active_support/core_ext/array/extract_options'
+
+# Extends the module object with class/module and instance accessors for
+# class/module attributes, just like the native attr* accessors for instance
+# attributes, but does so on a per-thread basis.
+#
+# So the values are scoped within the Thread.current space under the class name
+# of the module.
+class Module
+ # Defines a per-thread class attribute and creates class and instance reader methods.
+ # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
+ #
+ # module Current
+ # thread_mattr_reader :user
+ # end
+ #
+ # Current.user # => nil
+ # Thread.current[:attr_Current_user] = "DHH"
+ # Current.user # => "DHH"
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # module Foo
+ # thread_mattr_reader :"1_Badname"
+ # end
+ # # => NameError: invalid attribute name: 1_Badname
+ #
+ # If you want to opt out the creation on the instance reader method, pass
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # class Current
+ # thread_mattr_reader :user, instance_reader: false
+ # end
+ #
+ # Current.new.user # => NoMethodError
+ def thread_mattr_reader(*syms)
+ options = syms.extract_options!
+
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}
+ Thread.current[:"attr_#{name}_#{sym}"]
+ end
+ EOS
+
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}
+ Thread.current[:"attr_#{self.class.name}_#{sym}"]
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_reader :thread_mattr_reader
+
+ # Defines a per-thread class attribute and creates a class and instance writer methods to
+ # allow assignment to the attribute.
+ #
+ # module Current
+ # thread_mattr_writer :user
+ # end
+ #
+ # Current.user = "DHH"
+ # Thread.current[:attr_Current_user] # => "DHH"
+ #
+ # If you want to opt out the instance writer method, pass
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # class Current
+ # thread_mattr_writer :user, instance_writer: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ def thread_mattr_writer(*syms)
+ options = syms.extract_options!
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}=(obj)
+ Thread.current[:"attr_#{name}_#{sym}"] = obj
+ end
+ EOS
+
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}=(obj)
+ Thread.current[:"attr_#{self.class.name}_#{sym}"] = obj
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_writer :thread_mattr_writer
+
+ # Defines both class and instance accessors for class attributes.
+ #
+ # class Account
+ # thread_mattr_accessor :user
+ # end
+ #
+ # Account.user = "DHH"
+ # Account.user # => "DHH"
+ # Account.new.user # => "DHH"
+ #
+ # If a subclass changes the value, the parent class' value is not changed.
+ # Similarly, if the parent class changes the value, the value of subclasses
+ # is not changed.
+ #
+ # class Customer < Account
+ # end
+ #
+ # Customer.user = "Rafael"
+ # Customer.user # => "Rafael"
+ # Account.user # => "DHH"
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # class Current
+ # thread_mattr_accessor :user, instance_writer: false, instance_reader: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class Current
+ # mattr_accessor :user, instance_accessor: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ def thread_mattr_accessor(*syms, &blk)
+ thread_mattr_reader(*syms, &blk)
+ thread_mattr_writer(*syms, &blk)
+ end
+ alias :thread_cattr_accessor :thread_mattr_accessor
+end
diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
index 65525013db..3ea39d4267 100644
--- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb
+++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
@@ -3,13 +3,16 @@ require 'active_support/core_ext/string/inflections'
#--
# Allows code reuse in the methods below without polluting Module.
#++
-module QualifiedConstUtils
- def self.raise_if_absolute(path)
- raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
- end
- def self.names(path)
- path.split('::')
+module ActiveSupport
+ module QualifiedConstUtils
+ def self.raise_if_absolute(path)
+ raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
+ end
+
+ def self.names(path)
+ path.split('::')
+ end
end
end
@@ -24,9 +27,14 @@ end
#++
class Module
def qualified_const_defined?(path, search_parents=true)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_defined? is deprecated in favour of the builtin
+ Module#const_defined? and will be removed in Rails 5.1.
+ MESSAGE
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
+
+ ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
return unless mod.const_defined?(name, search_parents)
mod.const_get(name)
end
@@ -34,19 +42,29 @@ class Module
end
def qualified_const_get(path)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_get is deprecated in favour of the builtin
+ Module#const_get and will be removed in Rails 5.1.
+ MESSAGE
+
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
mod.const_get(name)
end
end
def qualified_const_set(path, value)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_set is deprecated in favour of the builtin
+ Module#const_set and will be removed in Rails 5.1.
+ MESSAGE
+
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
const_name = path.demodulize
mod_name = path.deconstantize
- mod = mod_name.empty? ? self : qualified_const_get(mod_name)
+ mod = mod_name.empty? ? self : const_get(mod_name)
mod.const_set(const_name, value)
end
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 9a3651f29a..9d832897ed 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/number_helper'
+require 'active_support/core_ext/module/deprecation'
module ActiveSupport::NumericWithFormat
@@ -75,6 +76,8 @@ module ActiveSupport::NumericWithFormat
# 1234567.to_s(:human_size) # => 1.18 MB
# 1234567890.to_s(:human_size) # => 1.15 GB
# 1234567890123.to_s(:human_size) # => 1.12 TB
+ # 1234567890123456.to_s(:human_size) # => 1.1 PB
+ # 1234567890123456789.to_s(:human_size) # => 1.07 EB
# 1234567.to_s(:human_size, precision: 2) # => 1.2 MB
# 483989.to_s(:human_size, precision: 2) # => 470 KB
# 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB
@@ -117,7 +120,11 @@ module ActiveSupport::NumericWithFormat
when :human_size
return ActiveSupport::NumberHelper.number_to_human_size(self, options)
else
- super
+ if is_a?(Float) || format.is_a?(Symbol)
+ super()
+ else
+ super
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 83eced50bf..965436c23a 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -1,34 +1,31 @@
-class Range
+module ActiveSupport::RangeWithFormat
RANGE_FORMATS = {
:db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
}
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
#
- # This method is aliased to <tt>to_s</tt>.
- #
# range = (1..100) # => 1..100
#
- # range.to_formatted_s # => "1..100"
# range.to_s # => "1..100"
- #
- # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'"
# range.to_s(:db) # => "BETWEEN '1' AND '100'"
#
- # == Adding your own range formats to to_formatted_s
+ # == Adding your own range formats to to_s
# You can add your own formats to the Range::RANGE_FORMATS hash.
# Use the format name as the hash key and a Proc instance.
#
# # config/initializers/range_formats.rb
# Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
- def to_formatted_s(format = :default)
+ def to_s(format = :default)
if formatter = RANGE_FORMATS[format]
formatter.call(first, last)
else
- to_default_s
+ super()
end
end
alias_method :to_default_s, :to_s
- alias_method :to_s, :to_formatted_s
+ alias_method :to_formatted_s, :to_s
end
+
+Range.prepend(ActiveSupport::RangeWithFormat)
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 510fa48189..04ed8e7cd8 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -5,7 +5,6 @@ class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
- HTML_ESCAPE_REGEXP = /[&"'><]/
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
@@ -37,7 +36,7 @@ class ERB
if s.html_safe?
s
else
- ActiveSupport::Multibyte::Unicode.tidy_bytes(s).gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
end
end
module_function :unwrapped_html_escape
@@ -243,8 +242,7 @@ module ActiveSupport #:nodoc:
private
def html_escape_interpolated_argument(arg)
- (!html_safe? || arg.html_safe?) ? arg :
- arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
+ (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 675db8a36b..768c9a1b2c 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -162,7 +162,6 @@ class Time
# Returns a new Time representing the start of the day (0:00)
def beginning_of_day
- #(self - seconds_since_midnight).change(usec: 0)
change(:hour => 0)
end
alias :midnight :beginning_of_day
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 46e9996d59..24545d766c 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -32,7 +32,7 @@ module ActiveSupport
# and the second is a library name
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = '5.0', gem_name = 'Rails')
+ def initialize(deprecation_horizon = '5.1', gem_name = 'Rails')
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 32fe8025fe..f5ea6669ce 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -21,15 +21,15 @@ module ActiveSupport
# # => [:aaa, :bbb, :ccc]
#
# Fred.aaa
- # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10)
+ # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
# # => nil
#
# Fred.bbb
- # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.0 (use zzz instead). (called from irb_binding at (irb):11)
+ # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
# # => nil
#
# Fred.ccc
- # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12)
+ # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
# # => nil
#
# Passing in a custom deprecator:
diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index bb0f26f874..315be85fb3 100644
--- a/activesupport/lib/active_support/file_evented_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -1,10 +1,9 @@
-require 'listen'
require 'set'
require 'pathname'
require 'concurrent/atomic/atomic_boolean'
module ActiveSupport
- class FileEventedUpdateChecker #:nodoc: all
+ class EventedFileUpdateChecker #:nodoc: all
def initialize(files, dirs = {}, &block)
@ph = PathHelper.new
@files = files.map { |f| @ph.xpath(f) }.to_set
@@ -19,6 +18,10 @@ module ActiveSupport
@lcsp = @ph.longest_common_subpath(@dirs.keys)
if (dtw = directories_to_watch).any?
+ # Loading listen triggers warnings. These are originated by a legit
+ # usage of attr_* macros for private attributes, but adds a lot of noise
+ # to our test suite. Thus, we lazy load it and disable warnings locally.
+ silence_warnings { require 'listen' }
Listen.to(*dtw, &method(:changed)).start
end
end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index ece68bbcb6..7790a9b2c0 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveSupport
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4ff35a45a1..b878f31e75 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -68,12 +68,10 @@ module ActiveSupport
end
end
- def default(key = nil)
- if key.is_a?(Symbol) && include?(key = key.to_s)
- self[key]
- else
- super
- end
+ def default(*args)
+ key = args.first
+ args[0] = key.to_s if key.is_a?(Symbol)
+ super(*args)
end
def self.new_from_hash_copying_default(hash)
@@ -159,6 +157,20 @@ module ActiveSupport
alias_method :has_key?, :key?
alias_method :member?, :key?
+
+ # Same as <tt>Hash#[]</tt> where the key passed as argument can be
+ # either a string or a symbol:
+ #
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
+ # counters[:foo] = 1
+ #
+ # counters['foo'] # => 1
+ # counters[:foo] # => 1
+ # counters[:zoo] # => nil
+ def [](key)
+ super(convert_key(key))
+ end
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
# either a string or a symbol:
#
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 595b0339cc..f741c0bfb8 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -173,7 +173,7 @@ module ActiveSupport
#
# Singular names are not handled correctly:
#
- # classify('calculus') # => "Calculu"
+ # classify('calculus') # => "Calculus"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index a4563ace8f..c64b7598ee 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -106,6 +106,8 @@ en:
mb: "MB"
gb: "GB"
tb: "TB"
+ pb: "PB"
+ eb: "EB"
# Used in NumberHelper.number_to_human()
decimal_units:
format: "%n %u"
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 33fccdcf95..7626b28108 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
require 'logger'
@@ -6,6 +5,17 @@ module ActiveSupport
class Logger < ::Logger
include LoggerSilence
+ # Returns true if the logger destination matches one of the sources
+ #
+ # logger = Logger.new(STDOUT)
+ # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
+ # # => true
+ def self.logger_outputs_to?(logger, *sources)
+ logdev = logger.instance_variable_get("@logdev")
+ logger_source = logdev.dev if logdev.respond_to?(:dev)
+ sources.any? { |source| source == logger_source }
+ end
+
# Broadcasts logs to multiple loggers.
def self.broadcast(logger) # :nodoc:
Module.new do
@@ -44,6 +54,20 @@ module ActiveSupport
def initialize(*args)
super
@formatter = SimpleFormatter.new
+ after_initialize if respond_to? :after_initialize
+ end
+
+ def add(severity, message = nil, progname = nil, &block)
+ return true if @logdev.nil? || (severity || UNKNOWN) < level
+ super
+ end
+
+ Logger::Severity.constants.each do |severity|
+ class_eval(<<-EOT, __FILE__, __LINE__ + 1)
+ def #{severity.downcase}? # def debug?
+ Logger::#{severity} >= level # DEBUG >= level
+ end # end
+ EOT
end
# Simple formatter which only displays the message.
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index a8efdef944..125d81d973 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,21 +1,42 @@
require 'active_support/concern'
+require 'active_support/core_ext/module/attribute_accessors'
+require 'concurrent'
module LoggerSilence
extend ActiveSupport::Concern
-
+
included do
cattr_accessor :silencer
+ attr_reader :local_levels
self.silencer = true
end
+ def after_initialize
+ @local_levels = Concurrent::Map.new(:initial_capacity => 2)
+ end
+
+ def local_log_id
+ Thread.current.__id__
+ end
+
+ def level
+ local_levels[local_log_id] || super
+ end
+
# Silences the logger for the duration of the block.
def silence(temporary_level = Logger::ERROR)
if silencer
begin
- old_logger_level, self.level = level, temporary_level
+ old_local_level = local_levels[local_log_id]
+ local_levels[local_log_id] = temporary_level
+
yield self
ensure
- self.level = old_logger_level
+ if old_local_level
+ local_levels[local_log_id] = old_local_level
+ else
+ local_levels.delete(local_log_id)
+ end
end
else
yield self
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 64c5232cf4..854029bf83 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -15,7 +15,7 @@ module ActiveSupport
# In the authentication filter:
#
# id, time = @verifier.verify(cookies[:remember_me])
- # if time < Time.now
+ # if Time.now < time
# self.current_user = User.find(id)
# end
#
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 586002b03b..72b20fff06 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -87,19 +87,44 @@ module ActiveSupport
pos += 1
previous = codepoints[pos-1]
current = codepoints[pos]
- if (
- # CR X LF
- ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or
- # L X (L|V|LV|LVT)
- ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
- # (LV|V) X (V|T)
- ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
- # (LVT|T) X (T)
- ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or
- # X Extend
- (database.boundary[:extend] === current)
- )
- else
+
+ should_break =
+ # GB3. CR X LF
+ if previous == database.boundary[:cr] and current == database.boundary[:lf]
+ false
+ # GB4. (Control|CR|LF) ÷
+ elsif previous and in_char_class?(previous, [:control,:cr,:lf])
+ true
+ # GB5. ÷ (Control|CR|LF)
+ elsif in_char_class?(current, [:control,:cr,:lf])
+ true
+ # GB6. L X (L|V|LV|LVT)
+ elsif database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt])
+ false
+ # GB7. (LV|V) X (V|T)
+ elsif in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t])
+ false
+ # GB8. (LVT|T) X (T)
+ elsif in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current
+ false
+ # GB8a. Regional_Indicator X Regional_Indicator
+ elsif database.boundary[:regional_indicator] === previous and database.boundary[:regional_indicator] === current
+ false
+ # GB9. X Extend
+ elsif database.boundary[:extend] === current
+ false
+ # GB9a. X SpacingMark
+ elsif database.boundary[:spacingmark] === current
+ false
+ # GB9b. Prepend X
+ elsif database.boundary[:prepend] === previous
+ false
+ # GB10. Any ÷ Any
+ else
+ true
+ end
+
+ if should_break
unpacked << codepoints[marker..pos-1]
marker = pos
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 7798c7ec60..c53f9c1039 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -42,8 +42,8 @@ module ActiveSupport
listeners_for(name).each { |s| s.start(name, id, payload) }
end
- def finish(name, id, payload)
- listeners_for(name).each { |s| s.finish(name, id, payload) }
+ def finish(name, id, payload, listeners = listeners_for(name))
+ listeners.each { |s| s.finish(name, id, payload) }
end
def publish(name, *args)
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 075ddc2382..91f94cb2d7 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -15,14 +15,16 @@ module ActiveSupport
# and publish it. Notice that events get sent even if an error occurs
# in the passed-in block.
def instrument(name, payload={})
- start name, payload
+ # some of the listeners might have state
+ listeners_state = start name, payload
begin
yield payload
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
+ payload[:exception_object] = e
raise e
ensure
- finish name, payload
+ finish_with_state listeners_state, name, payload
end
end
@@ -36,6 +38,10 @@ module ActiveSupport
@notifier.finish name, @id, payload
end
+ def finish_with_state(listeners_state, name, payload)
+ @notifier.finish name, @id, payload, listeners_state
+ end
+
private
def unique_id
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 248521e677..64d9e71f37 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -47,6 +47,14 @@ module ActiveSupport
# Formats a +number+ into a currency string (e.g., $13.65). You
# can customize the format in the +options+ hash.
#
+ # The currency unit and number formatting of the current locale will be used
+ # unless otherwise specified in the provided options. No currency conversion
+ # is performed. If the user is given a way to change their locale, they will
+ # also be able to change the relative value of the currency displayed with
+ # this helper. If your application will ever support multiple locales, you
+ # may want to specify a constant <tt>:locale</tt> option or consider
+ # using a library capable of currency conversion.
+ #
# ==== Options
#
# * <tt>:locale</tt> - Sets the locale to be used for formatting
@@ -235,6 +243,8 @@ module ActiveSupport
# number_to_human_size(1234567) # => 1.18 MB
# number_to_human_size(1234567890) # => 1.15 GB
# number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567890123456) # => 1.1 PB
+ # number_to_human_size(1234567890123456789) # => 1.07 EB
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index a4a8690bcd..a83b368b7f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -1,7 +1,7 @@
module ActiveSupport
module NumberHelper
class NumberToHumanSizeConverter < NumberConverter #:nodoc:
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
self.namespace = :human
self.validate_float = true
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 4680d5acb7..b1658f0f27 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -5,7 +5,7 @@ YAML.add_builtin_type("omap") do |type, val|
end
module ActiveSupport
- # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
+ # DEPRECATED: <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
# insertion order.
#
# oh = ActiveSupport::OrderedHash.new
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index a909a65bb6..88e2b12cc7 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -1,6 +1,9 @@
require 'active_support/core_ext/module/delegation'
module ActiveSupport
+ # NOTE: This approach has been deprecated for end-user code in favor of thread_mattr_accessor and friends.
+ # Please use that approach instead.
+ #
# This module is used to encapsulate access to thread local variables.
#
# Instead of polluting the thread locals namespace:
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index ae6f00b861..d9a668c0ea 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -9,7 +9,6 @@ require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
require 'active_support/testing/file_fixtures'
-require 'active_support/testing/composite_filter'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
@@ -39,15 +38,6 @@ module ActiveSupport
def test_order
ActiveSupport.test_order ||= :random
end
-
- def run(reporter, options = {})
- if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
- options[:filter] = \
- Testing::CompositeFilter.new(self, options[:filter], options[:patterns])
- end
-
- super
- end
end
alias_method :method_name, :name
diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb
deleted file mode 100644
index bde723e30b..0000000000
--- a/activesupport/lib/active_support/testing/composite_filter.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'method_source'
-
-module ActiveSupport
- module Testing
- class CompositeFilter # :nodoc:
- def initialize(runnable, filter, patterns)
- @runnable = runnable
- @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
- end
-
- def ===(method)
- @filters.any? { |filter| filter === method }
- end
-
- private
- def derive_regexp(filter)
- filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
- end
-
- def derive_line_filters(patterns)
- patterns.map do |file_and_line|
- file, line = file_and_line.split(':')
- Filter.new(@runnable, file, line) if file
- end
- end
-
- class Filter # :nodoc:
- def initialize(runnable, file, line)
- @runnable, @file = runnable, File.expand_path(file)
- @line = line.to_i if line
- end
-
- def ===(method)
- return unless @runnable.method_defined?(method)
-
- if @line
- test_file, test_range = definition_for(@runnable.instance_method(method))
- test_file == @file && test_range.include?(@line)
- else
- @runnable.instance_method(method).source_location.first == @file
- end
- end
-
- private
- def definition_for(method)
- file, start_line = method.source_location
- end_line = method.source.count("\n") + start_line - 1
-
- return file, start_line..end_line
- end
- end
- end
- end
-end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 7bef73136c..4a299429f3 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -493,28 +493,32 @@ module CacheStoreBehavior
def test_cache_hit_instrumentation
key = "test_key"
- subscribe_executed = false
- ActiveSupport::Notifications.subscribe "cache_read.active_support" do |name, start, finish, id, payload|
- subscribe_executed = true
- assert_equal :fetch, payload[:super_operation]
- assert payload[:hit]
+ @events = []
+ ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
end
assert @cache.write(key, "1", :raw => true)
assert @cache.fetch(key) {}
- assert subscribe_executed
+ assert_equal 1, @events.length
+ assert_equal 'cache_read.active_support', @events[0].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert @events[0].payload[:hit]
ensure
ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
end
def test_cache_miss_instrumentation
- subscribe_executed = false
- ActiveSupport::Notifications.subscribe "cache_read.active_support" do |name, start, finish, id, payload|
- subscribe_executed = true
- assert_equal :fetch, payload[:super_operation]
- assert_not payload[:hit]
+ @events = []
+ ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
end
assert_not @cache.fetch("bad_key") {}
- assert subscribe_executed
+ assert_equal 3, @events.length
+ assert_equal 'cache_read.active_support', @events[0].name
+ assert_equal 'cache_generate.active_support', @events[1].name
+ assert_equal 'cache_write.active_support', @events[2].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert_not @events[0].payload[:hit]
ensure
ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
end
@@ -776,10 +780,12 @@ class FileStoreTest < ActiveSupport::TestCase
include AutoloadingCacheBehavior
def test_clear
- filepath = File.join(cache_dir, ".gitkeep")
- FileUtils.touch(filepath)
+ gitkeep = File.join(cache_dir, ".gitkeep")
+ keep = File.join(cache_dir, ".keep")
+ FileUtils.touch([gitkeep, keep])
@cache.clear
- assert File.exist?(filepath)
+ assert File.exist?(gitkeep)
+ assert File.exist?(keep)
end
def test_clear_without_cache_dir
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 3b00ff87a0..a624473f46 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -59,7 +59,7 @@ module CallbacksTest
[:before_save, :after_save].each do |callback_method|
callback_method_sym = callback_method.to_sym
send(callback_method, callback_symbol(callback_method_sym))
- send(callback_method, callback_string(callback_method_sym))
+ ActiveSupport::Deprecation.silence { 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)
@@ -228,7 +228,7 @@ module CallbacksTest
set_callback :save, :before, :nope, :if => :no
set_callback :save, :before, :nope, :unless => :yes
set_callback :save, :after, :tweedle
- set_callback :save, :before, "tweedle_dee"
+ ActiveSupport::Deprecation.silence { set_callback :save, :before, "tweedle_dee" }
set_callback :save, :before, proc {|m| m.history << "yup" }
set_callback :save, :before, :nope, :if => proc { false }
set_callback :save, :before, :nope, :unless => proc { true }
@@ -1046,7 +1046,7 @@ module CallbacksTest
def test_add_eval
calls = []
- klass = build_class("bar")
+ klass = ActiveSupport::Deprecation.silence { build_class("bar") }
klass.class_eval { define_method(:bar) { calls << klass } }
klass.new.run
assert_equal 1, calls.length
@@ -1086,7 +1086,7 @@ module CallbacksTest
def test_skip_string # raises error
calls = []
- klass = build_class("bar")
+ klass = ActiveSupport::Deprecation.silence { build_class("bar") }
klass.class_eval { define_method(:bar) { calls << klass } }
assert_raises(ArgumentError) { klass.skip "bar" }
klass.new.run
@@ -1111,4 +1111,14 @@ module CallbacksTest
assert_equal 1, calls.length
end
end
+
+ class DeprecatedWarningTest < ActiveSupport::TestCase
+ def test_deprecate_string_callback
+ klass = Class.new(Record)
+
+ assert_deprecated do
+ klass.send :before_save, "tweedle_dee"
+ end
+ end
+ end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 2119352df0..1b66f784e4 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -1587,9 +1587,9 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal 3, hash_wia[:new_key]
end
- def test_should_use_default_proc_if_no_key_is_supplied
+ def test_should_return_nil_if_no_key_is_supplied
hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal 3, hash_wia.default
+ assert_equal nil, hash_wia.default
end
def test_should_use_default_value_for_unknown_key
diff --git a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
new file mode 100644
index 0000000000..65fadc5c20
--- /dev/null
+++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
@@ -0,0 +1,109 @@
+require 'abstract_unit'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
+
+class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase
+ def setup
+ @class = Class.new do
+ thread_mattr_accessor :foo
+ thread_mattr_accessor :bar, instance_writer: false
+ thread_mattr_reader :shaq, instance_reader: false
+ thread_mattr_accessor :camp, instance_accessor: false
+ end
+
+ @object = @class.new
+ end
+
+ def test_should_use_mattr_default
+ Thread.new do
+ assert_nil @class.foo
+ assert_nil @object.foo
+ end.join
+ end
+
+ def test_should_set_mattr_value
+ Thread.new do
+ @class.foo = :test
+ assert_equal :test, @class.foo
+
+ @class.foo = :test2
+ assert_equal :test2, @class.foo
+ end.join
+ end
+
+ def test_should_not_create_instance_writer
+ Thread.new do
+ assert_respond_to @class, :foo
+ assert_respond_to @class, :foo=
+ assert_respond_to @object, :bar
+ assert !@object.respond_to?(:bar=)
+ end.join
+ end
+
+ def test_should_not_create_instance_reader
+ Thread.new do
+ assert_respond_to @class, :shaq
+ assert !@object.respond_to?(:shaq)
+ end.join
+ end
+
+ def test_should_not_create_instance_accessors
+ Thread.new do
+ assert_respond_to @class, :camp
+ assert !@object.respond_to?(:camp)
+ assert !@object.respond_to?(:camp=)
+ end.join
+ end
+
+ def test_values_should_not_bleed_between_threads
+ threads = []
+ threads << Thread.new do
+ @class.foo = 'things'
+ sleep 1
+ assert_equal 'things', @class.foo
+ end
+
+ threads << Thread.new do
+ @class.foo = 'other things'
+ sleep 1
+ assert_equal 'other things', @class.foo
+ end
+
+ threads << Thread.new do
+ @class.foo = 'really other things'
+ sleep 1
+ assert_equal 'really other things', @class.foo
+ end
+
+ threads.each { |t| t.join }
+ end
+
+ def test_should_raise_name_error_if_attribute_name_is_invalid
+ exception = assert_raises NameError do
+ Class.new do
+ thread_cattr_reader "1nvalid"
+ end
+ end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ thread_cattr_writer "1nvalid"
+ end
+ end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ thread_mattr_reader "1valid_part"
+ end
+ end
+ assert_equal "invalid attribute name: 1valid_part", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ thread_mattr_writer "2valid_part"
+ end
+ end
+ assert_equal "invalid attribute name: 2valid_part", exception.message
+ end
+end
diff --git a/activesupport/test/core_ext/module/qualified_const_test.rb b/activesupport/test/core_ext/module/qualified_const_test.rb
index 37c9228a64..a3146cabe1 100644
--- a/activesupport/test/core_ext/module/qualified_const_test.rb
+++ b/activesupport/test/core_ext/module/qualified_const_test.rb
@@ -19,84 +19,94 @@ end
class QualifiedConstTest < ActiveSupport::TestCase
test "Object.qualified_const_defined?" do
- assert Object.qualified_const_defined?("QualifiedConstTestMod")
- assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod")
-
- assert Object.qualified_const_defined?("QualifiedConstTestMod::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y")
-
- assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y")
-
- if Module.method(:const_defined?).arity == 1
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
- else
- assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false)
- assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true)
+ assert_deprecated do
+ assert Object.qualified_const_defined?("QualifiedConstTestMod")
+ assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod")
+
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y")
+
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y")
+
+ if Module.method(:const_defined?).arity == 1
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ else
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false)
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true)
+ end
end
end
test "mod.qualified_const_defined?" do
- assert QualifiedConstTestMod.qualified_const_defined?("M")
- assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM")
-
- assert QualifiedConstTestMod.qualified_const_defined?("M::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("M::Y")
-
- assert QualifiedConstTestMod.qualified_const_defined?("M::C::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y")
-
- if Module.method(:const_defined?).arity == 1
- assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X")
- else
- assert QualifiedConstTestMod.qualified_const_defined?("N::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false)
- assert QualifiedConstTestMod.qualified_const_defined?("N::X", true)
+ assert_deprecated do
+ assert QualifiedConstTestMod.qualified_const_defined?("M")
+ assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM")
+
+ assert QualifiedConstTestMod.qualified_const_defined?("M::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("M::Y")
+
+ assert QualifiedConstTestMod.qualified_const_defined?("M::C::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y")
+
+ if Module.method(:const_defined?).arity == 1
+ assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ else
+ assert QualifiedConstTestMod.qualified_const_defined?("N::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false)
+ assert QualifiedConstTestMod.qualified_const_defined?("N::X", true)
+ end
end
end
test "qualified_const_get" do
- assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X")
- assert_equal false, QualifiedConstTestMod.qualified_const_get("X")
- assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X")
- assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X")
- assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X")
-
- assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")}
+ assert_deprecated do
+ assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X")
+ assert_equal false, QualifiedConstTestMod.qualified_const_get("X")
+ assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X")
+ assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X")
+ assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X")
+
+ assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")}
+ end
end
test "qualified_const_set" do
- begin
- m = Module.new
- assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m)
- assert_equal m, ::QualifiedConstTestMod2
-
- # We are going to assign to existing constants on purpose, so silence warnings.
- silence_warnings do
- assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true)
- assert_equal true, QualifiedConstTestMod::X
-
- assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10)
- assert_equal 10, QualifiedConstTestMod::M::X
- end
- ensure
- silence_warnings do
- QualifiedConstTestMod.qualified_const_set('QualifiedConstTestMod::X', false)
- QualifiedConstTestMod::M.qualified_const_set('X', 1)
+ assert_deprecated do
+ begin
+ m = Module.new
+ assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m)
+ assert_equal m, ::QualifiedConstTestMod2
+
+ # We are going to assign to existing constants on purpose, so silence warnings.
+ silence_warnings do
+ assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true)
+ assert_equal true, QualifiedConstTestMod::X
+
+ assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10)
+ assert_equal 10, QualifiedConstTestMod::M::X
+ end
+ ensure
+ silence_warnings do
+ QualifiedConstTestMod.qualified_const_set('QualifiedConstTestMod::X', false)
+ QualifiedConstTestMod::M.qualified_const_set('X', 1)
+ end
end
end
end
test "reject absolute paths" do
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")}
+ assert_deprecated do
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)}
+ end
end
private
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 0ff8f0f89b..5654aeb4f8 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -143,6 +143,14 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
gigabytes(number) * 1024
end
+ def petabytes(number)
+ terabytes(number) * 1024
+ end
+
+ def exabytes(number)
+ petabytes(number) * 1024
+ end
+
def test_to_s__phone
assert_equal("555-1234", 5551234.to_s(:phone))
assert_equal("800-555-1212", 8005551212.to_s(:phone))
@@ -266,7 +274,9 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1.18 MB', 1234567.to_s(:human_size)
assert_equal '1.15 GB', 1234567890.to_s(:human_size)
assert_equal '1.12 TB', 1234567890123.to_s(:human_size)
- assert_equal '1030 TB', terabytes(1026).to_s(:human_size)
+ assert_equal '1.1 PB', 1234567890123456.to_s(:human_size)
+ assert_equal '1.07 EB', 1234567890123456789.to_s(:human_size)
+ assert_equal '1030 EB', exabytes(1026).to_s(:human_size)
assert_equal '444 KB', kilobytes(444).to_s(:human_size)
assert_equal '1020 MB', megabytes(1023).to_s(:human_size)
assert_equal '3 TB', terabytes(3).to_s(:human_size)
@@ -289,6 +299,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 PB', 1234567890123456.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 EB', 1234567890123456789.to_s(:human_size, :prefix => :si)
end
end
@@ -388,6 +400,32 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
+ def test_to_formatted_s_is_deprecated
+ assert_deprecated do
+ 5551234.to_formatted_s(:phone)
+ end
+ end
+
+ def test_to_s_with_invalid_formatter
+ assert_equal '123', 123.to_s(:invalid)
+ assert_equal '2.5', 2.5.to_s(:invalid)
+ assert_equal '100000000000000000000', (100**10).to_s(:invalid)
+ assert_equal '1000010.0', BigDecimal("1000010").to_s(:invalid)
+ end
+
+ def test_default_to_s
+ assert_equal '123', 123.to_s
+ assert_equal '1111011', 123.to_s(2)
+
+ assert_equal '2.5', 2.5.to_s
+
+ assert_equal '100000000000000000000', (100**10).to_s
+ assert_equal '1010110101111000111010111100010110101100011000100000000000000000000', (100**10).to_s(2)
+
+ assert_equal '1000010.0', BigDecimal("1000010").to_s
+ assert_equal '10000 10.0', BigDecimal("1000010").to_s('5F')
+ end
+
def test_in_milliseconds
assert_equal 10_000, 10.seconds.in_milliseconds
end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index f096328cee..f28cebda3d 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/time'
+require 'active_support/core_ext/numeric'
require 'active_support/core_ext/range'
class RangeTest < ActiveSupport::TestCase
@@ -13,6 +14,11 @@ class RangeTest < ActiveSupport::TestCase
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
+ def test_to_s_with_numeric
+ number_range = (1..100)
+ assert_equal "BETWEEN '1' AND '100'", number_range.to_s(:db)
+ end
+
def test_date_range
assert_instance_of Range, DateTime.new..DateTime.new
assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index cd02ad3f3f..58a0a3964d 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -199,7 +199,7 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_assert_deprecated_warn_work_with_default_behavior
- ActiveSupport::Deprecation.instance_variable_set('@behavior' , nil)
+ ActiveSupport::Deprecation.instance_variable_set('@behavior', nil)
assert_deprecated('abc') do
ActiveSupport::Deprecation.warn 'abc'
end
@@ -340,6 +340,10 @@ class DeprecationTest < ActiveSupport::TestCase
assert_match(/You are calling deprecated method/, object.last_message)
end
+ def test_default_deprecation_horizon_should_always_bigger_than_current_rails_version
+ assert ActiveSupport::Deprecation.new.deprecation_horizon > ActiveSupport::VERSION::STRING
+ end
+
def test_default_gem_name
deprecator = ActiveSupport::Deprecation.new
diff --git a/activesupport/test/file_evented_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
index 071449d399..bc3f77bd54 100644
--- a/activesupport/test/file_evented_update_checker_test.rb
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -2,11 +2,16 @@ require 'abstract_unit'
require 'pathname'
require 'file_update_checker_shared_tests'
-class FileEventedUpdateCheckerTest < ActiveSupport::TestCase
+class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
include FileUpdateCheckerSharedTests
+ def setup
+ skip if ENV['LISTEN'] == '0'
+ super
+ end
+
def new_checker(files = [], dirs = {}, &block)
- ActiveSupport::FileEventedUpdateChecker.new(files, dirs, &block).tap do
+ ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do
wait
end
end
@@ -31,13 +36,13 @@ class FileEventedUpdateCheckerTest < ActiveSupport::TestCase
end
end
-class FileEventedUpdateCheckerPathHelperTest < ActiveSupport::TestCase
+class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase
def pn(path)
Pathname.new(path)
end
setup do
- @ph = ActiveSupport::FileEventedUpdateChecker::PathHelper.new
+ @ph = ActiveSupport::EventedFileUpdateChecker::PathHelper.new
end
test '#xpath returns the expanded path as a Pathname object' do
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index 100cbc9756..9c07e38fe5 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -21,6 +21,7 @@ module FileUpdateCheckerSharedTests
end
test 'should not execute the block if no paths are given' do
+ silence_warnings { require 'listen' }
i = 0
checker = new_checker { i += 1 }
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index d2801849ca..317e09b7f2 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -3,6 +3,7 @@ require 'multibyte_test_helpers'
require 'stringio'
require 'fileutils'
require 'tempfile'
+require 'concurrent/atomics'
class LoggerTest < ActiveSupport::TestCase
include MultibyteTestHelpers
@@ -16,6 +17,14 @@ class LoggerTest < ActiveSupport::TestCase
@logger = Logger.new(@output)
end
+ def test_log_outputs_to
+ assert Logger.logger_outputs_to?(@logger, @output), "Expected logger_outputs_to? @output to return true but was false"
+ assert Logger.logger_outputs_to?(@logger, @output, STDOUT), "Expected logger_outputs_to? @output or STDOUT to return true but was false"
+
+ assert_not Logger.logger_outputs_to?(@logger, STDOUT), "Expected logger_outputs_to? to STDOUT to return false, but was true"
+ assert_not Logger.logger_outputs_to?(@logger, STDOUT, STDERR), "Expected logger_outputs_to? to STDOUT or STDERR to return false, but was true"
+ end
+
def test_write_binary_data_to_existing_file
t = Tempfile.new ['development', 'log']
t.binmode
@@ -64,7 +73,7 @@ class LoggerTest < ActiveSupport::TestCase
def test_should_not_log_debug_messages_when_log_level_is_info
@logger.level = Logger::INFO
@logger.add(Logger::DEBUG, @message)
- assert ! @output.string.include?(@message)
+ assert_not @output.string.include?(@message)
end
def test_should_add_message_passed_as_block_when_using_add
@@ -113,6 +122,7 @@ class LoggerTest < ActiveSupport::TestCase
end
def test_buffer_multibyte
+ @logger.level = Logger::INFO
@logger.info(UNICODE_STRING)
@logger.info(BYTE_STRING)
assert @output.string.include?(UNICODE_STRING)
@@ -120,14 +130,93 @@ class LoggerTest < ActiveSupport::TestCase
byte_string.force_encoding("ASCII-8BIT")
assert byte_string.include?(BYTE_STRING)
end
-
+
def test_silencing_everything_but_errors
@logger.silence do
@logger.debug "NOT THERE"
@logger.error "THIS IS HERE"
end
-
- assert !@output.string.include?("NOT THERE")
+
+ assert_not @output.string.include?("NOT THERE")
assert @output.string.include?("THIS IS HERE")
end
+
+ def test_logger_level_per_object_thread_safety
+ logger1 = Logger.new(StringIO.new)
+ logger2 = Logger.new(StringIO.new)
+
+ level = Logger::DEBUG
+ assert_equal level, logger1.level, "Expected level #{level_name(level)}, got #{level_name(logger1.level)}"
+ assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}"
+
+ logger1.level = Logger::ERROR
+ assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}"
+ end
+
+ def test_logger_level_main_thread_safety
+ @logger.level = Logger::INFO
+ assert_level(Logger::INFO)
+
+ latch = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ t = Thread.new do
+ latch.wait
+ assert_level(Logger::INFO)
+ latch2.count_down
+ end
+
+ @logger.silence(Logger::ERROR) do
+ assert_level(Logger::ERROR)
+ latch.count_down
+ latch2.wait
+ end
+
+ t.join
+ end
+
+ def test_logger_level_local_thread_safety
+ @logger.level = Logger::INFO
+ assert_level(Logger::INFO)
+
+ thread_1_latch = Concurrent::CountDownLatch.new
+ thread_2_latch = Concurrent::CountDownLatch.new
+
+ threads = (1..2).collect do |thread_number|
+ Thread.new do
+ # force thread 2 to wait until thread 1 is already in @logger.silence
+ thread_2_latch.wait if thread_number == 2
+
+ @logger.silence(Logger::ERROR) do
+ assert_level(Logger::ERROR)
+ @logger.silence(Logger::DEBUG) do
+ # allow thread 2 to finish but hold thread 1
+ if thread_number == 1
+ thread_2_latch.count_down
+ thread_1_latch.wait
+ end
+ assert_level(Logger::DEBUG)
+ end
+ end
+
+ # allow thread 1 to finish
+ assert_level(Logger::INFO)
+ thread_1_latch.count_down if thread_number == 2
+ end
+ end
+
+ threads.each(&:join)
+ assert_level(Logger::INFO)
+ end
+
+ private
+ def level_name(level)
+ ::Logger::Severity.constants.find do |severity|
+ Logger.const_get(severity) == level
+ end.to_s
+ end
+
+ def assert_level(level)
+ assert_equal level, @logger.level, "Expected level #{level_name(level)}, got #{level_name(@logger.level)}"
+ end
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 8d4d9d736c..c1e0b19248 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -612,28 +612,54 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
['abc', 3],
['ã“ã«ã¡ã‚', 4],
[[0x0924, 0x094D, 0x0930].pack('U*'), 2],
+ # GB3
[%w(cr lf), 1],
+ # GB4
+ [%w(cr n), 2],
+ [%w(lf n), 2],
+ [%w(control n), 2],
+ [%w(cr extend), 2],
+ [%w(lf extend), 2],
+ [%w(control extend), 2],
+ # GB 5
+ [%w(n cr), 2],
+ [%w(n lf), 2],
+ [%w(n control), 2],
+ [%w(extend cr), 2],
+ [%w(extend lf), 2],
+ [%w(extend control), 2],
+ # GB 6
[%w(l l), 1],
[%w(l v), 1],
[%w(l lv), 1],
[%w(l lvt), 1],
+ # GB7
[%w(lv v), 1],
[%w(lv t), 1],
[%w(v v), 1],
[%w(v t), 1],
+ # GB8
[%w(lvt t), 1],
[%w(t t), 1],
+ # GB8a
+ [%w(r r), 1],
+ # GB9
[%w(n extend), 1],
+ # GB9a
+ [%w(n spacingmark), 1],
+ # GB10
[%w(n n), 2],
+ # Other
[%w(n cr lf n), 3],
- [%w(n l v t), 2]
+ [%w(n l v t), 2],
+ [%w(cr extend n), 3],
].each do |input, expected_length|
if input.kind_of?(Array)
str = string_from_classes(input)
else
str = input
end
- assert_equal expected_length, chars(str).grapheme_length
+ assert_equal expected_length, chars(str).grapheme_length, input.inspect
end
end
@@ -698,7 +724,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
# Characters from the character classes as described in UAX #29
character_from_class = {
:l => 0x1100, :v => 0x1160, :t => 0x11A8, :lv => 0xAC00, :lvt => 0xAC01, :cr => 0x000D, :lf => 0x000A,
- :extend => 0x094D, :n => 0x64
+ :extend => 0x094D, :n => 0x64, :spacingmark => 0x0903, :r => 0x1F1E6, :control => 0x0001
}
classes.collect do |k|
character_from_class[k.intern]
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index 2a885e32bf..d493a48fe4 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -104,11 +104,8 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
protected
def each_line_of_norm_tests(&block)
- lines = 0
- max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile
File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f |
- until f.eof? || (max_test_lines > 38 and lines > max_test_lines)
- lines += 1
+ until f.eof?
line = f.gets.chomp!
next if (line.empty? || line =~ /^\#/)
diff --git a/activesupport/test/multibyte_grapheme_break_conformance.rb b/activesupport/test/multibyte_grapheme_break_conformance.rb
new file mode 100644
index 0000000000..7d185e2cae
--- /dev/null
+++ b/activesupport/test/multibyte_grapheme_break_conformance.rb
@@ -0,0 +1,76 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ $stderr.puts "Downloading #{from} to #{to}"
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
+ end
+ end
+ end
+ end
+end
+
+class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
+ TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary"
+ TEST_DATA_FILE = '/GraphemeBreakTest.txt'
+ CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+
+ def setup
+ FileUtils.mkdir_p(CACHE_DIR)
+ Downloader.download(TEST_DATA_URL + TEST_DATA_FILE, CACHE_DIR + TEST_DATA_FILE)
+ end
+
+ def test_breaks
+ each_line_of_break_tests do |*cols|
+ *clusters, comment = *cols
+ packed = ActiveSupport::Multibyte::Unicode.pack_graphemes(clusters)
+ assert_equal clusters, ActiveSupport::Multibyte::Unicode.unpack_graphemes(packed), comment
+ end
+ end
+
+ protected
+ def each_line_of_break_tests(&block)
+ lines = 0
+ max_test_lines = 0 # Don't limit below 21, because that's the header of the testfile
+ File.open(File.join(CACHE_DIR, TEST_DATA_FILE), 'r') do | f |
+ until f.eof? || (max_test_lines > 21 and lines > max_test_lines)
+ lines += 1
+ line = f.gets.chomp!
+ next if (line.empty? || line =~ /^\#/)
+
+ cols, comment = line.split("#")
+ # Cluster breaks are represented by ÷
+ clusters = cols.split("÷").map{|e| e.strip}.reject{|e| e.empty? }
+ clusters = clusters.map do |cluster|
+ # Codepoints within each cluster are separated by ×
+ codepoints = cluster.split("×").map{|e| e.strip}.reject{|e| e.empty? }
+ # codepoints are in hex in the test suite, pack wants them as integers
+ codepoints.map{|codepoint| codepoint.to_i(16)}
+ end
+
+ # The tests contain a solitary U+D800 <Non Private Use High
+ # Surrogate, First> character, which Ruby does not allow to stand
+ # alone in a UTF-8 string. So we'll just skip it.
+ next if clusters.flatten.include?(0xd800)
+
+ clusters << comment.strip
+
+ yield(*clusters)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/multibyte_normalization_conformance.rb b/activesupport/test/multibyte_normalization_conformance.rb
new file mode 100644
index 0000000000..839aec7fa8
--- /dev/null
+++ b/activesupport/test/multibyte_normalization_conformance.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+require 'multibyte_test_helpers'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ $stderr.puts "Downloading #{from} to #{to}"
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
+ end
+ end
+ end
+ end
+end
+
+class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
+ include MultibyteTestHelpers
+
+ UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
+ UNIDATA_FILE = '/NormalizationTest.txt'
+ CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+
+ def setup
+ FileUtils.mkdir_p(CACHE_DIR)
+ Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
+ @proxy = ActiveSupport::Multibyte::Chars
+ end
+
+ def test_normalizations_C
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+
+ # CONFORMANCE:
+ # 1. The following invariants must be true for all conformant implementations
+ #
+ # NFC
+ # c2 == NFC(c1) == NFC(c2) == NFC(c3)
+ assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
+ #
+ # c4 == NFC(c4) == NFC(c5)
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_D
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFD
+ # c3 == NFD(c1) == NFD(c2) == NFD(c3)
+ assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
+ # c5 == NFD(c4) == NFD(c5)
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_KC
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKC
+ # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
+ assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_KD
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKD
+ # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
+ assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
+ end
+ end
+
+ protected
+ def each_line_of_norm_tests(&block)
+ lines = 0
+ max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile
+ File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f |
+ until f.eof? || (max_test_lines > 38 and lines > max_test_lines)
+ lines += 1
+ line = f.gets.chomp!
+ next if (line.empty? || line =~ /^\#/)
+
+ cols, comment = line.split("#")
+ cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? }
+ next unless cols.length == 5
+
+ # codepoints are in hex in the test suite, pack wants them as integers
+ cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") }
+ cols << comment
+
+ yield(*cols)
+ end
+ end
+ end
+
+ def inspect_codepoints(str)
+ str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ')
+ end
+end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index f729f0a95b..1cb17e6197 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -42,6 +42,21 @@ module Notifications
ActiveSupport::Notifications.instrument(name)
assert_equal expected, events
end
+
+ def test_subsribing_to_instrumentation_while_inside_it
+ # the repro requires that there are no evented subscribers for the "foo" event,
+ # so we have to duplicate some of the setup code
+ old_notifier = ActiveSupport::Notifications.notifier
+ ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new
+
+ ActiveSupport::Notifications.subscribe('foo', TestSubscriber.new)
+
+ ActiveSupport::Notifications.instrument('foo') do
+ ActiveSupport::Notifications.subscribe('foo') {}
+ end
+ ensure
+ ActiveSupport::Notifications.notifier = old_notifier
+ end
end
class UnsubscribeTest < TestCase
@@ -217,7 +232,7 @@ module Notifications
assert_equal 1, @events.size
assert_equal Hash[:payload => "notifications",
- :exception => ["RuntimeError", "FAIL"]], @events.last.payload
+ :exception => ["RuntimeError", "FAIL"], :exception_object => e], @events.last.payload
end
def test_event_is_pushed_even_without_block
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 7f62d7c0b3..b3464462c8 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -34,6 +34,14 @@ module ActiveSupport
gigabytes(number) * 1024
end
+ def petabytes(number)
+ terabytes(number) * 1024
+ end
+
+ def exabytes(number)
+ petabytes(number) * 1024
+ end
+
def test_number_to_phone
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert_equal("555-1234", number_helper.number_to_phone(5551234))
@@ -219,7 +227,9 @@ module ActiveSupport
assert_equal '1.18 MB', number_helper.number_to_human_size(1234567)
assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890)
assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123)
- assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026))
+ assert_equal '1.1 PB', number_helper.number_to_human_size(1234567890123456)
+ assert_equal '1.07 EB', number_helper.number_to_human_size(1234567890123456789)
+ assert_equal '1030 EB', number_helper.number_to_human_size(exabytes(1026))
assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444))
assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023))
assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3))
@@ -245,6 +255,8 @@ module ActiveSupport
assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ assert_equal '1.23 PB', number_helper.number_to_human_size(1234567890123456, :prefix => :si)
+ assert_equal '1.23 EB', number_helper.number_to_human_size(1234567890123456789, :prefix => :si)
end
end
end
diff --git a/ci/travis.rb b/ci/travis.rb
index 52fef05fbf..e9a3626b9a 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -23,6 +23,7 @@ class Build
'ar' => 'activerecord',
'av' => 'actionview',
'aj' => 'activejob',
+ 'ac' => 'actioncable',
'guides' => 'guides'
}
@@ -59,7 +60,14 @@ class Build
def tasks
if activerecord?
- ['db:mysql:rebuild', "#{adapter}:#{'isolated_' if isolated?}test"]
+ tasks = ["#{adapter}:#{'isolated_' if isolated?}test"]
+ case adapter
+ when 'mysql2'
+ tasks.unshift 'db:mysql:rebuild'
+ when 'postgresql'
+ tasks.unshift 'db:postgresql:rebuild'
+ end
+ tasks
else
["test", ('isolated' if isolated?), ('integration' if integration?)].compact.join(":")
end
@@ -72,6 +80,10 @@ class Build
key.join(':')
end
+ def activesupport?
+ gem == 'activesupport'
+ end
+
def activerecord?
gem == 'activerecord'
end
@@ -101,11 +113,22 @@ class Build
tasks.each do |task|
cmd = "bundle exec rake #{task}"
puts "Running command: #{cmd}"
- return false unless system(cmd)
+ return false unless system(env, cmd)
end
true
end
+ def env
+ if activesupport? && !isolated?
+ # There is a known issue with the listen tests that casuses files to be
+ # incorrectly GC'ed even when they are still in-use. The current is to
+ # only run them in isolation to avoid randomly failing our test suite.
+ { 'LISTEN' => '0' }
+ else
+ {}
+ end
+ end
+
def run_bug_report_templates
Dir.glob('bug_report_templates/*.rb').all? do |file|
system(Gem.ruby, '-w', file)
@@ -124,6 +147,7 @@ ENV['GEM'].split(',').each do |gem|
[false, true].each do |isolated|
next if ENV['TRAVIS_PULL_REQUEST'] && ENV['TRAVIS_PULL_REQUEST'] != 'false' && isolated
next if gem == 'railties' && isolated
+ next if gem == 'ac' && isolated
next if gem == 'aj:integration' && isolated
next if gem == 'guides' && isolated
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 09fb7b1a0e..aae405d5ac 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,5 @@
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
* Add code of conduct to contributing guide
*Jon Moss*
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 554d94ad50..73ca600361 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -16,7 +16,7 @@ HTML
end
def header(text, header_level)
- # Always increase the heading level by, so we can use h1, h2 heading in the document
+ # Always increase the heading level by 1, so we can use h1, h2 heading in the document
header_level += 1
%(<h#{header_level}>#{text}</h#{header_level}>)
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
new file mode 100644
index 0000000000..2650384df3
--- /dev/null
+++ b/guides/source/5_0_release_notes.md
@@ -0,0 +1,768 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+Ruby on Rails 5.0 Release Notes
+===============================
+
+Highlights in Rails 5.0:
+
+* Action Cable
+* Rails API
+* Active Record Attributes API
+* Test Runner
+* Exclusive use of `rails` CLI over Rake
+* Sprockets 3
+* Turbolinks 5
+* Ruby 2.2.2+ required
+
+These release notes cover only the major changes. To learn about various bug
+fixes and changes, please refer to the change logs or check out the [list of
+commits](https://github.com/rails/rails/commits/5-0-stable) in the main Rails
+repository on GitHub.
+
+--------------------------------------------------------------------------------
+
+Upgrading to Rails 5.0
+----------------------
+
+If you're upgrading an existing application, it's a great idea to have good test
+coverage before going in. You should also first upgrade to Rails 4.2 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 5.0. A list of things to watch out for when upgrading is
+available in the
+[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-2-to-rails-5-0)
+guide.
+
+
+Major Features
+--------------
+
+### Action Cable
+[Pull Request](https://github.com/rails/rails/pull/22586)
+
+ToDo...
+
+### Rails API
+[Pull Request](https://github.com/rails/rails/pull/19832)
+
+ToDo...
+
+### Active Record attributes API
+
+ToDo...
+
+### Test Runner
+[Pull Request](https://github.com/rails/rails/pull/19216)
+
+ToDo...
+
+
+Railties
+--------
+
+Please refer to the [Changelog][railties] for detailed changes.
+
+### Removals
+
+* Removed debugger support, use byebug instead. `debugger` is not supported by
+ Ruby
+ 2.2. ([commit](https://github.com/rails/rails/commit/93559da4826546d07014f8cfa399b64b4a143127))
+
+* Removed deprecated `test:all` and `test:all:db` tasks.
+ ([commit](https://github.com/rails/rails/commit/f663132eef0e5d96bf2a58cec9f7c856db20be7c))
+
+* Removed deprecated `Rails::Rack::LogTailer`.
+ ([commit](https://github.com/rails/rails/commit/c564dcb75c191ab3d21cc6f920998b0d6fbca623))
+
+* Removed deprecated `RAILS_CACHE` constant.
+ ([commit](https://github.com/rails/rails/commit/b7f856ce488ef8f6bf4c12bb549f462cb7671c08))
+
+* Removed deprecated `serve_static_assets` configuration.
+ ([commit](https://github.com/rails/rails/commit/463b5d7581ee16bfaddf34ca349b7d1b5878097c))
+
+* Removed the documentation tasks `doc:app`, `doc:rails`, and `doc:guides`.
+ ([commit](https://github.com/rails/rails/commit/cd7cc5254b090ccbb84dcee4408a5acede25ef2a))
+
+* Removed `Rack::ContentLength` middleware from the default
+ stack. ([Commit](https://github.com/rails/rails/commit/56903585a099ab67a7acfaaef0a02db8fe80c450))
+
+### Deprecations
+
+* Deprecated `config.static_cache_control` in favor of
+ `config.public_file_server.headers`.
+ ([Pull Request](https://github.com/rails/rails/pull/22173))
+
+* Deprecated `config.serve_static_files` in favor of `config.public_file_server.enabled`.
+ ([Pull Request](https://github.com/rails/rails/pull/22173))
+
+### Notable changes
+
+* Added Rails test runner `bin/rails test`.
+ ([Pull Request](https://github.com/rails/rails/pull/19216))
+
+* Newly generated applications and plugins get a `README.md` in Markdown.
+ ([commit](https://github.com/rails/rails/commit/89a12c931b1f00b90e74afffcdc2fc21f14ca663),
+ [Pull Request](https://github.com/rails/rails/pull/22068))
+
+* Added `bin/rails restart` task to restart your Rails app by touching `tmp/restart.txt`.
+ ([Pull Request](https://github.com/rails/rails/pull/18965))
+
+* Added `bin/rails initializers` task to print out all defined initializers in
+ the order they are invoked by Rails.
+ ([Pull Request](https://github.com/rails/rails/pull/19323))
+
+* Added `bin/rails dev:cache` to enable or disable caching in development mode.
+ ([Pull Request](https://github.com/rails/rails/pull/20961))
+
+* Added `bin/update` script to update the development environment automatically.
+ ([Pull Request](https://github.com/rails/rails/pull/20972))
+
+* Proxy Rake tasks through `bin/rails`.
+ ([Pull Request](https://github.com/rails/rails/pull/22457),
+ [Pull Request](https://github.com/rails/rails/pull/22288))
+
+
+Action Pack
+-----------
+
+Please refer to the [Changelog][action-pack] for detailed changes.
+
+### Removals
+
+* Removed `ActionDispatch::Request::Utils.deep_munge`.
+ ([commit](https://github.com/rails/rails/commit/52cf1a71b393486435fab4386a8663b146608996))
+
+* Removed `ActionController::HideActions`.
+ ([Pull Request](https://github.com/rails/rails/pull/18371))
+
+* Removed `respond_to` and `respond_with` placeholder methods, this functionality
+ has been extracted to the
+ [responders](https://github.com/plataformatec/responders) gem.
+ ([commit](https://github.com/rails/rails/commit/afd5e9a7ff0072e482b0b0e8e238d21b070b6280))
+
+* Removed deprecated assertion files.
+ ([commit](https://github.com/rails/rails/commit/92e27d30d8112962ee068f7b14aa7b10daf0c976))
+
+* Removed deprecated usage of string keys in URL helpers.
+ ([commit](https://github.com/rails/rails/commit/34e380764edede47f7ebe0c7671d6f9c9dc7e809))
+
+* Removed deprecated `only_path` option on `*_path` helpers.
+ ([commit](https://github.com/rails/rails/commit/e4e1fd7ade47771067177254cb133564a3422b8a))
+
+* Removed deprecated `NamedRouteCollection#helpers`.
+ ([commit](https://github.com/rails/rails/commit/2cc91c37bc2e32b7a04b2d782fb8f4a69a14503f))
+
+* Removed deprecated support to define routes with `:to` option that doesn't contain `#`.
+ ([commit](https://github.com/rails/rails/commit/1f3b0a8609c00278b9a10076040ac9c90a9cc4a6))
+
+* Removed deprecated `ActionDispatch::Response#to_ary`.
+ ([commit](https://github.com/rails/rails/commit/4b19d5b7bcdf4f11bd1e2e9ed2149a958e338c01))
+
+* Removed deprecated `ActionDispatch::Request#deep_munge`.
+ ([commit](https://github.com/rails/rails/commit/7676659633057dacd97b8da66e0d9119809b343e))
+
+* Removed deprecated
+ `ActionDispatch::Http::Parameters#symbolized_path_parameters`.
+ ([commit](https://github.com/rails/rails/commit/7fe7973cd8bd119b724d72c5f617cf94c18edf9e))
+
+* Removed deprecated option `use_route` in controller tests.
+ ([commit](https://github.com/rails/rails/commit/e4cfd353a47369dd32198b0e67b8cbb2f9a1c548))
+
+* Removed `assigns` and `assert_template`. Both methods have been extracted
+ into the
+ [rails-controller-testing](https://github.com/rails/rails-controller-testing)
+ gem.
+ ([Pull Request](https://github.com/rails/rails/pull/20138))
+
+### Deprecations
+
+* Deprecated all `*_filter` callbacks in favor of `*_action` callbacks.
+ ([Pull Request](https://github.com/rails/rails/pull/18410))
+
+* Deprecated `*_via_redirect` integration test methods. Use `follow_redirect!`
+ manually after the request call for the same behavior.
+ ([Pull Request](https://github.com/rails/rails/pull/18693))
+
+* Deprecated `AbstractController#skip_action_callback` in favor of individual
+ skip_callback methods.
+ ([Pull Request](https://github.com/rails/rails/pull/19060))
+
+* Deprecated `:nothing` option for `render` method.
+ ([Pull Request](https://github.com/rails/rails/pull/20336))
+
+* Deprecated passing first parameter as `Hash` and default status code for
+ `head` method.
+ ([Pull Request](https://github.com/rails/rails/pull/20407))
+
+* Deprecated using strings or symbols for middleware class names. Use class
+ names instead.
+ ([commit](https://github.com/rails/rails/commit/83b767ce))
+
+* Deprecated accessing mime types via constants (eg. `Mime::HTML`). Use the
+ subscript operator with a symbol instead (eg. `Mime[:html]`).
+ ([Pull Request](https://github.com/rails/rails/pull/21869))
+
+* Deprecated `redirect_to :back` in favor of `redirect_back`, which accepts a
+ required `fallback_location` argument, thus eliminating the possibility of a
+ `RedirectBackError`.
+ ([Pull Request](https://github.com/rails/rails/pull/22506))
+
+### Notable changes
+
+* Added `ActionController::Renderer` to render arbitrary templates
+ outside controller actions.
+ ([Pull Request](https://github.com/rails/rails/pull/18546))
+
+* Migrating to keyword arguments syntax in `ActionController::TestCase` and
+ `ActionDispatch::Integration` HTTP request methods.
+ ([Pull Request](https://github.com/rails/rails/pull/18323))
+
+* Added `http_cache_forever` to Action Controller, so we can cache a response
+ that never gets expired.
+ ([Pull Request](https://github.com/rails/rails/pull/18394))
+
+* Provide friendlier access to request variants.
+ ([Pull Request](https://github.com/rails/rails/pull/18939))
+
+* For actions with no corresponding templates, render `head :no_content`
+ instead of raising an error.
+ ([Pull Request](https://github.com/rails/rails/pull/19377))
+
+* Added the ability to override default form builder for a controller.
+ ([Pull Request](https://github.com/rails/rails/pull/19736))
+
+* Added support for API only apps.
+ `ActionController::API` is added as a replacement of
+ `ActionController::Base` for this kind of applications.
+ ([Pull Request](https://github.com/rails/rails/pull/19832))
+
+* Make `ActionController::Parameters` no longer inherits from
+ `HashWithIndifferentAccess`.
+ ([Pull Request](https://github.com/rails/rails/pull/20868))
+
+* Make it easier to opt in to `config.force_ssl` and `config.ssl_options` by
+ making them less dangerous to try and easier to disable.
+ ([Pull Request](https://github.com/rails/rails/pull/21520))
+
+* Added the ability of returning arbitrary headers to `ActionDispatch::Static`.
+ ([Pull Request](https://github.com/rails/rails/pull/19135))
+
+* Changed the `protect_from_forgery` prepend default to `false`.
+ ([commit](https://github.com/rails/rails/commit/39794037817703575c35a75f1961b01b83791191))
+
+* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1. Use
+ `ActionDispatch::IntegrationTest` instead.
+ ([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d))
+
+Action View
+-------------
+
+Please refer to the [Changelog][action-view] for detailed changes.
+
+### Removals
+
+* Removed deprecated `AbstractController::Base::parent_prefixes`.
+ ([commit](https://github.com/rails/rails/commit/34bcbcf35701ca44be559ff391535c0dd865c333))
+
+* Removed `ActionView::Helpers::RecordTagHelper`, this functionality
+ has been extracted to the
+ [record_tag_helper](https://github.com/rails/record_tag_helper) gem.
+ ([Pull Request](https://github.com/rails/rails/pull/18411))
+
+* Removed `:rescue_format` option for `translate` helper since it's no longer
+ supported by I18n.
+ ([Pull Request](https://github.com/rails/rails/pull/20019))
+
+### Notable Changes
+
+* Changed the default template handler from `ERB` to `Raw`.
+ ([commit](https://github.com/rails/rails/commit/4be859f0fdf7b3059a28d03c279f03f5938efc80))
+
+* Collection rendering automatically caches and fetches multiple partials.
+ ([Pull Request](https://github.com/rails/rails/pull/18948))
+
+* Allow defining explicit collection caching using a `# Template Collection: ...`
+ directive inside templates.
+ ([Pull Request](https://github.com/rails/rails/pull/20781))
+
+* Added wildcard matching to explicit dependencies.
+ ([Pull Request](https://github.com/rails/rails/pull/20904))
+
+* Make `disable_with` the default behavior for submit tags. Disables the
+ button on submit to prevent double submits.
+ ([Pull Request](https://github.com/rails/rails/pull/21135))
+
+
+Action Mailer
+-------------
+
+Please refer to the [Changelog][action-mailer] for detailed changes.
+
+### Removals
+
+* Removed deprecated `*_path` helpers in email views.
+ ([commit](https://github.com/rails/rails/commit/d282125a18c1697a9b5bb775628a2db239142ac7))
+
+* Removed deprecated `deliver` and `deliver!` methods.
+ ([commit](https://github.com/rails/rails/commit/755dcd0691f74079c24196135f89b917062b0715))
+
+### Notable changes
+
+* Template lookup now respects default locale and I18n fallbacks.
+ ([commit](https://github.com/rails/rails/commit/ecb1981b))
+
+* Added `_mailer` suffix to mailers created via generator, following the same
+ naming convention used in controllers and jobs.
+ ([Pull Request](https://github.com/rails/rails/pull/18074))
+
+* Added `assert_enqueued_emails` and `assert_no_enqueued_emails`.
+ ([Pull Request](https://github.com/rails/rails/pull/18403))
+
+* Added `config.action_mailer.deliver_later_queue_name` configuration to set
+ the mailer queue name.
+ ([Pull Request](https://github.com/rails/rails/pull/18587))
+
+
+Active Record
+-------------
+
+Please refer to the [Changelog][active-record] for detailed changes.
+
+### Removals
+
+* Removed deprecated behavior allowing nested arrays to be passed as query
+ values. ([Pull Request](https://github.com/rails/rails/pull/17919))
+
+* Removed deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema`. This
+ method was replaced by `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`.
+ ([commit](https://github.com/rails/rails/commit/ad783136d747f73329350b9bb5a5e17c8f8800da))
+
+* Removed deprecated `serialized_attributes`.
+ ([commit](https://github.com/rails/rails/commit/82043ab53cb186d59b1b3be06122861758f814b2))
+
+* Removed deprecated automatic counter caches on `has_many :through`.
+ ([commit](https://github.com/rails/rails/commit/87c8ce340c6c83342df988df247e9035393ed7a0))
+
+* Removed deprecated `sanitize_sql_hash_for_conditions`.
+ ([commit](https://github.com/rails/rails/commit/3a59dd212315ebb9bae8338b98af259ac00bbef3))
+
+* Removed deprecated `Reflection#source_macro`.
+ ([commit](https://github.com/rails/rails/commit/ede8c199a85cfbb6457d5630ec1e285e5ec49313))
+
+* Removed deprecated `symbolized_base_class` and `symbolized_sti_name`.
+ ([commit](https://github.com/rails/rails/commit/9013e28e52eba3a6ffcede26f85df48d264b8951))
+
+* Removed deprecated `ActiveRecord::Base.disable_implicit_join_references=`.
+ ([commit](https://github.com/rails/rails/commit/0fbd1fc888ffb8cbe1191193bf86933110693dfc))
+
+* Removed deprecated access to connection specification using a string accessor.
+ ([commit](https://github.com/rails/rails/commit/efdc20f36ccc37afbb2705eb9acca76dd8aabd4f))
+
+* Removed deprecated support to preload instance-dependent associations.
+ ([commit](https://github.com/rails/rails/commit/4ed97979d14c5e92eb212b1a629da0a214084078))
+
+* Removed deprecated support for PostgreSQL ranges with exclusive lower bounds.
+ ([commit](https://github.com/rails/rails/commit/a076256d63f64d194b8f634890527a5ed2651115))
+
+* Removed deprecation when modifying a relation with cached Arel.
+ This raises an `ImmutableRelation` error instead.
+ ([commit](https://github.com/rails/rails/commit/3ae98181433dda1b5e19910e107494762512a86c))
+
+* Removed `ActiveRecord::Serialization::XmlSerializer` from core. This feature
+ has been extracted into the
+ [activemodel-serializers-xml](https://github.com/rails/activemodel-serializers-xml)
+ gem. ([Pull Request](https://github.com/rails/rails/pull/21161))
+
+* Removed support for the legacy `mysql` database adapter from core. It will
+ live on in a separate gem for now, but most users should just use `mysql2`.
+
+* Removed support for the `protected_attributes` gem.
+ ([commit](https://github.com/rails/rails/commit/f4fbc0301021f13ae05c8e941c8efc4ae351fdf9))
+
+### Deprecations
+
+* Deprecated passing a class as a value in a query. Users should pass strings
+ instead. ([Pull Request](https://github.com/rails/rails/pull/17916))
+
+* Deprecated returning `false` as a way to halt Active Record callback
+ chains. The recommended way is to
+ `throw(:abort)`. ([Pull Request](https://github.com/rails/rails/pull/17227))
+
+* Deprecated `ActiveRecord::Base.errors_in_transactional_callbacks=`.
+ ([commit](https://github.com/rails/rails/commit/07d3d402341e81ada0214f2cb2be1da69eadfe72))
+
+* Deprecated passing of `start` value to `find_in_batches` and `find_each`
+ in favour of `begin_at` value.
+ ([Pull Request](https://github.com/rails/rails/pull/18961))
+
+* Deprecated `Relation#uniq` use `Relation#distinct` instead.
+ ([commit](https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f))
+
+* Deprecated the PostgreSQL `:point` type in favor of a new one which will return
+ `Point` objects instead of an `Array`
+ ([Pull Request](https://github.com/rails/rails/pull/20448))
+
+* Deprecated force association reload by passing a truthy argument to
+ association method.
+ ([Pull Request](https://github.com/rails/rails/pull/20888))
+
+* Deprecated the keys for association `restrict_dependent_destroy` errors in favor
+ of new key names.
+ ([Pull Request](https://github.com/rails/rails/pull/20668))
+
+* Synchronize behavior of `#tables`.
+ ([Pull Request](https://github.com/rails/rails/pull/21601))
+
+* Deprecated `SchemaCache#tables`, `SchemaCache#table_exists?` and
+ `SchemaCache#clear_table_cache!` in favor of their new data source
+ counterparts.
+ ([Pull Request](https://github.com/rails/rails/pull/21715))
+
+* Deprecated `connection.tables` on the SQLite3 and MySQL adapters.
+ ([Pull Request](https://github.com/rails/rails/pull/21601))
+
+* Deprecated passing arguments to `#tables` - the `#tables` method of some
+ adapters (mysql2, sqlite3) would return both tables and views while others
+ (postgresql) just return tables. To make their behavior consistent,
+ `#tables` will return only tables in the future.
+ ([Pull Request](https://github.com/rails/rails/pull/21601))
+
+* Deprecated `table_exists?` - The `#table_exists?` method would check both
+ tables and views. To make their behavior consistent with `#tables`,
+ `#table_exists?` will check only tables in the future.
+ ([Pull Request](https://github.com/rails/rails/pull/21601))
+
+* Deprecate sending the `offset` argument to `find_nth`. Please use the
+ `offset` method on relation instead.
+ ([Pull Request](https://github.com/rails/rails/pull/22053))
+
+### Notable changes
+
+* Added a `foreign_key` option to `references` while creating the table.
+ ([commit](https://github.com/rails/rails/commit/99a6f9e60ea55924b44f894a16f8de0162cf2702))
+
+* New attributes
+ API. ([commit](https://github.com/rails/rails/commit/8c752c7ac739d5a86d4136ab1e9d0142c4041e58))
+
+* Added `:enum_prefix`/`:enum_suffix` option to `enum`
+ definition. ([Pull Request](https://github.com/rails/rails/pull/19813))
+
+* Added `#cache_key` to `ActiveRecord::Relation`.
+ ([Pull Request](https://github.com/rails/rails/pull/20884))
+
+* Added `ActiveRecord::Relation#outer_joins`.
+ ([Pull Request](https://github.com/rails/rails/pull/12071))
+
+* Require `belongs_to` by default.
+ ([Pull Request](https://github.com/rails/rails/pull/18937)) - Deprecate
+ `required` option in favor of `optional` for `belongs_to`
+
+* Changed the default `null` value for `timestamps` to `false`.
+ ([commit](https://github.com/rails/rails/commit/a939506f297b667291480f26fa32a373a18ae06a))
+
+* Added `ActiveRecord::SecureToken` in order to encapsulate generation of
+ unique tokens for attributes in a model using `SecureRandom`.
+ ([Pull Request](https://github.com/rails/rails/pull/18217))
+
+* Added `:if_exists` option for `drop_table`.
+ ([Pull Request](https://github.com/rails/rails/pull/18597))
+
+* Added `ActiveRecord::Base#accessed_fields`, which can be used to quickly
+ discover which fields were read from a model when you are looking to only
+ select the data you need from the database.
+ ([commit](https://github.com/rails/rails/commit/be9b68038e83a617eb38c26147659162e4ac3d2c))
+
+* Added the `#or` method on `ActiveRecord::Relation`, allowing use of the OR
+ operator to combine WHERE or HAVING clauses.
+ ([commit](https://github.com/rails/rails/commit/b0b37942d729b6bdcd2e3178eda7fa1de203b3d0))
+
+* Added `:time` option added for `#touch`.
+ ([Pull Request](https://github.com/rails/rails/pull/18956))
+
+* Added `ActiveRecord::Base.suppress` to prevent the receiver from being saved
+ during the given block.
+ ([Pull Request](https://github.com/rails/rails/pull/18910))
+
+* `belongs_to` will now trigger a validation error by default if the
+ association is not present. You can turn this off on a per-association basis
+ with `optional: true`.
+ ([Pull Request](https://github.com/rails/rails/pull/18937))
+
+* Added `config.active_record.dump_schemas` to configure the behavior of
+ `db:structure:dump`.
+ ([Pull Request](https://github.com/rails/rails/pull/19347))
+
+* Added `config.active_record.warn_on_records_fetched_greater_than` option.
+ ([Pull Request](https://github.com/rails/rails/pull/18846))
+
+* Added a native JSON data type support in MySQL.
+ ([Pull Request](https://github.com/rails/rails/pull/21110))
+
+* Added support for dropping indexes concurrently in PostgreSQL.
+ ([Pull Request](https://github.com/rails/rails/pull/21317))
+
+* Added `#views` and `#view_exists?` methods on connection adapters.
+ ([Pull Request](https://github.com/rails/rails/pull/21609))
+
+* Added `ActiveRecord::Base.ignored_columns` to make some columns
+ invisible from Active Record.
+ ([Pull Request](https://github.com/rails/rails/pull/21720))
+
+* Added `connection.data_sources` and `connection.data_source_exists?`.
+ These methods determine what relations can be used to back Active Record
+ models (usually tables and views).
+ ([Pull Request](https://github.com/rails/rails/pull/21715))
+
+* Allow fixtures files to set the model class in the YAML file itself.
+ ([Pull Request](https://github.com/rails/rails/pull/20574))
+
+* Added ability to default to `uuid` as primary key when generating database
+ migrations. ([Pull Request](https://github.com/rails/rails/pull/21762))
+
+* Added `ActiveRecord::Relation#left_joins` and
+ `ActiveRecord::Relation#left_outer_joins`.
+ ([Pull Request](https://github.com/rails/rails/pull/12071))
+
+* Added `after_{create,update,delete}_commit` callbacks.
+ ([Pull Request](https://github.com/rails/rails/pull/22516))
+
+* Version the API presented to migration classes, so we can change parameter
+ defaults without breaking existing migrations, or forcing them to be
+ rewritten through a deprecation cycle.
+ ([Pull Request](https://github.com/rails/rails/pull/21538))
+
+* `ApplicationRecord` is a new superclass for all app models, analogous to app
+ controllers subclassing `ApplicationController` instead of
+ `ActionController::Base`. This gives apps a single spot to configure app-wide
+ model behavior.
+ ([Pull Request](https://github.com/rails/rails/pull/22567))
+
+
+Active Model
+------------
+
+Please refer to the [Changelog][active-model] for detailed changes.
+
+### Removals
+
+* Removed deprecated `ActiveModel::Dirty#reset_#{attribute}` and
+ `ActiveModel::Dirty#reset_changes`.
+ ([Pull Request](https://github.com/rails/rails/commit/37175a24bd508e2983247ec5d011d57df836c743))
+
+* Removed XML serialization. This feature has been extracted into the
+ [activemodel-serializers-xml](https://github.com/rails/activemodel-serializers-xml) gem.
+ ([Pull Request](https://github.com/rails/rails/pull/21161))
+
+### Deprecations
+
+* Deprecated returning `false` as a way to halt Active Model and
+ `ActiveModel::Validations` callback chains. The recommended way is to
+ `throw(:abort)`. ([Pull Request](https://github.com/rails/rails/pull/17227))
+
+* Deprecated `ActiveModel::Errors#get`, `ActiveModel::Errors#set` and
+ `ActiveModel::Errors#[]=` methods that have inconsistent behavior.
+ ([Pull Request](https://github.com/rails/rails/pull/18634))
+
+* Deprecated the `:tokenizer` option for `validates_length_of`, in favor of
+ plain Ruby.
+ ([Pull Request](https://github.com/rails/rails/pull/19585))
+
+* Deprecated `ActiveModel::Errors#add_on_empty` and `ActiveModel::Errors#add_on_blank`
+ with no replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/18996))
+
+### Notable changes
+
+* Added `ActiveModel::Errors#details` to determine what validator has failed.
+ ([Pull Request](https://github.com/rails/rails/pull/18322))
+
+* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment`
+ allowing to use it for any object as an includable module.
+ ([Pull Request](https://github.com/rails/rails/pull/10776))
+
+* Added `ActiveModel::Dirty#[attr_name]_previously_changed?` and
+ `ActiveModel::Dirty#[attr_name]_previous_change` to improve access
+ to recorded changes after the model has been saved.
+ ([Pull Request](https://github.com/rails/rails/pull/19847))
+
+* Validate multiple contexts on `valid?` and `invalid?` at once.
+ ([Pull Request](https://github.com/rails/rails/pull/21069))
+
+
+Active Job
+-----------
+
+Please refer to the [Changelog][active-job] for detailed changes.
+
+### Notable changes
+
+* `ActiveJob::Base.deserialize` delegates to the job class. this allows jobs
+ to attach arbitrary metadata when they get serialized and read it back when
+ they get performed.
+ ([Pull Request](https://github.com/rails/rails/pull/18260))
+
+* A generated job now inherits from `app/jobs/application_job.rb` by default.
+ ([Pull Request](https://github.com/rails/rails/pull/19034))
+
+* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to
+ `ActiveJob::Base` as `provider_job_id`.
+ ([Pull Request](https://github.com/rails/rails/pull/20064),
+ [Pull Request](https://github.com/rails/rails/pull/20056))
+
+* Implement a simple `AsyncJob` processor and associated `AsyncAdapter` that
+ queue jobs to a `concurrent-ruby` thread pool.
+ ([Pull Request](https://github.com/rails/rails/pull/21257))
+
+
+Active Support
+--------------
+
+Please refer to the [Changelog][active-support] for detailed changes.
+
+### Removals
+
+* Removed deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`.
+ ([commit](https://github.com/rails/rails/commit/d6e06ea8275cdc3f126f926ed9b5349fde374b10))
+
+* Removed deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=`
+ and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`.
+ ([commit](https://github.com/rails/rails/commit/c8019c0611791b2716c6bed48ef8dcb177b7869c))
+
+* Removed deprecated `ActiveSupport::SafeBuffer#prepend`.
+ ([commit](https://github.com/rails/rails/commit/e1c8b9f688c56aaedac9466a4343df955b4a67ec))
+
+* Removed deprecated methods from `Kernel`. `silence_stderr`, `silence_stream`,
+ `capture` and `quietly`.
+ ([commit](https://github.com/rails/rails/commit/481e49c64f790e46f4aff3ed539ed227d2eb46cb))
+
+* Removed deprecated `active_support/core_ext/big_decimal/yaml_conversions`
+ file.
+ ([commit](https://github.com/rails/rails/commit/98ea19925d6db642731741c3b91bd085fac92241))
+
+* Removed deprecated methods `ActiveSupport::Cache::Store.instrument` and
+ `ActiveSupport::Cache::Store.instrument=`.
+ ([commit](https://github.com/rails/rails/commit/a3ce6ca30ed0e77496c63781af596b149687b6d7))
+
+* Removed deprecated `Class#superclass_delegating_accessor`.
+ Use `Class#class_attribute` instead.
+ ([Pull Request](https://github.com/rails/rails/pull/16938))
+
+* Removed deprecated `ThreadSafe::Cache`. Use `Concurrent::Map` instead.
+ ([Pull Request](https://github.com/rails/rails/pull/21679))
+
+### Deprecations
+
+* Deprecated `MissingSourceFile` in favor of `LoadError`.
+ ([commit](https://github.com/rails/rails/commit/734d97d2))
+
+* Deprecated `alias_method_chain` in favour of `Module#prepend` introduced in
+ Ruby 2.0.
+ ([Pull Request](https://github.com/rails/rails/pull/19434))
+
+* Deprecated `ActiveSupport::Concurrency::Latch` in favor of
+ `Concurrent::CountDownLatch` from concurrent-ruby.
+ ([Pull Request](https://github.com/rails/rails/pull/20866))
+
+* Deprecated `:prefix` option of `number_to_human_size` with no replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/21191))
+
+* Deprecated `Module#qualified_const_` in favour of the builtin
+ `Module#const_` methods.
+ ([Pull Request](https://github.com/rails/rails/pull/17845))
+
+* Deprecated passing string to define callback.
+ ([Pull Request](https://github.com/rails/rails/pull/22598))
+
+* Deprecated `ActiveSupport::Cache::Store#namespaced_key`,
+ `ActiveSupport::Cache::MemCachedStore#escape_key`, and
+ `ActiveSupport::Cache::FileStore#key_file_path`.
+ Use `normalize_key` instead.
+
+ Deprecated `ActiveSupport::Cache::LocaleCache#set_cache_value` in favor of `write_cache_value`.
+ ([Pull Request](https://github.com/rails/rails/pull/22215))
+
+### Notable changes
+
+* Added `#verified` and `#valid_message?` methods to
+ `ActiveSupport::MessageVerifier`.
+ ([Pull Request](https://github.com/rails/rails/pull/17727))
+
+* Changed the way in which callback chains can be halted. The preferred method
+ to halt a callback chain from now on is to explicitly `throw(:abort)`.
+ ([Pull Request](https://github.com/rails/rails/pull/17227))
+
+* New config option
+ `config.active_support.halt_callback_chains_on_return_false` to specify
+ whether ActiveRecord, ActiveModel and ActiveModel::Validations callback
+ chains can be halted by returning `false` in a 'before' callback.
+ ([Pull Request](https://github.com/rails/rails/pull/17227))
+
+* Changed the default test order from `:sorted` to `:random`.
+ ([commit](https://github.com/rails/rails/commit/5f777e4b5ee2e3e8e6fd0e2a208ec2a4d25a960d))
+
+* Added `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
+ `Time`, and `DateTime`.
+ ([Pull Request](https://github.com/rails/rails/pull/18335))
+
+* Added `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`,
+ and `DateTime`.
+ ([Pull Request](https://github.com/rails/rails/pull/18335))
+
+* Added `#prev_day` and `#next_day` counterparts to `#yesterday` and
+ `#tomorrow` for `Date`, `Time`, and `DateTime`.
+ ([Pull Request](httpshttps://github.com/rails/rails/pull/18335))
+
+* Added `SecureRandom.base58` for generation of random base58 strings.
+ ([commit](https://github.com/rails/rails/commit/b1093977110f18ae0cafe56c3d99fc22a7d54d1b))
+
+* Added `file_fixture` to `ActiveSupport::TestCase`.
+ It provides a simple mechanism to access sample files in your test cases.
+ ([Pull Request](https://github.com/rails/rails/pull/18658))
+
+* Added `#without` on `Enumerable` and `Array` to return a copy of an
+ enumerable without the specified elements.
+ ([Pull Request](https://github.com/rails/rails/pull/19157))
+
+* Added `ActiveSupport::ArrayInquirer` and `Array#inquiry`.
+ ([Pull Request](https://github.com/rails/rails/pull/18939))
+
+* Added `ActiveSupport::TimeZone#strptime` to allow parsing times as if
+ from a given timezone.
+ ([commit](https://github.com/rails/rails/commit/a5e507fa0b8180c3d97458a9b86c195e9857d8f6))
+
+* Added `Integer#positive?` and `Integer#negative?` query methods
+ in the vein of `Fixnum#zero?`.
+ ([commit](https://github.com/rails/rails/commit/e54277a45da3c86fecdfa930663d7692fd083daa))
+
+* Added a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
+ an `KeyError` if the value is `.blank?`.
+ ([Pull Request](https://github.com/rails/rails/pull/20208))
+
+* Added `Time.days_in_year` to return the number of days in the given year, or the
+ current year if no argument is provided.
+ ([commit](https://github.com/rails/rails/commit/2f4f4d2cf1e4c5a442459fc250daf66186d110fa))
+
+* Added an evented file watcher to asynchronously detect changes in the
+ application source code, routes, locales, etc.
+ ([Pull Request](https://github.com/rails/rails/pull/22254))
+
+* Added thread_m/cattr_accessor/reader/writer suite of methods for declaring
+ class and module variables that live per-thread.
+ ([Pull Request](https://github.com/rails/rails/pull/22630))
+
+
+Credits
+-------
+
+See the
+[full list of contributors to Rails](http://contributors.rubyonrails.org/) for
+the many people who spent many hours making Rails, the stable and robust
+framework it is. Kudos to all of them.
+
+[railties]: https://github.com/rails/rails/blob/5-0-stable/railties/CHANGELOG.md
+[action-pack]: https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md
+[action-view]: https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md
+[action-mailer]: https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md
+[active-record]: https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md
+[active-model]: https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md
+[active-support]: https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md
+[active-job]: https://github.com/rails/rails/blob/5-0-stable/activejob/CHANGELOG.md
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 7e43ba375a..f68f2d1faf 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -394,7 +394,7 @@ Rails sets up (for the CookieStore) a secret key used for signing the session da
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-# You can use `rake secret` to generate a secure secret key.
+# You can use `rails secret` to generate a secure secret key.
# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.
@@ -1118,7 +1118,7 @@ Rails default exception handling displays a "500 Server Error" message for all e
### The Default 500 and 404 Templates
-By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
+By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
### `rescue_from`
@@ -1150,7 +1150,7 @@ class ApplicationController < ActionController::Base
def user_not_authorized
flash[:error] = "You don't have access to this section."
- redirect_to :back
+ redirect_back(fallback_location: root_path)
end
end
@@ -1174,7 +1174,11 @@ end
WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development).
-NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed.
+NOTE: When running in the production environment, all
+`ActiveRecord::RecordNotFound` errors render the 404 error page. Unless you need
+a custom behavior you don't need to handle this.
+
+NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed.
Force HTTPS protocol
--------------------
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 4800cece82..cd2c13e8c1 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -170,7 +170,7 @@ First, let's create a simple `User` scaffold:
```bash
$ bin/rails generate scaffold user name email login
-$ bin/rake db:migrate
+$ bin/rails db:migrate
```
Now that we have a user model to play with, we will just edit the
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 4b0e9bff7c..543937f8e5 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -599,7 +599,7 @@ This would add something like "Process data files (0.34523)" to the log, which y
#### cache
-A method for caching fragments of a view rather than an entire action or page. This technique is useful caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `ActionController::Caching::Fragments` for more information.
+A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `ActionController::Caching::Fragments` for more information.
```erb
<% cache do %>
@@ -990,11 +990,11 @@ Returns `select` and `option` tags for the collection of existing return values
Example object structure for use with this method:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
belongs_to :author
end
-class Author < ActiveRecord::Base
+class Author < ApplicationRecord
has_many :articles
def name_with_initial
"#{first_name.first}. #{last_name}"
@@ -1026,11 +1026,11 @@ Returns `radio_button` tags for the collection of existing return values of `met
Example object structure for use with this method:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
belongs_to :author
end
-class Author < ActiveRecord::Base
+class Author < ApplicationRecord
has_many :articles
def name_with_initial
"#{first_name.first}. #{last_name}"
@@ -1062,11 +1062,11 @@ Returns `check_box` tags for the collection of existing return values of `method
Example object structure for use with this method:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
has_and_belongs_to_many :authors
end
-class Author < ActiveRecord::Base
+class Author < ApplicationRecord
has_and_belongs_to_many :articles
def name_with_initial
"#{first_name.first}. #{last_name}"
@@ -1099,12 +1099,12 @@ Returns a string of `option` tags, like `options_from_collection_for_select`, bu
Example object structure for use with this method:
```ruby
-class Continent < ActiveRecord::Base
+class Continent < ApplicationRecord
has_many :countries
# attribs: id, name
end
-class Country < ActiveRecord::Base
+class Country < ApplicationRecord
belongs_to :continent
# attribs: id, name, continent_id
end
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index e36c0f899f..76c13f0ea9 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -174,7 +174,7 @@ module YourApp
end
end
-# app/jobs/guests_cleanup.rb
+# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ActiveJob::Base
queue_as :low_priority
#....
@@ -197,7 +197,7 @@ module YourApp
end
end
-# app/jobs/guests_cleanup.rb
+# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ActiveJob::Base
queue_as :low_priority
#....
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 8f8256c983..c05e20aceb 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -435,7 +435,7 @@ the Active Model API.
```
```bash
-$ rake test
+$ rails test
Run options: --seed 14596
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index dafbe17bbd..fba89f9d13 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -132,10 +132,10 @@ Creating Active Record Models
-----------------------------
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:
+subclass the `ApplicationRecord` class and you're good to go:
```ruby
-class Product < ActiveRecord::Base
+class Product < ApplicationRecord
end
```
@@ -168,11 +168,12 @@ 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
-name that should be used:
+`ApplicationRecord` inherits from `ActiveRecord::Base`, which defines a
+number of helpful methods. You can use the `ActiveRecord::Base.table_name=`
+method to specify the table name that should be used:
```ruby
-class Product < ActiveRecord::Base
+class Product < ApplicationRecord
self.table_name = "my_products"
end
```
@@ -193,7 +194,7 @@ It's also possible to override the column that should be used as the table's
primary key using the `ActiveRecord::Base.primary_key=` method:
```ruby
-class Product < ActiveRecord::Base
+class Product < ApplicationRecord
self.primary_key = "product_id"
end
```
@@ -320,7 +321,7 @@ they raise the exception `ActiveRecord::RecordInvalid` if validation fails.
A quick example to illustrate:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
validates :name, presence: true
end
@@ -350,7 +351,7 @@ database that Active Record supports using `rake`. Here's a migration that
creates a table:
```ruby
-class CreatePublications < ActiveRecord::Migration
+class CreatePublications < ActiveRecord::Migration[5.0]
def change
create_table :publications do |t|
t.string :title
@@ -368,8 +369,8 @@ end
```
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`.
+provides rollback features. To actually create the table, you'd run `rails db:migrate`
+and to roll it back, `rails 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
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 13989a3b33..d95c6c0e78 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -31,7 +31,7 @@ Callbacks are methods that get called at certain moments of an object's life cyc
In order to use the available callbacks, you need to register them. You can implement the callbacks as ordinary methods and use a macro-style class method to register them as callbacks:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
validates :login, :email, presence: true
before_validation :ensure_login_has_a_value
@@ -48,7 +48,7 @@ end
The macro-style class methods can also receive a block. Consider using this style if the code inside your block is so short that it fits in a single line:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
validates :login, :email, presence: true
before_create do
@@ -60,7 +60,7 @@ end
Callbacks can also be registered to only fire on certain life cycle events:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
before_validation :normalize_name, on: :create
# :on takes an array as well
@@ -126,7 +126,7 @@ The `after_find` callback will be called whenever Active Record loads a record f
The `after_initialize` and `after_find` callbacks have no `before_*` counterparts, but they can be registered just like the other Active Record callbacks.
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
after_initialize do |user|
puts "You have initialized an object!"
end
@@ -151,7 +151,7 @@ You have initialized an object!
The `after_touch` callback will be called whenever an Active Record object is touched.
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
after_touch do |user|
puts "You have touched an object"
end
@@ -168,14 +168,14 @@ You have touched an object
It can be used along with `belongs_to`:
```ruby
-class Employee < ActiveRecord::Base
+class Employee < ApplicationRecord
belongs_to :company, touch: true
after_touch do
puts 'An Employee was touched'
end
end
-class Company < ActiveRecord::Base
+class Company < ApplicationRecord
has_many :employees
after_touch :log_when_employees_or_company_touched
@@ -266,11 +266,11 @@ Relational Callbacks
Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many articles. A user's articles should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Article` model:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
has_many :articles, dependent: :destroy
end
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
after_destroy :log_destroy_action
def log_destroy_action
@@ -297,7 +297,7 @@ As with validations, we can also make the calling of a callback method condition
You can associate the `:if` and `:unless` options with a symbol corresponding to the name of a predicate method that will get called right before the callback. When using the `:if` option, the callback won't be executed if the predicate method returns false; when using the `:unless` option, the callback won't be executed if the predicate method returns true. This is the most common option. Using this form of registration it is also possible to register several different predicates that should be called to check if the callback should be executed.
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
before_save :normalize_card_number, if: :paid_with_card?
end
```
@@ -307,7 +307,7 @@ end
You can also use a string that will be evaluated using `eval` and hence needs to contain valid Ruby code. You should use this option only when the string represents a really short condition:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
before_save :normalize_card_number, if: "paid_with_card?"
end
```
@@ -317,7 +317,7 @@ end
Finally, it is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
before_save :normalize_card_number,
if: Proc.new { |order| order.paid_with_card? }
end
@@ -328,7 +328,7 @@ end
When writing conditional callbacks, it is possible to mix both `:if` and `:unless` in the same callback declaration:
```ruby
-class Comment < ActiveRecord::Base
+class Comment < ApplicationRecord
after_create :send_email_to_author, if: :author_wants_emails?,
unless: Proc.new { |comment| comment.article.ignore_comments? }
end
@@ -354,7 +354,7 @@ end
When declared inside a class, as above, the callback methods will receive the model object as a parameter. We can now use the callback class in the model:
```ruby
-class PictureFile < ActiveRecord::Base
+class PictureFile < ApplicationRecord
after_destroy PictureFileCallbacks.new
end
```
@@ -374,7 +374,7 @@ end
If the callback method is declared this way, it won't be necessary to instantiate a `PictureFileCallbacks` object.
```ruby
-class PictureFile < ActiveRecord::Base
+class PictureFile < ApplicationRecord
after_destroy PictureFileCallbacks
end
```
@@ -398,7 +398,7 @@ end
By using the `after_commit` callback we can account for this case.
```ruby
-class PictureFile < ActiveRecord::Base
+class PictureFile < ApplicationRecord
after_commit :delete_picture_file_from_disk, on: [:destroy]
def delete_picture_file_from_disk
@@ -412,4 +412,23 @@ end
NOTE: the `:on` option specifies when a callback will be fired. If you
don't supply the `:on` option the callback will fire for every action.
+Since using `after_commit` callback only on create, update or delete is
+common, there are aliases for those operations:
+
+* `after_create_commit`
+* `after_update_commit`
+* `after_destroy_commit`
+
+```ruby
+class PictureFile < ApplicationRecord
+ after_destroy_commit :delete_picture_file_from_disk
+
+ def delete_picture_file_from_disk
+ if File.exist?(filepath)
+ File.delete(filepath)
+ end
+ end
+end
+```
+
WARNING. The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback.
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 5aa5dc4f60..a4a23395fb 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -35,7 +35,7 @@ history to the latest version. Active Record will also update your
Here's an example of a migration:
```ruby
-class CreateProducts < ActiveRecord::Migration
+class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
@@ -72,7 +72,7 @@ If you wish for a migration to do something that Active Record doesn't know how
to reverse, you can use `reversible`:
```ruby
-class ChangeProductsPrice < ActiveRecord::Migration
+class ChangeProductsPrice < ActiveRecord::Migration[5.0]
def change
reversible do |dir|
change_table :products do |t|
@@ -87,7 +87,7 @@ end
Alternatively, you can use `up` and `down` instead of `change`:
```ruby
-class ChangeProductsPrice < ActiveRecord::Migration
+class ChangeProductsPrice < ActiveRecord::Migration[5.0]
def up
change_table :products do |t|
t.change :price, :string
@@ -129,7 +129,7 @@ $ bin/rails generate migration AddPartNumberToProducts
This will create an empty but appropriately named migration:
```ruby
-class AddPartNumberToProducts < ActiveRecord::Migration
+class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
end
end
@@ -146,7 +146,7 @@ $ bin/rails generate migration AddPartNumberToProducts part_number:string
will generate
```ruby
-class AddPartNumberToProducts < ActiveRecord::Migration
+class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :part_number, :string
end
@@ -162,7 +162,7 @@ $ bin/rails generate migration AddPartNumberToProducts part_number:string:index
will generate
```ruby
-class AddPartNumberToProducts < ActiveRecord::Migration
+class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :part_number, :string
add_index :products, :part_number
@@ -180,7 +180,7 @@ $ bin/rails generate migration RemovePartNumberFromProducts part_number:string
generates
```ruby
-class RemovePartNumberFromProducts < ActiveRecord::Migration
+class RemovePartNumberFromProducts < ActiveRecord::Migration[5.0]
def change
remove_column :products, :part_number, :string
end
@@ -196,7 +196,7 @@ $ bin/rails generate migration AddDetailsToProducts part_number:string price:dec
generates
```ruby
-class AddDetailsToProducts < ActiveRecord::Migration
+class AddDetailsToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
@@ -215,7 +215,7 @@ $ bin/rails generate migration CreateProducts name:string part_number:string
generates
```ruby
-class CreateProducts < ActiveRecord::Migration
+class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
@@ -239,7 +239,7 @@ $ bin/rails generate migration AddUserRefToProducts user:references
generates
```ruby
-class AddUserRefToProducts < ActiveRecord::Migration
+class AddUserRefToProducts < ActiveRecord::Migration[5.0]
def change
add_reference :products, :user, index: true, foreign_key: true
end
@@ -257,7 +257,7 @@ $ bin/rails g migration CreateJoinTableCustomerProduct customer product
will produce the following migration:
```ruby
-class CreateJoinTableCustomerProduct < ActiveRecord::Migration
+class CreateJoinTableCustomerProduct < ActiveRecord::Migration[5.0]
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
@@ -281,7 +281,7 @@ $ bin/rails generate model Product name:string description:text
will create a migration that looks like this
```ruby
-class CreateProducts < ActiveRecord::Migration
+class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
@@ -309,7 +309,7 @@ $ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplie
will produce a migration that looks like this
```ruby
-class AddDetailsToProducts < ActiveRecord::Migration
+class AddDetailsToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true, index: true
@@ -563,7 +563,7 @@ to reverse. You can use `reversible` to specify what to do when running a
migration and what else to do when reverting it. For example:
```ruby
-class ExampleMigration < ActiveRecord::Migration
+class ExampleMigration < ActiveRecord::Migration[5.0]
def change
create_table :distributors do |t|
t.string :zipcode
@@ -616,7 +616,7 @@ is wise to perform the transformations in precisely the reverse order they were
made in the `up` method. The example in the `reversible` section is equivalent to:
```ruby
-class ExampleMigration < ActiveRecord::Migration
+class ExampleMigration < ActiveRecord::Migration[5.0]
def up
create_table :distributors do |t|
t.string :zipcode
@@ -659,7 +659,7 @@ You can use Active Record's ability to rollback migrations using the `revert` me
```ruby
require_relative '20121212123456_example_migration'
-class FixupExampleMigration < ActiveRecord::Migration
+class FixupExampleMigration < ActiveRecord::Migration[5.0]
def change
revert ExampleMigration
@@ -677,7 +677,7 @@ is later decided it would be best to use Active Record validations,
in place of the `CHECK` constraint, to verify the zipcode.
```ruby
-class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration
+class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration[5.0]
def change
revert do
# copy-pasted code from ExampleMigration
@@ -720,7 +720,7 @@ Running Migrations
Rails provides a set of Rake tasks to run certain sets of migrations.
The very first migration related Rake task you will use will probably be
-`rake db:migrate`. In its most basic form it just runs the `change` or `up`
+`rails db:migrate`. In its most basic form it just runs the `change` or `up`
method for all the migrations that have not yet been run. If there are
no such migrations, it exits. It will run these migrations in order based
on the date of the migration.
@@ -734,7 +734,7 @@ is the numerical prefix on the migration's filename. For example, to migrate
to version 20080906120000 run:
```bash
-$ bin/rake db:migrate VERSION=20080906120000
+$ bin/rails db:migrate VERSION=20080906120000
```
If version 20080906120000 is greater than the current version (i.e., it is
@@ -751,7 +751,7 @@ mistake in it and wish to correct it. Rather than tracking down the version
number associated with the previous migration you can run:
```bash
-$ bin/rake db:rollback
+$ bin/rails db:rollback
```
This will rollback the latest migration, either by reverting the `change`
@@ -759,7 +759,7 @@ method or by running the `down` method. If you need to undo
several migrations you can provide a `STEP` parameter:
```bash
-$ bin/rake db:rollback STEP=3
+$ bin/rails db:rollback STEP=3
```
will revert the last 3 migrations.
@@ -769,7 +769,7 @@ back up again. As with the `db:rollback` task, you can use the `STEP` parameter
if you need to go more than one version back, for example:
```bash
-$ bin/rake db:migrate:redo STEP=3
+$ bin/rails db:migrate:redo STEP=3
```
Neither of these Rake tasks do anything you could not do with `db:migrate`. They
@@ -778,17 +778,17 @@ version to migrate to.
### Setup the Database
-The `rake db:setup` task will create the database, load the schema and initialize
+The `rails db:setup` task will create the database, load the schema and initialize
it with the seed data.
### Resetting the Database
-The `rake db:reset` task will drop the database and set it up again. This is
+The `rails db:reset` task will drop the database and set it up again. This is
functionally equivalent to `rake db:drop db:setup`.
NOTE: This is not the same as running all the migrations. It will only use the
contents of the current `db/schema.rb` or `db/structure.sql` file. If a migration can't be rolled back,
-`rake db:reset` may not help you. To find out more about dumping the schema see
+`rails db:reset` may not help you. To find out more about dumping the schema see
[Schema Dumping and You](#schema-dumping-and-you) section.
### Running Specific Migrations
@@ -799,7 +799,7 @@ the corresponding migration will have its `change`, `up` or `down` method
invoked, for example:
```bash
-$ bin/rake db:migrate:up VERSION=20080906120000
+$ bin/rails db:migrate:up VERSION=20080906120000
```
will run the 20080906120000 migration by running the `change` method (or the
@@ -815,7 +815,7 @@ To run migrations against another environment you can specify it using the
migrations against the `test` environment you could run:
```bash
-$ bin/rake db:migrate RAILS_ENV=test
+$ bin/rails db:migrate RAILS_ENV=test
```
### Changing the Output of Running Migrations
@@ -841,7 +841,7 @@ Several methods are provided in migrations that allow you to control all this:
For example, this migration:
```ruby
-class CreateProducts < ActiveRecord::Migration
+class CreateProducts < ActiveRecord::Migration[5.0]
def change
suppress_messages do
create_table :products do |t|
@@ -876,7 +876,7 @@ generates the following output
== CreateProducts: migrated (10.0054s) =======================================
```
-If you want Active Record to not output anything, then running `rake db:migrate
+If you want Active Record to not output anything, then running `rails db:migrate
VERBOSE=false` will suppress all output.
Changing Existing Migrations
@@ -885,9 +885,9 @@ Changing Existing Migrations
Occasionally you will make a mistake when writing a migration. If you have
already run the migration then you cannot just edit the migration and run the
migration again: Rails thinks it has already run the migration and so will do
-nothing when you run `rake db:migrate`. You must rollback the migration (for
+nothing when you run `rails db:migrate`. You must rollback the migration (for
example with `rake db:rollback`), edit your migration and then run
-`rake db:migrate` to run the corrected version.
+`rails db:migrate` to run the corrected version.
In general, editing existing migrations is not a good idea. You will be
creating extra work for yourself and your co-workers and cause major headaches
@@ -969,7 +969,7 @@ this, then you should set the schema format to `:sql`.
Instead of using Active Record's schema dumper, the database's structure will
be dumped using a tool specific to the database (via the `db:structure:dump`
-Rake task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump`
+rails task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump`
utility is used. For MySQL, this file will contain the output of
`SHOW CREATE TABLE` for the various tables.
@@ -1015,7 +1015,7 @@ to add or modify data. This is useful in an existing database that can't be dest
and recreated, such as a production database.
```ruby
-class AddInitialProducts < ActiveRecord::Migration
+class AddInitialProducts < ActiveRecord::Migration[5.0]
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
@@ -1032,7 +1032,7 @@ To add initial data after a database is created, Rails has a built-in
'seeds' feature that makes the process quick and easy. This is especially
useful when reloading the database frequently in development and test environments.
It's easy to get started with this feature: just fill up `db/seeds.rb` with some
-Ruby code, and run `rake db:seed`:
+Ruby code, and run `rails db:seed`:
```ruby
5.times do |i|
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 742db7be32..68c6a77882 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -39,7 +39,7 @@ create_table :documents do |t|
end
# app/models/document.rb
-class Document < ActiveRecord::Base
+class Document < ApplicationRecord
end
# Usage
@@ -63,7 +63,7 @@ add_index :books, :tags, using: 'gin'
add_index :books, :ratings, using: 'gin'
# app/models/book.rb
-class Book < ActiveRecord::Base
+class Book < ApplicationRecord
end
# Usage
@@ -84,6 +84,7 @@ Book.where("array_length(ratings, 1) >= 3")
### Hstore
* [type definition](http://www.postgresql.org/docs/current/static/hstore.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/hstore.html#AEN167712)
NOTE: You need to enable the `hstore` extension to use hstore.
@@ -97,7 +98,7 @@ ActiveRecord::Schema.define do
end
# app/models/profile.rb
-class Profile < ActiveRecord::Base
+class Profile < ApplicationRecord
end
# Usage
@@ -108,6 +109,9 @@ profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
+
+Profile.where("settings->'color' = ?", "yellow")
+#=> #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>
```
### JSON
@@ -122,7 +126,7 @@ create_table :events do |t|
end
# app/models/event.rb
-class Event < ActiveRecord::Base
+class Event < ApplicationRecord
end
# Usage
@@ -150,7 +154,7 @@ create_table :events do |t|
end
# app/models/event.rb
-class Event < ActiveRecord::Base
+class Event < ApplicationRecord
end
# Usage
@@ -200,7 +204,7 @@ create_table :contacts do |t|
end
# app/models/contact.rb
-class Contact < ActiveRecord::Base
+class Contact < ApplicationRecord
end
# Usage
@@ -239,7 +243,7 @@ def down
end
# app/models/article.rb
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
end
# Usage
@@ -294,7 +298,7 @@ create_table :revisions do |t|
end
# app/models/revision.rb
-class Revision < ActiveRecord::Base
+class Revision < ApplicationRecord
end
# Usage
@@ -317,12 +321,12 @@ create_table :comments, id: :uuid, default: 'gen_random_uuid()' do |t|
end
# app/models/post.rb
-class Post < ActiveRecord::Base
+class Post < ApplicationRecord
has_many :comments
end
# app/models/comment.rb
-class Comment < ActiveRecord::Base
+class Comment < ApplicationRecord
belongs_to :post
end
```
@@ -341,7 +345,7 @@ create_table :users, force: true do |t|
end
# app/models/device.rb
-class User < ActiveRecord::Base
+class User < ApplicationRecord
end
# Usage
@@ -370,7 +374,7 @@ create_table(:devices, force: true) do |t|
end
# app/models/device.rb
-class Device < ActiveRecord::Base
+class Device < ApplicationRecord
end
# Usage
@@ -410,7 +414,7 @@ create_table :devices, id: :uuid, default: 'gen_random_uuid()' do |t|
end
# app/models/device.rb
-class Device < ActiveRecord::Base
+class Device < ApplicationRecord
end
# Usage
@@ -434,7 +438,7 @@ end
execute "CREATE INDEX documents_idx ON documents USING gin(to_tsvector('english', title || ' ' || body));"
# app/models/document.rb
-class Document < ActiveRecord::Base
+class Document < ApplicationRecord
end
# Usage
@@ -484,7 +488,7 @@ CREATE VIEW articles AS
SQL
# app/models/article.rb
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
self.primary_key = "id"
def archive!
update_attribute :archived, true
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index ec31fa9d67..fd5399baec 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -25,7 +25,7 @@ Code examples throughout this guide will refer to one or more of the following m
TIP: All of the following models use `id` as the primary key, unless specified otherwise.
```ruby
-class Client < ActiveRecord::Base
+class Client < ApplicationRecord
has_one :address
has_many :orders
has_and_belongs_to_many :roles
@@ -33,19 +33,19 @@ end
```
```ruby
-class Address < ActiveRecord::Base
+class Address < ApplicationRecord
belongs_to :client
end
```
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :client, counter_cache: true
end
```
```ruby
-class Role < ActiveRecord::Base
+class Role < ApplicationRecord
has_and_belongs_to_many :clients
end
```
@@ -59,7 +59,7 @@ To retrieve objects from the database, Active Record provides several finder met
The methods are:
-* `bind`
+* `find`
* `create_with`
* `distinct`
* `eager_load`
@@ -740,7 +740,7 @@ SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20
The `reorder` method overrides the default scope order. For example:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
has_many :comments, -> { order('posted_at DESC') }
end
@@ -889,7 +889,7 @@ This behavior can be turned off by setting `ActiveRecord::Base.lock_optimistical
To override the name of the `lock_version` column, `ActiveRecord::Base` provides a class attribute called `locking_column`:
```ruby
-class Client < ActiveRecord::Base
+class Client < ApplicationRecord
self.locking_column = :lock_client_column
end
```
@@ -970,26 +970,26 @@ Active Record lets you use the names of the [associations](association_basics.ht
For example, consider the following `Category`, `Article`, `Comment`, `Guest` and `Tag` models:
```ruby
-class Category < ActiveRecord::Base
+class Category < ApplicationRecord
has_many :articles
end
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
belongs_to :category
has_many :comments
has_many :tags
end
-class Comment < ActiveRecord::Base
+class Comment < ApplicationRecord
belongs_to :article
has_one :guest
end
-class Guest < ActiveRecord::Base
+class Guest < ApplicationRecord
belongs_to :comment
end
-class Tag < ActiveRecord::Base
+class Tag < ApplicationRecord
belongs_to :article
end
```
@@ -1199,7 +1199,7 @@ Scoping allows you to specify commonly-used queries which can be referenced as m
To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
scope :published, -> { where(published: true) }
end
```
@@ -1207,7 +1207,7 @@ end
This is exactly the same as defining a class method, and which you use is a matter of personal preference:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
def self.published
where(published: true)
end
@@ -1217,7 +1217,7 @@ end
Scopes are also chainable within scopes:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
scope :published, -> { where(published: true) }
scope :published_and_commented, -> { published.where("comments_count > 0") }
end
@@ -1241,7 +1241,7 @@ category.articles.published # => [published articles belonging to this category]
Your scope can take arguments:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
scope :created_before, ->(time) { where("created_at < ?", time) }
end
```
@@ -1255,7 +1255,7 @@ Article.created_before(Time.zone.now)
However, this is just duplicating the functionality that would be provided to you by a class method.
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
def self.created_before(time)
where("created_at < ?", time)
end
@@ -1274,7 +1274,7 @@ If we wish for a scope to be applied across all queries to the model we can use
`default_scope` method within the model itself.
```ruby
-class Client < ActiveRecord::Base
+class Client < ApplicationRecord
default_scope { where("removed_at IS NULL") }
end
```
@@ -1290,7 +1290,7 @@ If you need to do more complex things with a default scope, you can alternativel
define it as a class method:
```ruby
-class Client < ActiveRecord::Base
+class Client < ApplicationRecord
def self.default_scope
# Should return an ActiveRecord::Relation.
end
@@ -1301,7 +1301,7 @@ NOTE: The `default_scope` is also applied while creating/building a record.
It is not applied while updating a record. E.g.:
```ruby
-class Client < ActiveRecord::Base
+class Client < ApplicationRecord
default_scope { where(active: true) }
end
@@ -1314,7 +1314,7 @@ Client.unscoped.new # => #<Client id: nil, active: nil>
Just like `where` clauses scopes are merged using `AND` conditions.
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
scope :active, -> { where state: 'active' }
scope :inactive, -> { where state: 'inactive' }
end
@@ -1343,7 +1343,7 @@ One important caveat is that `default_scope` will be prepended in
`scope` and `where` conditions.
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
default_scope { where state: 'pending' }
scope :active, -> { where state: 'active' }
scope :inactive, -> { where state: 'inactive' }
@@ -1374,8 +1374,15 @@ Client.unscoped.load
This method removes all scoping and will do a normal query on the table.
-Note that chaining `unscoped` with a `scope` does not work. In these cases, it is
-recommended that you use the block form of `unscoped`:
+```ruby
+Client.unscoped.all
+# SELECT "clients".* FROM "clients"
+
+Client.where(published: false).unscoped.all
+# SELECT "clients".* FROM "clients"
+```
+
+`unscoped` can also accept a block.
```ruby
Client.unscoped {
@@ -1392,6 +1399,36 @@ You can specify an exclamation point (`!`) on the end of the dynamic finders to
If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`.
+Enums
+-----
+
+The `enum` macro maps an integer column to a set of possible values.
+
+```ruby
+class Book < ApplicationRecord
+ enum availability: [:available, :unavailable]
+end
+```
+
+This will automatically create the corresponding [scopes](#scopes) to query the
+model. Methods to transition between states and query the current state are also
+added.
+
+```ruby
+# Both examples below query just available books.
+Book.available
+# or
+Book.where(availability: :available)
+
+book = Book.new(availability: :available)
+book.available? # => true
+book.unavailable! # => true
+book.available? # => false
+```
+
+Read the full documentation about enums
+[in the Rails API docs](http://api.rubyonrails.org/classes/ActiveRecord/Enum.html).
+
Understanding The Method Chaining
---------------------------------
@@ -1620,7 +1657,7 @@ a large or often-running query. However, any model method overrides will
not be available. For example:
```ruby
-class Client < ActiveRecord::Base
+class Client < ApplicationRecord
def name
"I am #{super}"
end
@@ -1655,7 +1692,7 @@ Person.ids
```
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
self.primary_key = "person_id"
end
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index fe42cec158..dd7adf09a2 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -20,7 +20,7 @@ Validations Overview
Here's an example of a very simple validation:
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true
end
@@ -80,7 +80,7 @@ method to determine whether an object is already in the database or not.
Consider the following simple Active Record class:
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
end
```
@@ -157,7 +157,7 @@ and returns true if no errors were found in the object, and false otherwise.
As you saw above:
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true
end
@@ -175,7 +175,7 @@ even if it's technically invalid, because validations are automatically run
only when the object is saved, such as with the `create` or `save` methods.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true
end
@@ -221,7 +221,7 @@ it doesn't verify the validity of the object as a whole. It only checks to see
whether there are errors found on an individual attribute of the object.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true
end
@@ -239,7 +239,7 @@ To check which validations failed on an invalid attribute, you can use
key to get the symbol of the validator:
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true
end
@@ -285,7 +285,7 @@ the field does exist in your database, the `accept` option must be set to
`true` or else the validation will not run.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :terms_of_service, acceptance: true
end
```
@@ -297,7 +297,7 @@ It can receive an `:accept` option, which determines the value that will be
considered acceptance. It defaults to "1" and can be easily changed.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :terms_of_service, acceptance: { accept: 'yes' }
end
```
@@ -309,7 +309,7 @@ and they also need to be validated. When you try to save your object, `valid?`
will be called upon each one of the associated objects.
```ruby
-class Library < ActiveRecord::Base
+class Library < ApplicationRecord
has_many :books
validates_associated :books
end
@@ -332,7 +332,7 @@ or a password. This validation creates a virtual attribute whose name is the
name of the field that has to be confirmed with "_confirmation" appended.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :email, confirmation: true
end
```
@@ -349,7 +349,7 @@ confirmation, make sure to add a presence check for the confirmation attribute
(we'll take a look at `presence` later on in this guide):
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true
end
@@ -360,7 +360,7 @@ confirmation constraint will be case sensitive or not. This option defaults to
true.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :email, confirmation: { case_sensitive: false }
end
```
@@ -373,7 +373,7 @@ This helper validates that the attributes' values are not included in a given
set. In fact, this set can be any enumerable object.
```ruby
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value} is reserved." }
end
@@ -393,7 +393,7 @@ This helper validates the attributes' values by testing whether they match a
given regular expression, which is specified using the `:with` option.
```ruby
-class Product < ActiveRecord::Base
+class Product < ApplicationRecord
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
message: "only allows letters" }
end
@@ -409,7 +409,7 @@ This helper validates that the attributes' values are included in a given set.
In fact, this set can be any enumerable object.
```ruby
-class Coffee < ActiveRecord::Base
+class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end
@@ -428,7 +428,7 @@ This helper validates the length of the attributes' values. It provides a
variety of options, so you can specify length constraints in different ways:
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, length: { minimum: 2 }
validates :bio, length: { maximum: 500 }
validates :password, length: { in: 6..20 }
@@ -451,7 +451,7 @@ number corresponding to the length constraint being used. You can still use the
`:message` option to specify an error message.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :bio, length: { maximum: 1000,
too_long: "%{count} characters is the maximum allowed" }
end
@@ -483,7 +483,7 @@ WARNING. Note that the regular expression above allows a trailing newline
character.
```ruby
-class Player < ActiveRecord::Base
+class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
@@ -521,7 +521,7 @@ This helper validates that the specified attributes are not empty. It uses the
is, a string that is either empty or consists of whitespace.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, :login, :email, presence: true
end
```
@@ -531,7 +531,7 @@ whether the associated object itself is present, and not the foreign key used
to map the association.
```ruby
-class LineItem < ActiveRecord::Base
+class LineItem < ApplicationRecord
belongs_to :order
validates :order, presence: true
end
@@ -541,7 +541,7 @@ In order to validate associated records whose presence is required, you must
specify the `:inverse_of` option for the association:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
```
@@ -568,7 +568,7 @@ This helper validates that the specified attributes are absent. It uses the
is, a string that is either empty or consists of whitespace.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, :login, :email, absence: true
end
```
@@ -578,7 +578,7 @@ whether the associated object itself is absent, and not the foreign key used
to map the association.
```ruby
-class LineItem < ActiveRecord::Base
+class LineItem < ApplicationRecord
belongs_to :order
validates :order, absence: true
end
@@ -588,7 +588,7 @@ 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
+class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
```
@@ -611,7 +611,7 @@ with the same value for a column that you intend to be unique. To avoid that,
you must create a unique index on that column in your database.
```ruby
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
validates :email, uniqueness: true
end
```
@@ -623,7 +623,7 @@ There is a `:scope` option that you can use to specify one or more attributes th
are used to limit the uniqueness check:
```ruby
-class Holiday < ActiveRecord::Base
+class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year,
message: "should happen once per year" }
end
@@ -635,7 +635,7 @@ uniqueness constraint will be case sensitive or not. This option defaults to
true.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
```
@@ -658,7 +658,7 @@ class GoodnessValidator < ActiveModel::Validator
end
end
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates_with GoodnessValidator
end
```
@@ -686,7 +686,7 @@ class GoodnessValidator < ActiveModel::Validator
end
end
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates_with GoodnessValidator, fields: [:first_name, :last_name]
end
```
@@ -699,7 +699,7 @@ If your validator is complex enough that you want instance variables, you can
easily use a plain old Ruby object instead:
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validate do |person|
GoodnessValidator.new(person).validate
end
@@ -728,7 +728,7 @@ passed to `validates_each` will be tested against it. In the following example,
we don't want names and surnames to begin with lower case.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/
end
@@ -751,7 +751,7 @@ The `:allow_nil` option skips the validation when the value being validated is
`nil`.
```ruby
-class Coffee < ActiveRecord::Base
+class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }, allow_nil: true
end
@@ -764,7 +764,7 @@ will let validation pass if the attribute's value is `blank?`, like `nil` or an
empty string for example.
```ruby
-class Topic < ActiveRecord::Base
+class Topic < ApplicationRecord
validates :title, length: { is: 5 }, allow_blank: true
end
@@ -777,7 +777,36 @@ Topic.create(title: nil).valid? # => true
As you've already seen, the `:message` option lets you specify the message that
will be added to the `errors` collection when validation fails. When this
option is not used, Active Record will use the respective default error message
-for each validation helper.
+for each validation helper. The `:message` option accepts a `String` or `Proc`.
+
+A `String` `:message` value can optionally contain any/all of `%{value}`,
+`%{attribute}`, and `%{model}` which will be dynamically replaced when
+validation fails.
+
+A `Proc` `:message` value is given two arguments: a message key for i18n, and
+a hash with `:model`, `:attribute`, and `:value` key-value pairs.
+
+```ruby
+class Person < ApplicationRecord
+ # Hard-coded message
+ validates :name, presence: { message: "must be given please" }
+
+ # Message with dynamic attribute value. %{value} will be replaced with
+ # the actual value of the attribute. %{attribute} and %{model} also
+ # available.
+ validates :age, numericality: { message: "%{value} seems wrong" }
+
+ # Proc
+ validates :username,
+ uniqueness: {
+ # key = "activerecord.errors.models.person.attributes.username.taken"
+ # data = { model: "Person", attribute: "Username", value: <username> }
+ message: ->(key, data) do
+ "#{data[:value]} taken! Try again #{Time.zone.tomorrow}"
+ end
+ }
+end
+```
### `:on`
@@ -789,7 +818,7 @@ new record is created or `on: :update` to run the validation only when a record
is updated.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
# it will be possible to update email with a duplicated value
validates :email, uniqueness: true, on: :create
@@ -808,7 +837,7 @@ You can also specify validations to be strict and raise
`ActiveModel::StrictValidationFailed` when the object is invalid.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: { strict: true }
end
@@ -818,7 +847,7 @@ Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank
There is also the ability to pass a custom exception to the `:strict` option.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
@@ -842,7 +871,7 @@ to the name of a method that will get called right before validation happens.
This is the most commonly used option.
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
@@ -858,7 +887,7 @@ contain valid Ruby code. You should use this option only when the string
represents a really short condition.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :surname, presence: true, if: "name.nil?"
end
```
@@ -871,7 +900,7 @@ inline condition instead of a separate method. This option is best suited for
one-liners.
```ruby
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
validates :password, confirmation: true,
unless: Proc.new { |a| a.password.blank? }
end
@@ -883,7 +912,7 @@ Sometimes it is useful to have multiple validations use one condition. It can
be easily achieved using `with_options`.
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
with_options if: :is_admin? do |admin|
admin.validates :password, length: { minimum: 10 }
admin.validates :email, presence: true
@@ -901,7 +930,7 @@ should happen, an `Array` can be used. Moreover, you can apply both `:if` and
`:unless` to the same validation.
```ruby
-class Computer < ActiveRecord::Base
+class Computer < ApplicationRecord
validates :mouse, presence: true,
if: ["market.retail?", :desktop?],
unless: Proc.new { |c| c.trackpad.present? }
@@ -955,7 +984,7 @@ class EmailValidator < ActiveModel::EachValidator
end
end
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :email, presence: true, email: true
end
```
@@ -979,7 +1008,7 @@ so your custom validation methods should add errors to it when you
wish validation to fail:
```ruby
-class Invoice < ActiveRecord::Base
+class Invoice < ApplicationRecord
validate :expiration_date_cannot_be_in_the_past,
:discount_cannot_be_greater_than_total_value
@@ -1003,7 +1032,7 @@ custom validations by giving an `:on` option to the `validate` method,
with either: `:create` or `:update`.
```ruby
-class Invoice < ActiveRecord::Base
+class Invoice < ApplicationRecord
validate :active_customer, on: :create
def active_customer
@@ -1024,7 +1053,7 @@ The following is a list of the most commonly used methods. Please refer to the `
Returns an instance of the class `ActiveModel::Errors` containing all errors. Each key is the attribute name and the value is an array of strings with all errors.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
@@ -1043,7 +1072,7 @@ person.errors.messages # => {}
`errors[]` is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
@@ -1068,7 +1097,7 @@ The `add` method lets you add an error message related to a particular attribute
The `errors.full_messages` method (or its equivalent, `errors.to_a`) returns the error messages in a user-friendly format, with the capitalized attribute name prepended to each message, as shown in the examples below.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
def a_method_used_for_validation_purposes
errors.add(:name, "cannot contain the characters !@#%*()_-+=")
end
@@ -1086,7 +1115,7 @@ person.errors.full_messages
An equivalent to `errors#add` is to use `<<` to append a message to the `errors.messages` array for an attribute:
```ruby
- class Person < ActiveRecord::Base
+ class Person < ApplicationRecord
def a_method_used_for_validation_purposes
errors.messages[:name] << "cannot contain the characters !@#%*()_-+="
end
@@ -1107,7 +1136,7 @@ You can specify a validator type to the returned error details hash using the
`errors.add` method.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
def a_method_used_for_validation_purposes
errors.add(:name, :invalid_characters)
end
@@ -1123,7 +1152,7 @@ To improve the error details to contain the unallowed characters set for instanc
you can pass additional keys to `errors.add`.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
def a_method_used_for_validation_purposes
errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=")
end
@@ -1143,7 +1172,7 @@ validator type.
You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
def a_method_used_for_validation_purposes
errors[:base] << "This person is invalid because ..."
end
@@ -1155,7 +1184,7 @@ end
The `clear` method is used when you intentionally want to clear all the messages in the `errors` collection. Of course, calling `errors.clear` upon an invalid object won't actually make it valid: the `errors` collection will now be empty, but the next time you call `valid?` or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the `errors` collection will be filled again.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
@@ -1178,7 +1207,7 @@ p.errors[:name]
The `size` method returns the total number of error messages for the object.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 181dca4b71..0aca6db9b6 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -397,7 +397,7 @@ The method `with_options` provides a way to factor out common options in a serie
Given a default options hash, `with_options` yields a proxy object to a block. Within the block, methods called on the proxy are forwarded to the receiver with their options merged. For example, you get rid of the duplication in:
```ruby
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
has_many :customers, dependent: :destroy
has_many :products, dependent: :destroy
has_many :invoices, dependent: :destroy
@@ -408,7 +408,7 @@ end
this way:
```ruby
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
with_options dependent: :destroy do |assoc|
assoc.has_many :customers
assoc.has_many :products
@@ -517,17 +517,17 @@ Extensions to `Module`
Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_.
-For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionController::TestCase#process` this way in `test/test_helper.rb`:
+For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionDispatch::IntegrationTest#process` this way in `test/test_helper.rb`:
```ruby
-ActionController::TestCase.class_eval do
+ActionDispatch::IntegrationTest.class_eval do
# save a reference to the original process method
alias_method :original_process, :process
# now redefine process and delegate to original_process
- def process(action, params=nil, session=nil, flash=nil, http_method='GET')
+ def process('GET', path, params: nil, headers: nil, env: nil, xhr: false)
params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
- original_process(action, params, session, flash, http_method)
+ original_process('GET', path, params: params)
end
end
```
@@ -537,10 +537,10 @@ That's the method `get`, `post`, etc., delegate the work to.
That technique has a risk, it could be the case that `:original_process` was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about:
```ruby
-ActionController::TestCase.class_eval do
+ActionDispatch::IntegrationTest.class_eval do
def process_with_stringified_params(...)
params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
- process_without_stringified_params(action, params, session, flash, http_method)
+ process_without_stringified_params(method, path, params: params)
end
alias_method :process_without_stringified_params, :process
alias_method :process, :process_with_stringified_params
@@ -550,10 +550,10 @@ end
The method `alias_method_chain` provides a shortcut for that pattern:
```ruby
-ActionController::TestCase.class_eval do
+ActionDispatch::IntegrationTest.class_eval do
def process_with_stringified_params(...)
params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
- process_without_stringified_params(action, params, session, flash, http_method)
+ process_without_stringified_params(method, path, params: params)
end
alias_method_chain :process, :stringified_params
end
@@ -568,7 +568,7 @@ NOTE: Defined in `active_support/core_ext/module/aliasing.rb`.
Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (one mnemonic is that they go in the same order as if you did an assignment):
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
# You can refer to the email column as "login".
# This can be meaningful for authentication code.
alias_attribute :login, :email
@@ -876,7 +876,7 @@ The macro `delegate` offers an easy way to forward methods.
Let's imagine that users in some application have login information in the `User` model but name and other data in a separate `Profile` model:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
has_one :profile
end
```
@@ -884,7 +884,7 @@ end
With that configuration you get a user's name via their profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
has_one :profile
def name
@@ -896,7 +896,7 @@ end
That is what `delegate` does for you:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
has_one :profile
delegate :name, to: :profile
@@ -2024,12 +2024,14 @@ Produce a string representation of a number rounded to a precision:
Produce a string representation of a number as a human-readable number of bytes:
```ruby
-123.to_s(:human_size) # => 123 Bytes
-1234.to_s(:human_size) # => 1.21 KB
-12345.to_s(:human_size) # => 12.1 KB
-1234567.to_s(:human_size) # => 1.18 MB
-1234567890.to_s(:human_size) # => 1.15 GB
-1234567890123.to_s(:human_size) # => 1.12 TB
+123.to_s(:human_size) # => 123 Bytes
+1234.to_s(:human_size) # => 1.21 KB
+12345.to_s(:human_size) # => 12.1 KB
+1234567.to_s(:human_size) # => 1.18 MB
+1234567890.to_s(:human_size) # => 1.15 GB
+1234567890123.to_s(:human_size) # => 1.12 TB
+1234567890123456.to_s(:human_size) # => 1.1 PB
+1234567890123456789.to_s(:human_size) # => 1.07 EB
```
Produce a string representation of a number in human-readable words:
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index fb3127555e..86baa9ee84 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -163,6 +163,14 @@ class definition:
config.api_only = true
```
+Optionally, in `config/environments/development.rb` add the following line
+to render error responses using the API format (JSON by default) when it
+is a local request:
+
+```ruby
+config.debug_exception_response_format = :api
+```
+
Finally, inside `app/controllers/application_controller.rb`, instead of:
```ruby
@@ -208,7 +216,7 @@ building, and make sense in an API-only Rails application.
You can get a list of all middlewares in your application via:
```bash
-$ rake middleware
+$ rails middleware
```
### Using the Cache Middleware
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 73e62eb6d9..cd208c2e13 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -20,7 +20,7 @@ The [Rails API documentation](http://api.rubyonrails.org) is generated with
in the rails root directory, run `bundle install` and execute:
```bash
- bundle exec rake rdoc
+ ./bin/rails rdoc
```
Resulting HTML files can be found in the ./doc/rdoc directory.
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 0f2283318a..0083fc0e6c 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -676,7 +676,7 @@ content changes.
### Precompiling Assets
-Rails comes bundled with a rake task to compile the asset manifests and other
+Rails comes bundled with a task to compile the asset manifests and other
files in the pipeline.
Compiled assets are written to the location specified in `config.assets.prefix`.
@@ -686,10 +686,10 @@ You can call this task on the server during deployment to create compiled
versions of your assets directly on the server. See the next section for
information on compiling locally.
-The rake task is:
+The task is:
```bash
-$ RAILS_ENV=production bin/rake assets:precompile
+$ RAILS_ENV=production bin/rails assets:precompile
```
Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment.
@@ -731,7 +731,7 @@ Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObje
NOTE. Always specify an expected compiled filename that ends with .js or .css,
even if you want to add Sass or CoffeeScript files to the precompile array.
-The rake task also generates a `manifest-md5hash.json` that contains a list with
+The task also generates a `manifest-md5hash.json` that contains a list with
all your assets and their respective fingerprints. This is used by the Rails
helper methods to avoid handing the mapping requests back to Sprockets. A
typical manifest file looks like:
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 2e485b9932..520b324ea7 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -19,10 +19,10 @@ Why Associations?
In Rails, an _association_ is a connection between two Active Record models. Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for customers and a model for orders. Each customer can have many orders. Without associations, the model declarations would look like this:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
end
```
@@ -45,11 +45,11 @@ end
With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, dependent: :destroy
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
end
```
@@ -89,7 +89,7 @@ In the remainder of this guide, you'll learn how to declare and use the various
A `belongs_to` association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
end
```
@@ -101,7 +101,7 @@ NOTE: `belongs_to` associations _must_ use the singular term. If you used the pl
The corresponding migration might look like this:
```ruby
-class CreateOrders < ActiveRecord::Migration
+class CreateOrders < ActiveRecord::Migration[5.0]
def change
create_table :customers do |t|
t.string :name
@@ -122,7 +122,7 @@ end
A `has_one` association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account
end
```
@@ -132,7 +132,7 @@ end
The corresponding migration might look like this:
```ruby
-class CreateSuppliers < ActiveRecord::Migration
+class CreateSuppliers < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
@@ -164,7 +164,7 @@ end
A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -176,7 +176,7 @@ NOTE: The name of the other model is pluralized when declaring a `has_many` asso
The corresponding migration might look like this:
```ruby
-class CreateCustomers < ActiveRecord::Migration
+class CreateCustomers < ActiveRecord::Migration[5.0]
def change
create_table :customers do |t|
t.string :name
@@ -197,17 +197,17 @@ end
A `has_many :through` association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this:
```ruby
-class Physician < ActiveRecord::Base
+class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
-class Appointment < ActiveRecord::Base
+class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
-class Patient < ActiveRecord::Base
+class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
@@ -218,7 +218,7 @@ end
The corresponding migration might look like this:
```ruby
-class CreateAppointments < ActiveRecord::Migration
+class CreateAppointments < ActiveRecord::Migration[5.0]
def change
create_table :physicians do |t|
t.string :name
@@ -255,17 +255,17 @@ WARNING: Automatic deletion of join models is direct, no destroy callbacks are t
The `has_many :through` association is also useful for setting up "shortcuts" through nested `has_many` associations. For example, if a document has many sections, and a section has many paragraphs, you may sometimes want to get a simple collection of all paragraphs in the document. You could set that up this way:
```ruby
-class Document < ActiveRecord::Base
+class Document < ApplicationRecord
has_many :sections
has_many :paragraphs, through: :sections
end
-class Section < ActiveRecord::Base
+class Section < ApplicationRecord
belongs_to :document
has_many :paragraphs
end
-class Paragraph < ActiveRecord::Base
+class Paragraph < ApplicationRecord
belongs_to :section
end
```
@@ -284,17 +284,17 @@ For example, if each supplier has one account, and each account is associated wi
supplier model could look like this:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account
has_one :account_history, through: :account
end
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
belongs_to :supplier
has_one :account_history
end
-class AccountHistory < ActiveRecord::Base
+class AccountHistory < ApplicationRecord
belongs_to :account
end
```
@@ -304,7 +304,7 @@ end
The corresponding migration might look like this:
```ruby
-class CreateAccountHistories < ActiveRecord::Migration
+class CreateAccountHistories < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
@@ -331,11 +331,11 @@ end
A `has_and_belongs_to_many` association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way:
```ruby
-class Assembly < ActiveRecord::Base
+class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
-class Part < ActiveRecord::Base
+class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
```
@@ -345,7 +345,7 @@ end
The corresponding migration might look like this:
```ruby
-class CreateAssembliesAndParts < ActiveRecord::Migration
+class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
def change
create_table :assemblies do |t|
t.string :name
@@ -372,11 +372,11 @@ If you want to set up a one-to-one relationship between two models, you'll need
The distinction is in where you place the foreign key (it goes on the table for the class declaring the `belongs_to` association), but you should give some thought to the actual meaning of the data as well. The `has_one` relationship says that one of something is yours - that is, that something points back to you. For example, it makes more sense to say that a supplier owns an account than that an account owns a supplier. This suggests that the correct relationships are like this:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account
end
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
belongs_to :supplier
end
```
@@ -384,7 +384,7 @@ end
The corresponding migration might look like this:
```ruby
-class CreateSuppliers < ActiveRecord::Migration
+class CreateSuppliers < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
@@ -409,11 +409,11 @@ NOTE: Using `t.integer :supplier_id` makes the foreign key naming obvious and ex
Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use `has_and_belongs_to_many`, which allows you to make the association directly:
```ruby
-class Assembly < ActiveRecord::Base
+class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
-class Part < ActiveRecord::Base
+class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
```
@@ -421,17 +421,17 @@ end
The second way to declare a many-to-many relationship is to use `has_many :through`. This makes the association indirectly, through a join model:
```ruby
-class Assembly < ActiveRecord::Base
+class Assembly < ApplicationRecord
has_many :manifests
has_many :parts, through: :manifests
end
-class Manifest < ActiveRecord::Base
+class Manifest < ApplicationRecord
belongs_to :assembly
belongs_to :part
end
-class Part < ActiveRecord::Base
+class Part < ApplicationRecord
has_many :manifests
has_many :assemblies, through: :manifests
end
@@ -446,15 +446,15 @@ You should use `has_many :through` if you need validations, callbacks or extra a
A slightly more advanced twist on associations is the _polymorphic association_. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model. Here's how this could be declared:
```ruby
-class Picture < ActiveRecord::Base
+class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
-class Employee < ActiveRecord::Base
+class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
-class Product < ActiveRecord::Base
+class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
```
@@ -466,7 +466,7 @@ Similarly, you can retrieve `@product.pictures`.
If you have an instance of the `Picture` model, you can get to its parent via `@picture.imageable`. To make this work, you need to declare both a foreign key column and a type column in the model that declares the polymorphic interface:
```ruby
-class CreatePictures < ActiveRecord::Migration
+class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
@@ -483,7 +483,7 @@ end
This migration can be simplified by using the `t.references` form:
```ruby
-class CreatePictures < ActiveRecord::Migration
+class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
@@ -501,7 +501,7 @@ end
In designing a data model, you will sometimes find a model that should have a relation to itself. For example, you may want to store all employees in a single database model, but be able to trace relationships such as between manager and subordinates. This situation can be modeled with self-joining associations:
```ruby
-class Employee < ActiveRecord::Base
+class Employee < ApplicationRecord
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"
@@ -514,7 +514,7 @@ With this setup, you can retrieve `@employee.subordinates` and `@employee.manage
In your migrations/schema, you will add a references column to the model itself.
```ruby
-class CreateEmployees < ActiveRecord::Migration
+class CreateEmployees < ActiveRecord::Migration[5.0]
def change
create_table :employees do |t|
t.references :manager, index: true
@@ -567,7 +567,7 @@ Associations are extremely useful, but they are not magic. You are responsible f
When you declare a `belongs_to` association, you need to create foreign keys as appropriate. For example, consider this model:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
end
```
@@ -575,7 +575,7 @@ end
This declaration needs to be backed up by the proper foreign key declaration on the orders table:
```ruby
-class CreateOrders < ActiveRecord::Migration
+class CreateOrders < ActiveRecord::Migration[5.0]
def change
create_table :orders do |t|
t.datetime :order_date
@@ -599,11 +599,11 @@ WARNING: The precedence between model names is calculated using the `<=>` operat
Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations:
```ruby
-class Assembly < ActiveRecord::Base
+class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
-class Part < ActiveRecord::Base
+class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
```
@@ -611,7 +611,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 CreateAssembliesPartsJoinTable < ActiveRecord::Migration
+class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
def change
create_table :assemblies_parts, id: false do |t|
t.integer :assembly_id
@@ -629,7 +629,7 @@ We pass `id: false` to `create_table` because that table does not represent a mo
You can also use the method `create_join_table`
```ruby
-class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
+class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
def change
create_join_table :assemblies, :parts do |t|
t.index :assembly_id
@@ -646,11 +646,11 @@ By default, associations look for objects only within the current module's scope
```ruby
module MyApplication
module Business
- class Supplier < ActiveRecord::Base
+ class Supplier < ApplicationRecord
has_one :account
end
- class Account < ActiveRecord::Base
+ class Account < ApplicationRecord
belongs_to :supplier
end
end
@@ -662,13 +662,13 @@ This will work fine, because both the `Supplier` and the `Account` class are def
```ruby
module MyApplication
module Business
- class Supplier < ActiveRecord::Base
+ class Supplier < ApplicationRecord
has_one :account
end
end
module Billing
- class Account < ActiveRecord::Base
+ class Account < ApplicationRecord
belongs_to :supplier
end
end
@@ -680,14 +680,14 @@ To associate a model with a model in a different namespace, you must specify the
```ruby
module MyApplication
module Business
- class Supplier < ActiveRecord::Base
+ class Supplier < ApplicationRecord
has_one :account,
class_name: "MyApplication::Billing::Account"
end
end
module Billing
- class Account < ActiveRecord::Base
+ class Account < ApplicationRecord
belongs_to :supplier,
class_name: "MyApplication::Business::Supplier"
end
@@ -700,11 +700,11 @@ end
It's normal for associations to work in two directions, requiring declaration on two different models:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
end
```
@@ -722,11 +722,11 @@ c.first_name == o.customer.first_name # => false
This happens because `c` and `o.customer` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, inverse_of: :customer
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, inverse_of: :orders
end
```
@@ -781,7 +781,7 @@ When you declare a `belongs_to` association, the declaring class automatically g
In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
end
```
@@ -848,7 +848,7 @@ Does the same as `create_association` above, but raises `ActiveRecord::RecordInv
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `belongs_to` association reference. Such customizations can easily be accomplished by passing options and scope blocks when you create the association. For example, this association uses two such options:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, dependent: :destroy,
counter_cache: true
end
@@ -877,7 +877,7 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if an order belongs to a customer, but the actual name of the model containing customers is `Patron`, you'd set things up this way:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, class_name: "Patron"
end
```
@@ -887,10 +887,10 @@ end
The `:counter_cache` option can be used to make finding the number of belonging objects more efficient. Consider these models:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
end
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -898,10 +898,10 @@ end
With these declarations, asking for the value of `@customer.orders.size` requires making a call to the database to perform a `COUNT(*)` query. To avoid this call, you can add a counter cache to the _belonging_ model:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, counter_cache: true
end
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -918,10 +918,10 @@ the `counter_cache` declaration instead of `true`. For example, to use
`count_of_orders` instead of `orders_count`:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, counter_cache: :count_of_orders
end
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -949,7 +949,7 @@ WARNING: You should not specify this option on a `belongs_to` association that i
By convention, Rails assumes that the column used to hold the foreign key on this model is the name of the association with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, class_name: "Patron",
foreign_key: "patron_id"
end
@@ -965,11 +965,11 @@ of its tables. The `:primary_key` option allows you to specify a different colum
For example, given we have a `users` table with `guid` as the primary key. If we want a separate `todos` table to hold the foreign key `user_id` in the `guid` column, then we can use `primary_key` to achieve this like so:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
self.primary_key = 'guid' # primary key is guid and not id
end
-class Todo < ActiveRecord::Base
+class Todo < ApplicationRecord
belongs_to :user, primary_key: 'guid'
end
```
@@ -982,11 +982,11 @@ When we execute `@user.todos.create` then the `@todo` record will have its
The `:inverse_of` option specifies the name of the `has_many` or `has_one` association that is the inverse of this association. Does not work in combination with the `:polymorphic` options.
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, inverse_of: :customer
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, inverse_of: :orders
end
```
@@ -1000,11 +1000,11 @@ Passing `true` to the `:polymorphic` option indicates that this is a polymorphic
If you set the `:touch` option to `true`, then the `updated_at` or `updated_on` timestamp on the associated object will be set to the current time whenever this object is saved or destroyed:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, touch: true
end
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -1012,7 +1012,7 @@ end
In this case, saving or destroying an order will update the timestamp on the associated customer. You can also specify a particular timestamp attribute to update:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, touch: :orders_updated_at
end
```
@@ -1031,7 +1031,7 @@ object won't be validated. By default, this option is set to `false`.
There may be times when you wish to customize the query used by `belongs_to`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, -> { where active: true },
dependent: :destroy
end
@@ -1049,7 +1049,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, -> { where active: true }
end
```
@@ -1059,16 +1059,16 @@ end
You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
```ruby
-class LineItem < ActiveRecord::Base
+class LineItem < ApplicationRecord
belongs_to :order
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
has_many :line_items
end
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -1076,16 +1076,16 @@ end
If you frequently retrieve customers directly from line items (`@line_item.order.customer`), then you can make your code somewhat more efficient by including customers in the association from line items to orders:
```ruby
-class LineItem < ActiveRecord::Base
+class LineItem < ApplicationRecord
belongs_to :order, -> { includes :customer }
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
has_many :line_items
end
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -1133,7 +1133,7 @@ When you declare a `has_one` association, the declaring class automatically gain
In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account
end
```
@@ -1197,7 +1197,7 @@ Does the same as `create_association` above, but raises `ActiveRecord::RecordInv
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_one` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account, class_name: "Billing", dependent: :nullify
end
```
@@ -1229,7 +1229,7 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a supplier has an account, but the actual name of the model containing accounts is `Billing`, you'd set things up this way:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account, class_name: "Billing"
end
```
@@ -1255,7 +1255,7 @@ unallowed `NULL` value.
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account, foreign_key: "supp_id"
end
```
@@ -1267,11 +1267,11 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to
The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options.
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account, inverse_of: :supplier
end
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
belongs_to :supplier, inverse_of: :account
end
```
@@ -1301,7 +1301,7 @@ If you set the `:validate` option to `true`, then associated objects will be val
There may be times when you wish to customize the query used by `has_one`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account, -> { where active: true }
end
```
@@ -1318,7 +1318,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account, -> { where "confirmed = 1" }
end
```
@@ -1328,16 +1328,16 @@ end
You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account
end
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
belongs_to :supplier
belongs_to :representative
end
-class Representative < ActiveRecord::Base
+class Representative < ApplicationRecord
has_many :accounts
end
```
@@ -1345,16 +1345,16 @@ end
If you frequently retrieve representatives directly from suppliers (`@supplier.account.representative`), then you can make your code somewhat more efficient by including representatives in the association from suppliers to accounts:
```ruby
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_one :account, -> { includes :representative }
end
-class Account < ActiveRecord::Base
+class Account < ApplicationRecord
belongs_to :supplier
belongs_to :representative
end
-class Representative < ActiveRecord::Base
+class Representative < ApplicationRecord
has_many :accounts
end
```
@@ -1415,7 +1415,7 @@ When you declare a `has_many` association, the declaring class automatically gai
In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
```
@@ -1582,7 +1582,7 @@ Does the same as `collection.create` above, but raises `ActiveRecord::RecordInva
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, dependent: :delete_all, validate: false
end
```
@@ -1615,7 +1615,7 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a customer has many orders, but the actual name of the model containing orders is `Transaction`, you'd set things up this way:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, class_name: "Transaction"
end
```
@@ -1639,7 +1639,7 @@ Controls what happens to the associated objects when their owner is destroyed:
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, foreign_key: "cust_id"
end
```
@@ -1651,11 +1651,11 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to
The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options.
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, inverse_of: :customer
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer, inverse_of: :orders
end
```
@@ -1670,7 +1670,7 @@ hold the `guid` column value as the foreign key and not `id`
value. This can be achieved like this:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
has_many :todos, primary_key: :guid
end
```
@@ -1700,7 +1700,7 @@ If you set the `:validate` option to `false`, then associated objects will not b
There may be times when you wish to customize the query used by `has_many`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, -> { where processed: true }
end
```
@@ -1723,7 +1723,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :confirmed_orders, -> { where "confirmed = 1" },
class_name: "Order"
end
@@ -1732,7 +1732,7 @@ end
You can also set conditions via a hash:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :confirmed_orders, -> { where confirmed: true },
class_name: "Order"
end
@@ -1749,7 +1749,7 @@ The `extending` method specifies a named module to extend the association proxy.
The `group` method supplies an attribute name to group the result set by, using a `GROUP BY` clause in the finder SQL.
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :line_items, -> { group 'orders.id' },
through: :orders
end
@@ -1760,16 +1760,16 @@ end
You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
has_many :line_items
end
-class LineItem < ActiveRecord::Base
+class LineItem < ApplicationRecord
belongs_to :order
end
```
@@ -1777,16 +1777,16 @@ end
If you frequently retrieve line items directly from customers (`@customer.orders.line_items`), then you can make your code somewhat more efficient by including line items in the association from customers to orders:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, -> { includes :line_items }
end
-class Order < ActiveRecord::Base
+class Order < ApplicationRecord
belongs_to :customer
has_many :line_items
end
-class LineItem < ActiveRecord::Base
+class LineItem < ApplicationRecord
belongs_to :order
end
```
@@ -1796,7 +1796,7 @@ end
The `limit` method lets you restrict the total number of objects that will be fetched through an association.
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :recent_orders,
-> { order('order_date desc').limit(100) },
class_name: "Order",
@@ -1812,7 +1812,7 @@ The `offset` method lets you specify the starting offset for fetching objects vi
The `order` method dictates the order in which associated objects will be received (in the syntax used by an SQL `ORDER BY` clause).
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, -> { order "date_confirmed DESC" }
end
```
@@ -1833,7 +1833,7 @@ 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
+class Person < ApplicationRecord
has_many :readings
has_many :articles, through: :readings
end
@@ -1937,7 +1937,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au
In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration:
```ruby
-class Part < ActiveRecord::Base
+class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
```
@@ -2091,7 +2091,7 @@ Does the same as `collection.create`, but raises `ActiveRecord::RecordInvalid` i
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options:
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, -> { readonly },
autosave: true
end
@@ -2113,7 +2113,7 @@ By convention, Rails assumes that the column in the join table used to hold the
TIP: The `:foreign_key` and `:association_foreign_key` options are useful when setting up a many-to-many self-join. For example:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
has_and_belongs_to_many :friends,
class_name: "User",
foreign_key: "this_user_id",
@@ -2130,7 +2130,7 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a part has many assemblies, but the actual name of the model containing assemblies is `Gadget`, you'd set things up this way:
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, class_name: "Gadget"
end
```
@@ -2140,7 +2140,7 @@ end
By convention, Rails assumes that the column in the join table used to hold the foreign key pointing to this model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
has_and_belongs_to_many :friends,
class_name: "User",
foreign_key: "this_user_id",
@@ -2161,7 +2161,7 @@ If you set the `:validate` option to `false`, then associated objects will not b
There may be times when you wish to customize the query used by `has_and_belongs_to_many`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, -> { where active: true }
end
```
@@ -2184,7 +2184,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { where "factory = 'Seattle'" }
end
@@ -2193,7 +2193,7 @@ end
You can also set conditions via a hash:
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { where factory: 'Seattle' }
end
@@ -2210,7 +2210,7 @@ The `extending` method specifies a named module to extend the association proxy.
The `group` method supplies an attribute name to group the result set by, using a `GROUP BY` clause in the finder SQL.
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, -> { group "factory" }
end
```
@@ -2224,7 +2224,7 @@ You can use the `includes` method to specify second-order associations that shou
The `limit` method lets you restrict the total number of objects that will be fetched through an association.
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { order("created_at DESC").limit(50) }
end
@@ -2239,7 +2239,7 @@ The `offset` method lets you specify the starting offset for fetching objects vi
The `order` method dictates the order in which associated objects will be received (in the syntax used by an SQL `ORDER BY` clause).
```ruby
-class Parts < ActiveRecord::Base
+class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { order "assembly_name ASC" }
end
@@ -2281,7 +2281,7 @@ Association callbacks are similar to normal callbacks, but they are triggered by
You define association callbacks by adding options to the association declaration. For example:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, before_add: :check_credit_limit
def check_credit_limit(order)
@@ -2295,7 +2295,7 @@ Rails passes the object being added or removed to the callback.
You can stack callbacks on a single event by passing them as an array:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders,
before_add: [:check_credit_limit, :calculate_shipping_charges]
@@ -2316,7 +2316,7 @@ If a `before_add` callback throws an exception, the object does not get added to
You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example:
```ruby
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders do
def find_by_order_prefix(order_number)
find_by(region_id: order_number[0..2])
@@ -2334,11 +2334,11 @@ module FindRecentExtension
end
end
-class Customer < ActiveRecord::Base
+class Customer < ApplicationRecord
has_many :orders, -> { extending FindRecentExtension }
end
-class Supplier < ActiveRecord::Base
+class Supplier < ApplicationRecord
has_many :deliveries, -> { extending FindRecentExtension }
end
```
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index 5126d87bee..de0fa2fdc0 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -181,14 +181,14 @@ constant.
That is,
```ruby
-class Project < ActiveRecord::Base
+class Project < ApplicationRecord
end
```
performs a constant assignment equivalent to
```ruby
-Project = Class.new(ActiveRecord::Base)
+Project = Class.new(ApplicationRecord)
```
including setting the name of the class as a side-effect:
@@ -685,7 +685,7 @@ to trigger the heuristic is defined in the conflicting place.
### Automatic Modules
When a module acts as a namespace, Rails does not require the application to
-defines a file for it, a directory matching the namespace is enough.
+define a file for it, a directory matching the namespace is enough.
Suppose an application has a back office whose controllers are stored in
`app/controllers/admin`. If the `Admin` module is not yet loaded when
@@ -912,7 +912,7 @@ these classes:
```ruby
# app/models/polygon.rb
-class Polygon < ActiveRecord::Base
+class Polygon < ApplicationRecord
end
# app/models/triangle.rb
@@ -987,7 +987,7 @@ root class:
```ruby
# app/models/polygon.rb
-class Polygon < ActiveRecord::Base
+class Polygon < ApplicationRecord
end
require_dependency ‘square’
```
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 9a56233e4a..3a1a1ccfe6 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -175,11 +175,11 @@ your app will serve stale data. To fix this, we tie the models together with
the `touch` method:
```ruby
-class Product < ActiveRecord::Base
+class Product < ApplicationRecord
has_many :games
end
-class Game < ActiveRecord::Base
+class Game < ApplicationRecord
belongs_to :product, touch: true
end
```
@@ -284,7 +284,7 @@ The most efficient way to implement low-level caching is using the `Rails.cache.
Consider the following example. An application has a `Product` model with an instance method that looks up the product’s price on a competing website. The data returned by this method would be perfect for low-level caching:
```ruby
-class Product < ActiveRecord::Base
+class Product < ApplicationRecord
def competing_price
Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index e85f9fc9c6..5240b5f343 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -39,7 +39,7 @@ INFO: You can install the rails gem by typing `gem install rails`, if you don't
```bash
$ rails new commandsapp
create
- create README.rdoc
+ create README.md
create Rakefile
create config.ru
create .gitignore
@@ -392,7 +392,7 @@ rake assets:clobber # Remove compiled assets
rake assets:precompile # Compile all the assets named in config.assets.precompile
rake db:create # Create the database from config/database.yml for the current Rails.env
...
-rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
+rake log:clear # Truncates all/specified *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
rake middleware # Prints out your Rack middleware stack
...
rake tmp:clear # Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)
@@ -584,8 +584,8 @@ $ rails new . --git --database=postgresql
create tmp/pids
create Rakefile
add 'Rakefile'
- create README.rdoc
-add 'README.rdoc'
+ create README.md
+add 'README.md'
create app/controllers/application_controller.rb
add 'app/controllers/application_controller.rb'
create app/helpers/application_helper.rb
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index dbb432db20..0b24d4d06a 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -98,13 +98,13 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
* `config.exceptions_app` sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to `ActionDispatch::PublicExceptions.new(Rails.public_path)`.
-* `config.file_watcher` the class used to detect file updates in the filesystem when `config.reload_classes_only_on_change` is true. Must conform to `ActiveSupport::FileUpdateChecker` API.
+* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is true. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API.
* `config.filter_parameters` used for filtering out the parameters that
you don't want shown in the logs, such as passwords or credit card
numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`.
-* `config.force_ssl` forces all requests to be under HTTPS protocol by using `ActionDispatch::SSL` middleware.
+* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
@@ -198,7 +198,7 @@ The full set of methods that can be used in this block are as follows:
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
-* `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`.
+* `ActionDispatch::SSL` forces every request to be served using HTTPS. Enabled if `config.force_ssl` is set to `true`. Options passed to this can be configured by setting `config.ssl_options`.
* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`.
* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`.
* `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
@@ -319,7 +319,7 @@ All these configuration options are delegated to the `I18n` library.
The MySQL adapter adds one additional configuration option:
-* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
+* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
The schema dumper adds one additional configuration option:
@@ -335,8 +335,6 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`.
-* `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8".
-
* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`.
* `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging.
@@ -345,6 +343,10 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.allow_forgery_protection` enables or disables CSRF protection. By default this is `false` in test mode and `true` in all other modes.
+* `config.action_controller.forgery_protection_origin_check` configures whether the HTTP `Origin` header should be checked against the site's origin as an additional CSRF defense.
+
+* `config.action_controller.per_form_csrf_tokens` configures whether CSRF tokens are only valid for the method/action they were generated for.
+
* `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`.
* `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`.
@@ -367,6 +369,8 @@ The schema dumper adds one additional configuration option:
}
```
+* `config.action_dispatch.default_charset` specifies the default character set for all renders. Defaults to `nil`.
+
* `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`.
* `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults
@@ -459,6 +463,8 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.automatically_disable_submit_tag` determines whether
submit_tag should automatically disable on click, this defaults to true.
+* `config.action_view.debug_missing_translation` determins whether to wrap the missing translations key in a `<span>` tag or not. This defaults to true.
+
### Configuring Action Mailer
There are a number of settings available on `config.action_mailer`:
@@ -541,7 +547,7 @@ There are a few configuration options available in Active Support:
* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
-* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. Defaults to `true`.
+* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns false (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rake rails:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
@@ -1043,9 +1049,9 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `action_mailer.compile_config_methods` Initializes methods for the config settings specified so that they are quicker to access.
-* `set_load_path` This initializer runs before `bootstrap_hook`. Adds the `vendor`, `lib`, all directories of `app` and any paths specified by `config.load_paths` to `$LOAD_PATH`.
+* `set_load_path` This initializer runs before `bootstrap_hook`. Adds paths specified by `config.load_paths` and all autoload paths to `$LOAD_PATH`.
-* `set_autoload_paths` This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths` to `ActiveSupport::Dependencies.autoload_paths`.
+* `set_autoload_paths` This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths`, `config.eager_load_paths` and `config.autoload_once_paths` to `ActiveSupport::Dependencies.autoload_paths`.
* `add_routing_paths` Loads (by default) all `config/routes.rb` files (in the application and railties, including engines) and sets up the routes for the application.
@@ -1113,21 +1119,48 @@ NOTE. If you are running in a multi-threaded environment, there could be a chanc
Custom configuration
--------------------
-You can configure your own code through the Rails configuration object with custom configuration under the `config.x` property. It works like this:
+You can configure your own code through the Rails configuration object with custom configuration. It works like this:
```ruby
- config.x.payment_processing.schedule = :daily
- config.x.payment_processing.retries = 3
- config.x.super_debugger = true
+ config.payment_processing.schedule = :daily
+ config.payment_processing.retries = 3
+ config.super_debugger = true
```
These configuration points are then available through the configuration object:
```ruby
- Rails.configuration.x.payment_processing.schedule # => :daily
- Rails.configuration.x.payment_processing.retries # => 3
- Rails.configuration.x.super_debugger # => true
- Rails.configuration.x.super_debugger.not_set # => nil
+ Rails.configuration.payment_processing.schedule # => :daily
+ Rails.configuration.payment_processing.retries # => 3
+ Rails.configuration.super_debugger # => true
+ Rails.configuration.super_debugger.not_set # => nil
+ ```
+
+You can also use `Rails::Application.config_for` to load whole configuration files:
+
+ ```ruby
+ # config/payment.yml:
+ production:
+ environment: production
+ merchant_id: production_merchant_id
+ public_key: production_public_key
+ private_key: production_private_key
+ development:
+ environment: sandbox
+ merchant_id: development_merchant_id
+ public_key: development_public_key
+ private_key: development_private_key
+
+ # config/application.rb
+ module MyApp
+ class Application < Rails::Application
+ config.payment = config_for(:payment)
+ end
+ end
+ ```
+
+ ```ruby
+ Rails.configuration.payment['merchant_id'] # => production_merchant_id or development_merchant_id
```
Search Engines Indexing
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 5885eb6e1c..cbc304c87f 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -16,7 +16,7 @@ After reading this guide, you will know:
Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches.
As mentioned in [Rails
-README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](https://github.com/rails/rails/blob/master/CODE_OF_CONDUCT.md).
+README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/).
--------------------------------------------------------------------------------
@@ -357,7 +357,6 @@ $ bundle exec rake test:sqlite3
You can now run the tests as you did for `sqlite3`. The tasks are respectively:
```bash
-test:mysql
test:mysql2
test:postgresql
```
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 5424313b33..0046ff7b4e 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -608,7 +608,7 @@ Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200
Processing by ArticlesController#index as HTML
[1, 8] in /home/davidr/Proyectos/test_app/app/models/article.rb
- 1: class Article < ActiveRecord::Base
+ 1: class Article < ApplicationRecord
2:
3: def self.find_recent(limit = 10)
4: byebug
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 4322f03d05..7beb8f72a9 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -165,7 +165,7 @@ $ bundle exec ruby -Itest path/to/test.rb -n test_name
### Active Record Setup
-The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (`mysql` and `mysql2`), and once for PostgreSQL. We are going to see now how to set up the environment for them.
+Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them.
WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 359796b1ff..8382bde4d3 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -508,7 +508,7 @@ Turning the model into this:
```ruby
module Blorgh
- class Article < ActiveRecord::Base
+ class Article < ApplicationRecord
has_many :comments
end
end
@@ -1033,9 +1033,9 @@ typical `GET` to a controller in a controller's functional test like this:
```ruby
module Blorgh
- class FooControllerTest < ActionController::TestCase
+ class FooControllerTest < ActionDispatch::IntegrationTest
def test_index
- get :index
+ get foos_url
...
end
end
@@ -1049,13 +1049,13 @@ in your setup code:
```ruby
module Blorgh
- class FooControllerTest < ActionController::TestCase
+ class FooControllerTest < ActionDispatch::IntegrationTest
setup do
@routes = Engine.routes
end
def test_index
- get :index
+ get foos_url
...
end
end
@@ -1129,7 +1129,7 @@ end
```ruby
# Blorgh/app/models/article.rb
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
has_many :comments
end
```
@@ -1150,7 +1150,7 @@ end
```ruby
# Blorgh/app/models/article.rb
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
has_many :comments
def summary
"#{title}"
@@ -1171,7 +1171,7 @@ classes at run time allowing you to significantly modularize your code.
```ruby
# MyApp/app/models/blorgh/article.rb
-class Blorgh::Article < ActiveRecord::Base
+class Blorgh::Article < ApplicationRecord
include Blorgh::Concerns::Models::Article
def time_since_created
@@ -1187,7 +1187,7 @@ end
```ruby
# Blorgh/app/models/article.rb
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
include Blorgh::Concerns::Models::Article
end
```
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 93bb51557a..2a289dd33a 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -880,12 +880,12 @@ Many apps grow beyond simple forms editing a single object. For example, when cr
Active Record provides model level support via the `accepts_nested_attributes_for` method:
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses
end
-class Address < ActiveRecord::Base
+class Address < ApplicationRecord
belongs_to :person
end
```
@@ -973,7 +973,7 @@ private
You can allow users to delete associated objects by passing `allow_destroy: true` to `accepts_nested_attributes_for`
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses, allow_destroy: true
end
@@ -1014,7 +1014,7 @@ end
It is often useful to ignore sets of fields that the user has not filled in. You can control this by passing a `:reject_if` proc to `accepts_nested_attributes_for`. This proc will be called with each hash of attributes submitted by the form. If the proc returns `false` then Active Record will not build an associated object for that hash. The example below only tries to build an address if the `kind` attribute is set.
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| attributes['kind'].blank?}
end
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 5700e71103..9f38de6247 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -37,6 +37,7 @@ curve diving straight into Rails. There are several curated lists of online reso
for learning Ruby:
* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/)
+* [List of Free Programming Books](https://github.com/vhf/free-programming-books/blob/master/free-programming-books.md#ruby)
Be aware that some resources, while still excellent, cover versions of Ruby as old as
1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day
@@ -173,7 +174,7 @@ of the files and folders that Rails created by default:
|log/|Application log files.|
|public/|The only folder seen by the world as-is. Contains static files and compiled assets.|
|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.|
-|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
+|README.md|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).|
|tmp/|Temporary files (like cache and pid files).|
|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.|
@@ -679,7 +680,7 @@ If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file
(remember, yours will have a slightly different name), here's what you'll find:
```ruby
-class CreateArticles < ActiveRecord::Migration
+class CreateArticles < ActiveRecord::Migration[5.0]
def change
create_table :articles do |t|
t.string :title
@@ -990,21 +991,22 @@ and restart the web server when a change is made.
The model file, `app/models/article.rb` is about as simple as it can get:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
end
```
There isn't much to this file - but note that the `Article` class inherits from
-`ActiveRecord::Base`. Active Record supplies a great deal of functionality to
-your Rails models for free, including basic database CRUD (Create, Read, Update,
-Destroy) operations, data validation, as well as sophisticated search support
-and the ability to relate multiple models to one another.
+`ApplicationRecord`. `ApplicationRecord` inherits from `ActiveRecord::Base`
+which supplies a great deal of functionality to your Rails models for free,
+including basic database CRUD (Create, Read, Update, Destroy) operations, data
+validation, as well as sophisticated search support and the ability to relate
+multiple models to one another.
Rails includes methods to help you validate the data that you send to models.
Open the `app/models/article.rb` file and edit it:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
validates :title, presence: true,
length: { minimum: 5 }
end
@@ -1529,7 +1531,7 @@ This command will generate four files:
First, take a look at `app/models/comment.rb`:
```ruby
-class Comment < ActiveRecord::Base
+class Comment < ApplicationRecord
belongs_to :article
end
```
@@ -1542,7 +1544,7 @@ In addition to the model, Rails has also made a migration to create the
corresponding database table:
```ruby
-class CreateComments < ActiveRecord::Migration
+class CreateComments < ActiveRecord::Migration[5.0]
def change
create_table :comments do |t|
t.string :commenter
@@ -1587,7 +1589,7 @@ association. You've already seen the line of code inside the `Comment` model
(app/models/comment.rb) that makes each comment belong to an Article:
```ruby
-class Comment < ActiveRecord::Base
+class Comment < ApplicationRecord
belongs_to :article
end
```
@@ -1596,7 +1598,7 @@ You'll need to edit `app/models/article.rb` to add the other side of the
association:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
has_many :comments
validates :title, presence: true,
length: { minimum: 5 }
@@ -1962,7 +1964,7 @@ you to use the `dependent` option of an association to achieve this. Modify the
Article model, `app/models/article.rb`, as follows:
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 8381636196..5bbd4048b9 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -470,7 +470,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali
```erb
# app/views/home/index.html.erb
<h1><%=t :hello_world %></h1>
-<p><%= flash[:notice] %></p
+<p><%= flash[:notice] %></p>
<p><%= l Time.now, format: :short %></p>
```
@@ -816,7 +816,7 @@ This gives you quite powerful means to flexibly adjust your messages to your app
Consider a User model with a validation for the name attribute like this:
```ruby
-class User < ActiveRecord::Base
+class User < ApplicationRecord
validates :name, presence: true
end
```
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index ebe1cb206a..6232ef4c57 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -139,7 +139,8 @@ aliases = {
"c" => "console",
"s" => "server",
"db" => "dbconsole",
- "r" => "runner"
+ "r" => "runner",
+ "t" => "test"
}
command = ARGV.shift
@@ -158,19 +159,20 @@ defined here to find the matching command.
### `rails/commands/command_tasks.rb`
-When one types an incorrect rails command, the `run_command` is responsible for
-throwing an error message. If the command is valid, a method of the same name
-is called.
+When one types a valid Rails command, `run_command!` a method of the same name
+is called. If Rails doesn't recognize the command, it tries to run a Rake task
+of the same name.
```ruby
COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole application runner new version help)
def run_command!(command)
command = parse_command(command)
+
if COMMAND_WHITELIST.include?(command)
send(command)
else
- write_error_message(command)
+ run_rake_task(command)
end
end
```
@@ -528,16 +530,17 @@ This file is responsible for requiring all the individual frameworks of Rails:
require "rails"
%w(
- active_record
- action_controller
- action_view
- action_mailer
- active_job
- rails/test_unit
- sprockets
-).each do |framework|
+ active_record/railtie
+ action_controller/railtie
+ action_view/railtie
+ action_mailer/railtie
+ active_job/railtie
+ action_cable/engine
+ rails/test_unit/railtie
+ sprockets/railtie
+).each do |railtie|
begin
- require "#{framework}/railtie"
+ require "#{railtie}"
rescue LoadError
end
end
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 71cc030f6a..9b7f916b9e 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -622,10 +622,13 @@ Another way to handle returning responses to an HTTP request is with `redirect_t
redirect_to photos_url
```
-You can use `redirect_to` with any arguments that you could use with `link_to` or `url_for`. There's also a special redirect that sends the user back to the page they just came from:
+You can use `redirect_back` to return the user to the page they just came from.
+This location is pulled from the `HTTP_REFERER` header which is not guaranteed
+to be set by the browser, so you must provide the `fallback_location`
+to use in this case.
```ruby
-redirect_to :back
+redirect_back(fallback_location: root_path)
```
#### Getting a Different Redirect Status Code
@@ -1154,14 +1157,12 @@ To pass a local variable to a partial in only specific cases use the `local_assi
* `_articles.html.erb`
```erb
- <%= content_tag_for :article, article do |article| %>
- <h2><%= article.title %></h2>
+ <h2><%= article.title %></h2>
- <% if local_assigns[:full] %>
- <%= simple_format article.body %>
- <% else %>
- <%= truncate article.body %>
- <% end %>
+ <% if local_assigns[:full] %>
+ <%= simple_format article.body %>
+ <% else %>
+ <%= truncate article.body %>
<% end %>
```
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 50308f505a..f99b6ebd31 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -44,7 +44,7 @@ from.
In special situations, where someone from the Core Team agrees to support more series,
they are included in the list of supported series.
-**Currently included series:** `4.2.Z`, `4.1.Z` (Supported by Rafael França).
+**Currently included series:** `5.0.Z`.
Security Issues
---------------
@@ -59,7 +59,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that
security releases are easy to upgrade to if you're running the latest version
of Rails.
-**Currently included series:** `4.2.Z`, `4.1.Z`.
+**Currently included series:** `5.0.Z`, `4.2.Z`.
Severe Security Issues
----------------------
@@ -68,7 +68,7 @@ For severe security issues we will provide new versions as above, and also the
last major release series will receive patches and new versions. The
classification of the security issue is judged by the core team.
-**Currently included series:** `4.2.Z`, `4.1.Z`, `3.2.Z`.
+**Currently included series:** `5.0.Z`, `4.2.Z`.
Unsupported Release Series
--------------------------
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index 121cf2b185..71efa4b0d0 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -32,7 +32,7 @@ For an ActiveRecord::Base model and association this writer method is commonly d
#### has_one
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
end
@@ -41,7 +41,7 @@ end
#### belongs_to
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
belongs_to :firm
accepts_nested_attributes_for :firm
end
@@ -50,7 +50,7 @@ end
#### has_many / has_and_belongs_to_many
```ruby
-class Person < ActiveRecord::Base
+class Person < ApplicationRecord
has_many :projects
accepts_nested_attributes_for :projects
end
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 922bbb4f73..68e54f2414 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -17,7 +17,7 @@ After reading this guide, you will know:
This guide describes how to build a test-driven plugin that will:
* Extend core Ruby classes like Hash and String.
-* Add methods to `ActiveRecord::Base` in the tradition of the `acts_as` plugins.
+* Add methods to `ApplicationRecord` in the tradition of the `acts_as` plugins.
* Give you information about where to put generators in your plugin.
For the purpose of this guide pretend for a moment that you are an avid bird watcher.
@@ -37,7 +37,7 @@ different rails applications using RubyGems and Bundler if desired.
Rails ships with a `rails plugin new` command which creates a
skeleton for developing any kind of Rails extension with the ability
-to run integration tests using a dummy Rails application. Create your
+to run integration tests using a dummy Rails application. Create your
plugin with the command:
```bash
@@ -54,7 +54,7 @@ Testing Your Newly Generated Plugin
-----------------------------------
You can navigate to the directory that contains the plugin, run the `bundle install` command
- and run the one generated test using the `rake` command.
+ and run the one generated test using the `bin/test` command.
You should see:
@@ -83,13 +83,23 @@ class CoreExtTest < ActiveSupport::TestCase
end
```
-Run `rake` to run the test. This test should fail because we haven't implemented the `to_squawk` method:
+Run `bin/test` to run the test. This test should fail because we haven't implemented the `to_squawk` method:
```bash
- 1) Error:
- CoreExtTest#test_to_squawk_prepends_the_word_squawk:
- NoMethodError: undefined method `to_squawk' for "Hello World":String
- /path/to/yaffle/test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk'
+E
+
+Error:
+CoreExtTest#test_to_squawk_prepends_the_word_squawk:
+NoMethodError: undefined method `to_squawk' for "Hello World":String
+
+
+bin/test /path/to/yaffle/test/core_ext_test.rb:4
+
+.
+
+Finished in 0.003358s, 595.6483 runs/s, 297.8242 assertions/s.
+
+2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
```
Great - now you are ready to start development.
@@ -117,7 +127,7 @@ String.class_eval do
end
```
-To test that your method does what it says it does, run the unit tests with `rake` from your plugin directory.
+To test that your method does what it says it does, run the unit tests with `bin/test` from your plugin directory.
```bash
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
@@ -182,7 +192,6 @@ To start out, write a failing test that shows the behavior you'd like:
require 'test_helper'
class ActsAsYaffleTest < ActiveSupport::TestCase
-
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
@@ -190,24 +199,37 @@ class ActsAsYaffleTest < ActiveSupport::TestCase
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
-
end
```
-When you run `rake`, you should see the following:
+When you run `bin/test`, you should see the following:
```
- 1) Error:
- ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
- NameError: uninitialized constant ActsAsYaffleTest::Hickwall
- /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
+# Running:
+
+..E
+
+Error:
+ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
+NameError: uninitialized constant ActsAsYaffleTest::Wickwall
- 2) Error:
- ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
- NameError: uninitialized constant ActsAsYaffleTest::Wickwall
- /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
- 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
+bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8
+
+E
+
+Error:
+ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
+NameError: uninitialized constant ActsAsYaffleTest::Hickwall
+
+
+bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4
+
+
+
+Finished in 0.004812s, 831.2949 runs/s, 415.6475 assertions/s.
+
+4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
```
This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test.
@@ -234,22 +256,22 @@ like yaffles.
```ruby
# test/dummy/app/models/hickwall.rb
-class Hickwall < ActiveRecord::Base
+class Hickwall < ApplicationRecord
acts_as_yaffle
end
# test/dummy/app/models/wickwall.rb
-class Wickwall < ActiveRecord::Base
+class Wickwall < ApplicationRecord
acts_as_yaffle yaffle_text_field: :last_tweet
end
-
```
We will also add code to define the `acts_as_yaffle` method.
```ruby
# yaffle/lib/yaffle/acts_as_yaffle.rb
+
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
@@ -265,26 +287,43 @@ module Yaffle
end
end
-ActiveRecord::Base.include(Yaffle::ActsAsYaffle)
+# test/dummy/app/models/application_record.rb
+
+class ApplicationRecord < ActiveRecord::Base
+ include Yaffle::ActsAsYaffle
+
+ self.abstract_class = true
+end
```
-You can then return to the root directory (`cd ../..`) of your plugin and rerun the tests using `rake`.
+You can then return to the root directory (`cd ../..`) of your plugin and rerun the tests using `bin/test`.
```
- 1) Error:
- ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
- NoMethodError: undefined method `yaffle_text_field' for #<Class:0x007fd105e3b218>
- activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing'
- /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
+# Running:
+
+.E
+
+Error:
+ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
+NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974ebbe9d8>
+
+
+bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4
+
+E
+
+Error:
+ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
+NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974eb8cfc8>
- 2) Error:
- ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
- NoMethodError: undefined method `yaffle_text_field' for #<Class:0x007fd105e409c0>
- activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing'
- /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
- 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
+bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8
+.
+
+Finished in 0.008263s, 484.0999 runs/s, 242.0500 assertions/s.
+
+4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
```
Getting closer... Now we will implement the code of the `acts_as_yaffle` method to make the tests pass.
@@ -308,10 +347,16 @@ module Yaffle
end
end
-ActiveRecord::Base.include(Yaffle::ActsAsYaffle)
+# test/dummy/app/models/application_record.rb
+
+class ApplicationRecord < ActiveRecord::Base
+ include Yaffle::ActsAsYaffle
+
+ self.abstract_class = true
+end
```
-When you run `rake`, you should see the tests all pass:
+When you run `bin/test`, you should see the tests all pass:
```bash
4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
@@ -329,7 +374,6 @@ To start out, write a failing test that shows the behavior you'd like:
require 'test_helper'
class ActsAsYaffleTest < ActiveSupport::TestCase
-
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
@@ -382,10 +426,16 @@ module Yaffle
end
end
-ActiveRecord::Base.include(Yaffle::ActsAsYaffle)
+# test/dummy/app/models/application_record.rb
+
+class ApplicationRecord < ActiveRecord::Base
+ include Yaffle::ActsAsYaffle
+
+ self.abstract_class = true
+end
```
-Run `rake` one final time and you should see:
+Run `bin/test` one final time and you should see:
```
6 runs, 6 assertions, 0 failures, 0 errors, 0 skips
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 245689932b..9401132500 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -142,10 +142,10 @@ Sometimes, you have a resource that clients always look up without referencing a
get 'profile', to: 'users#show'
```
-Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action:
+Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action but you must also specify the `controller:` to use:
```ruby
-get 'profile', to: :show
+get 'profile', to: :show, controller: 'users'
```
This resourceful route:
@@ -252,11 +252,11 @@ TIP: _If you need to use a different controller namespace inside a `namespace` b
It's common to have resources that are logically children of other resources. For example, suppose your application includes these models:
```ruby
-class Magazine < ActiveRecord::Base
+class Magazine < ApplicationRecord
has_many :ads
end
-class Ad < ActiveRecord::Base
+class Ad < ApplicationRecord
belongs_to :magazine
end
```
@@ -392,7 +392,7 @@ The comments resource here will have the following routes generated for it:
### Routing concerns
-Routing Concerns allows you to declare common routes that can be reused inside other resources and routes. To define a concern:
+Routing concerns allow you to declare common routes that can be reused inside other resources and routes. To define a concern:
```ruby
concern :commentable do
@@ -1099,7 +1099,7 @@ You can override `ActiveRecord::Base#to_param` of a related model to construct
a URL:
```ruby
-class Video < ActiveRecord::Base
+class Video < ApplicationRecord
def to_param
identifier
end
diff --git a/guides/source/security.md b/guides/source/security.md
index df8c24864e..1d0e87d831 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -171,7 +171,7 @@ NOTE: _Sessions that never expire extend the time-frame for attacks such as cros
One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago.
```ruby
-class Session < ActiveRecord::Base
+class Session < ApplicationRecord
def self.sweep(time = 1.hour)
if time.is_a?(String)
time = time.split.inject { |count, unit| count.to_i.send(unit) }
@@ -381,9 +381,9 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend
**CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
-A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
+A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for the user, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
-Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.

+Another example changed Google Adsense's e-mail address and password. If the victim was logged into Google Adsense, the administration interface for Google advertisement campaigns, an attacker could change the credentials of the victim.

Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination.
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 3bfbf4f7ff..84f80e3c37 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -177,7 +177,7 @@ Saved the article without a title
Now to get this test to pass we can add a model level validation for the _title_ field.
```ruby
-class Article < ActiveRecord::Base
+class Article < ApplicationRecord
validates :title, presence: true
end
```
@@ -330,7 +330,6 @@ You'll see the usage of some of these assertions in the next chapter.
All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from:
* `ActiveSupport::TestCase`
-* `ActionController::TestCase`
* `ActionMailer::TestCase`
* `ActionView::TestCase`
* `ActionDispatch::IntegrationTest`
@@ -655,7 +654,7 @@ The easiest way to see functional tests in action is to generate a controller
scaffold:
```bash
-$ bin/rails generate scaffold_controller article title:string body:test
+$ bin/rails generate scaffold_controller article title:string body:text
...
create app/controllers/articles_controller.rb
...
@@ -682,9 +681,9 @@ Let me take you through one such test, `test_should_get_index` from the file `ar
```ruby
# articles_controller_test.rb
-class ArticlesControllerTest < ActionController::TestCase
+class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
- get :index
+ get '/articles'
assert_response :success
assert_includes @response.body, 'Articles'
end
@@ -697,7 +696,7 @@ and also ensuring that the right response body has been generated.
The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
* The action of the controller you are requesting.
- This can be in the form of a string or a symbol.
+ This can be in the form of a string or a route (i.e. `articles_url`).
* `params`: option with a hash of request parameters to pass into the action
(e.g. query string parameters or article variables).
@@ -717,7 +716,7 @@ get(:show, params: { id: 12 }, session: { user_id: 5 })
Another example: Calling the `:view` action, passing an `id` of 12 as the `params`, this time with no session, but with a flash message.
```ruby
-get(:view, params: { id: 12 }, flash: { message: 'booya!' })
+get(view_url, params: { id: 12 }, flash: { message: 'booya!' })
```
NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so.
@@ -727,7 +726,7 @@ Let us modify `test_should_create_article` test in `articles_controller_test.rb`
```ruby
test "should create article" do
assert_difference('Article.count') do
- post :create, params: { article: { title: 'Some title' } }
+ post '/article', params: { article: { title: 'Some title' } }
end
assert_redirected_to article_path(Article.last)
@@ -758,7 +757,8 @@ To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`,
```ruby
test "ajax request" do
- get :show, params: { id: articles(:first).id }, xhr: true
+ article = articles(:first)
+ get article_url(article), xhr: true
assert_equal 'hello world', @response.body
assert_equal "text/javascript", @response.content_type
@@ -799,11 +799,11 @@ can be set directly on the `@request` instance variable:
```ruby
# setting a HTTP Header
@request.headers["Accept"] = "text/plain, text/html"
-get :index # simulate the request with custom header
+get articles_url # simulate the request with custom header
# setting a CGI variable
@request.headers["HTTP_REFERER"] = "http://example.com/home"
-post :create # simulate the request with custom env variable
+post article_url # simulate the request with custom env variable
```
### Testing `flash` notices
@@ -818,7 +818,7 @@ Let's start by adding this assertion to our `test_should_create_article` test:
```ruby
test "should create article" do
assert_difference('Article.count') do
- post :create, params: { article: { title: 'Some title' } }
+ post article_url, params: { article: { title: 'Some title' } }
end
assert_redirected_to article_path(Article.last)
@@ -888,7 +888,7 @@ Let's write a test for the `:show` action:
```ruby
test "should show article" do
article = articles(:one)
- get :show, params: { id: article.id }
+ get '/article', params: { id: article.id }
assert_response :success
end
```
@@ -901,7 +901,7 @@ How about deleting an existing Article?
test "should destroy article" do
article = articles(:one)
assert_difference('Article.count', -1) do
- delete :destroy, params: { id: article.id }
+ delete article_url(article)
end
assert_redirected_to articles_path
@@ -913,7 +913,7 @@ We can also add a test for updating an existing Article.
```ruby
test "should update article" do
article = articles(:one)
- patch :update, params: { id: article.id, article: { title: "updated" } }
+ patch '/article', params: { id: article.id, article: { title: "updated" } }
assert_redirected_to article_path(article)
end
```
@@ -925,7 +925,7 @@ Our test should now look something like this, disregard the other tests we're le
```ruby
require 'test_helper'
-class ArticlesControllerTest < ActionController::TestCase
+class ArticlesControllerTest < ActionDispatch::IntegrationTest
# called before every single test
setup do
@article = articles(:one)
@@ -939,20 +939,20 @@ class ArticlesControllerTest < ActionController::TestCase
test "should show article" do
# Reuse the @article instance variable from setup
- get :show, params: { id: @article.id }
+ get article_url(@article)
assert_response :success
end
test "should destroy article" do
assert_difference('Article.count', -1) do
- delete :destroy, params: { id: @article.id }
+ delete article_url(@article)
end
assert_redirected_to articles_path
end
test "should update article" do
- patch :update, params: { id: @article.id, article: { title: "updated" } }
+ patch article_url(@article), params: { article: { title: "updated" } }
assert_redirected_to article_path(@article)
end
end
@@ -974,7 +974,7 @@ module SignInHelper
end
end
-class ActionController::TestCase
+class ActionDispatch::IntegrationTest
include SignInHelper
end
```
@@ -982,13 +982,13 @@ end
```ruby
require 'test_helper'
-class ProfileControllerTest < ActionController::TestCase
+class ProfileControllerTest < ActionDispatch::IntegrationTest
test "should show profile" do
# helper is now reusable from any controller test case
sign_in users(:david)
- get :show
+ get profile_url
assert_response :success
end
end
@@ -1186,10 +1186,10 @@ Functional testing for mailers involves more than just checking that the email b
```ruby
require 'test_helper'
-class UserControllerTest < ActionController::TestCase
+class UserControllerTest < ActionDispatch::IntegrationTest
test "invite friend" do
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
- post :invite_friend, params: { email: 'friend@example.com' }
+ post invite_friend_url, params: { email: 'friend@example.com' }
end
invite_email = ActionMailer::Base.deliveries.last
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index fa6a01671b..936547a8cc 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -53,7 +53,34 @@ Don't forget to review the difference, to see if there were any unexpected chang
Upgrading from Rails 4.2 to Rails 5.0
-------------------------------------
-### Halting callback chains by returning `false`
+### Ruby 2.2.2+
+
+ToDo...
+
+### Ruby 2.2.2+
+
+ToDo...
+
+### Active Record models now inherit from ApplicationRecord by default
+
+In Rails 4.2 an Active Record model inherits from `ActiveRecord::Base`. In Rails 5.0,
+all models inherit from `ApplicationRecord`.
+
+`ApplicationRecord` is a new superclass for all app models, analogous to app
+controllers subclassing `ApplicationController` instead of
+`ActionController::Base`. This gives apps a single spot to configure app-wide
+model behavior.
+
+When upgrading from Rails 4.2 to Rails 5.0 you need to create an
+`application_record.rb` file in `app/models/` and add the following content:
+
+```
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
+```
+
+### Halting callback chains via `throw(:abort)`
In Rails 4.2, when a 'before' callback returns `false` in Active Record
and Active Model, then the entire callback chain is halted. In other words,
@@ -944,7 +971,7 @@ Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for d
* Rails 4.0 has removed the XML parameters parser. You will need to add the `actionpack-xml_parser` gem if you require this feature.
-* Rails 4.0 changes the default `layout` lookup set using symbols or procs that return nil. To get the "no layout" behavior, return false instead of nil.
+* Rails 4.0 changes the default `layout` lookup set using symbols or procs that return nil. To get the "no layout" behavior, return false instead of nil.
* Rails 4.0 changes the default memcached client from `memcache-client` to `dalli`. To upgrade, simply add `gem 'dalli'` to your `Gemfile`.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 1c42ff2914..48fc6bc9c0 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -81,7 +81,7 @@ Awkward, right? We could pull the function definition out of the click handler,
and turn it into CoffeeScript:
```coffeescript
-paintIt = (element, backgroundColor, textColor) ->
+@paintIt = (element, backgroundColor, textColor) ->
element.style.backgroundColor = backgroundColor
if textColor?
element.style.color = textColor
@@ -107,7 +107,7 @@ attribute to our link, and then bind a handler to the click event of every link
that has that attribute:
```coffeescript
-paintIt = (element, backgroundColor, textColor) ->
+@paintIt = (element, backgroundColor, textColor) ->
element.style.backgroundColor = backgroundColor
if textColor?
element.style.color = textColor
diff --git a/rails.gemspec b/rails.gemspec
index 0286af0a57..d4e78b6aa1 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
s.add_dependency 'activerecord', version
s.add_dependency 'actionmailer', version
s.add_dependency 'activejob', version
+ s.add_dependency 'actioncable', version
s.add_dependency 'railties', version
s.add_dependency 'bundler', '>= 1.3.0', '< 2.0'
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index aa12708669..2c363c55da 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,13 +1,72 @@
+* Bring back `TEST=` env for `rake test` task.
+
+ *Yves Senn*
+
+* Specify log file names or all logs to clear `rake log:clear`
+
+ Specify which logs to clear when using the `rake log:clear` task, e.g. `rake log:clear LOGS=test,staging`
+
+ Clear all logs from log/*.log e.g. `rake log:clear LOGS=all`
+
+ By default `rake log:clear` clears standard environment log files i.e. 'development,test,production'
+
+ *Pramod Shinde*
+
+* Fix using `add_source` with a block after using `gem` in a custom generator.
+
+ *Will Fisher*
+
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
+* Newly generated plugins get a `README.md` in Markdown.
+
+ *Yuji Yaginuma*
+
+* The generated config file for the development environment includes a new
+ config line, commented out, showing how to enable the evented file watcher.
+
+ *Xavier Noria*
+
+* `config.debug_exception_response_format` configures the format used
+ in responses when errors occur in development mode.
+
+ Set `config.debug_exception_response_format` to render an HTML page with
+ debug info (using the value `:default`) or render debug info preserving
+ the response format (using the value `:api`).
+
+ *Jorge Bejar*
+
+* Fix setting exit status code for rake test tasks. The exit status code
+ was not set when tests were fired with `rake`. Now, it is being set and it matches
+ behavior of running tests via `rails` command (`rails test`), so no matter if
+ `rake test` or `rails test` command is used the exit code will be set.
+
+ *Arkadiusz Fal*
+
+* Add Command infrastructure to replace rake.
+
+ Also move `rake dev:cache` to new infrastructure. You'll need to use
+ `rails dev:cache` to toggle development caching from now on.
+
+ *Chuck Callebs*
+
+* Allow use of minitest-rails gem with Rails test runner.
+
+ Fixes #22455.
+
+ *Chris Kottom*
+
+* Add `bin/test` script to rails plugin.
+
+ `bin/test` can use the same API as `bin/rails test`.
+
+ *Yuji Yaginuma*
+
* Make `static_index` part of the `config.public_file_server` config and
call it `public_file_server.index_name`.
*Yuki Nishijima*
-* Generated `Gemfile`s for new applications include a new dependency on
- [listen](https://github.com/guard/listen) commented out.
-
- *Puneet Agarwal* and *Xavier Noria*
-
* Deprecate `serve_static_files` in favor of `public_file_server.enabled`.
Unifies the static asset options under `public_file_server`.
@@ -156,7 +215,7 @@
* Fix STATS_DIRECTORIES already defined warning when running rake from within
the top level directory of an engine that has a test app.
- Fixes #20510
+ Fixes #20510.
*Ersin Akinci*
@@ -204,13 +263,13 @@
middleware for API apps & generators generates the right files,
folders and configurations.
- *Santiago Pastorino & Jorge Bejar*
+ *Santiago Pastorino*, *Jorge Bejar*
* Make generated scaffold functional tests work inside engines.
*Yuji Yaginuma*
-* Generator a `.keep` file in the `tmp` folder by default as many scripts
+* Generate a `.keep` file in the `tmp` folder by default as many scripts
assume the existence of this folder and most would fail if it is absent.
See #20299.
@@ -276,7 +335,7 @@
* Created rake restart task. Restarts your Rails app by touching the
`tmp/restart.txt`.
- Fixes #18876.
+ See #18876.
*Hyonjee Joo*
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 7c2197229d..1f496cf280 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc
index 8d847eaa1c..ce024563c4 100644
--- a/railties/RDOC_MAIN.rdoc
+++ b/railties/RDOC_MAIN.rdoc
@@ -57,7 +57,7 @@ can read more about Action Pack in its {README}[link:files/actionpack/README_rdo
* 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 Tutorial}[http://www.railstutorial.org/book].
* {Ruby on \Rails Guides}[http://guides.rubyonrails.org].
* {The API Documentation}[http://api.rubyonrails.org].
diff --git a/railties/Rakefile b/railties/Rakefile
index 73d881b318..cf130a5f14 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -5,33 +5,20 @@ task :default => :test
desc "Run all unit tests"
task :test => 'test:isolated'
-dash_i = [
- 'test',
- 'lib',
- "#{File.dirname(__FILE__)}/../activesupport/lib",
- "#{File.dirname(__FILE__)}/../actionpack/lib",
- "#{File.dirname(__FILE__)}/../activemodel/lib"
-]
-
-dash_i.reverse_each do |x|
- $:.unshift x unless $:.include? x
-end
-$-w = true
-
-require 'bundler/setup' unless defined?(Bundler)
-require 'active_support'
-
namespace :test do
task :isolated do
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")
- puts "#{FileUtils::RUBY} -w -I#{dash_i.join ':'} #{file}"
-
- # We could run these in parallel, but pretty much all of the
- # railties tests already run in parallel, so ¯\_(⊙︿⊙)_/¯
- Process.waitpid fork { ARGV.clear; load file }
+ dash_i = [
+ 'test',
+ 'lib',
+ "#{File.dirname(__FILE__)}/../activesupport/lib",
+ "#{File.dirname(__FILE__)}/../actionpack/lib",
+ "#{File.dirname(__FILE__)}/../activemodel/lib"
+ ]
+ ruby "-w", "-I#{dash_i.join ':'}", file
end
end
end
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 45361fca83..11f4d5c4bc 100644
--- a/railties/lib/rails/all.rb
+++ b/railties/lib/rails/all.rb
@@ -1,16 +1,17 @@
require "rails"
%w(
- active_record
- action_controller
- action_view
- action_mailer
- active_job
- rails/test_unit
- sprockets
-).each do |framework|
+ active_record/railtie
+ action_controller/railtie
+ action_view/railtie
+ action_mailer/railtie
+ active_job/railtie
+ action_cable/engine
+ rails/test_unit/railtie
+ sprockets/railtie
+).each do |railtie|
begin
- require "#{framework}/railtie"
+ require "#{railtie}"
rescue LoadError
end
end
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index a082932632..d478bbf9e8 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -57,6 +57,13 @@ module Rails
)
},
+ 'actioncable' => {
+ :include => %w(
+ README.md
+ lib/action_cable/**/*.rb
+ )
+ },
+
'railties' => {
:include => %w(
README.rdoc
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 77efe3248d..cac31e1eed 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -219,7 +219,11 @@ module Rails
# config.middleware.use ExceptionNotifier, config_for(:exception_notification)
# end
def config_for(name, env: Rails.env)
- yaml = Pathname.new("#{paths["config"].existent.first}/#{name}.yml")
+ if name.is_a?(Pathname)
+ yaml = name
+ else
+ yaml = Pathname.new("#{paths["config"].existent.first}/#{name}.yml")
+ end
if yaml.exist?
require "erb"
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 80733c2d90..65cff1561a 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -24,35 +24,36 @@ module Rails
def initialize(*)
super
self.encoding = "utf-8"
- @allow_concurrency = nil
- @consider_all_requests_local = false
- @filter_parameters = []
- @filter_redirect = []
- @helpers_paths = []
- @public_file_server = ActiveSupport::OrderedOptions.new
- @public_file_server.enabled = true
- @public_file_server.index_name = "index"
- @force_ssl = false
- @ssl_options = {}
- @session_store = :cookie_store
- @session_options = {}
- @time_zone = "UTC"
- @beginning_of_week = :monday
- @log_level = nil
- @generators = app_generators
- @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
- @railties_order = [:all]
- @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
- @reload_classes_only_on_change = true
- @file_watcher = file_update_checker
- @exceptions_app = nil
- @autoflush_log = true
- @log_formatter = ActiveSupport::Logger::SimpleFormatter.new
- @eager_load = nil
- @secret_token = nil
- @secret_key_base = nil
- @api_only = false
- @x = Custom.new
+ @allow_concurrency = nil
+ @consider_all_requests_local = false
+ @filter_parameters = []
+ @filter_redirect = []
+ @helpers_paths = []
+ @public_file_server = ActiveSupport::OrderedOptions.new
+ @public_file_server.enabled = true
+ @public_file_server.index_name = "index"
+ @force_ssl = false
+ @ssl_options = {}
+ @session_store = :cookie_store
+ @session_options = {}
+ @time_zone = "UTC"
+ @beginning_of_week = :monday
+ @log_level = nil
+ @generators = app_generators
+ @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
+ @railties_order = [:all]
+ @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
+ @reload_classes_only_on_change = true
+ @file_watcher = ActiveSupport::FileUpdateChecker
+ @exceptions_app = nil
+ @autoflush_log = true
+ @log_formatter = ActiveSupport::Logger::SimpleFormatter.new
+ @eager_load = nil
+ @secret_token = nil
+ @secret_key_base = nil
+ @api_only = false
+ @debug_exception_response_format = nil
+ @x = Custom.new
end
def static_cache_control=(value)
@@ -95,6 +96,16 @@ module Rails
def api_only=(value)
@api_only = value
generators.api_only = value
+
+ @debug_exception_response_format ||= :api
+ end
+
+ def debug_exception_response_format
+ @debug_exception_response_format || :default
+ end
+
+ def debug_exception_response_format=(value)
+ @debug_exception_response_format = value
end
def paths
@@ -180,30 +191,21 @@ module Rails
SourceAnnotationExtractor::Annotation
end
- private
- def file_update_checker
- if defined?(Listen) && Listen::Adapter.select() != Listen::Adapter::Polling
- ActiveSupport::FileEventedUpdateChecker
- else
- ActiveSupport::FileUpdateChecker
- end
+ class Custom #:nodoc:
+ def initialize
+ @configurations = Hash.new
end
- class Custom #:nodoc:
- def initialize
- @configurations = Hash.new
- end
-
- def method_missing(method, *args)
- if method =~ /=$/
- @configurations[$`.to_sym] = args.first
- else
- @configurations.fetch(method) {
- @configurations[method] = ActiveSupport::OrderedOptions.new
- }
- end
+ def method_missing(method, *args)
+ if method =~ /=$/
+ @configurations[$`.to_sym] = args.first
+ else
+ @configurations.fetch(method) {
+ @configurations[method] = ActiveSupport::OrderedOptions.new
+ }
end
end
+ end
end
end
end
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 5cb5bfb8b7..4f1cc0703d 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -57,7 +57,7 @@ module Rails
# Must come after Rack::MethodOverride to properly log overridden methods
middleware.use ::Rails::Rack::Logger, config.log_tags
middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app
- middleware.use ::ActionDispatch::DebugExceptions, app
+ middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
unless config.cache_classes
@@ -68,7 +68,7 @@ module Rails
middleware.use ::ActionDispatch::Cookies unless config.api_only
if !config.api_only && config.session_store
- if config.force_ssl && !config.session_options.key?(:secure)
+ if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure)
config.session_options[:secure] = true
end
middleware.use config.session_store, config.session_options
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
new file mode 100644
index 0000000000..f7753cbb83
--- /dev/null
+++ b/railties/lib/rails/command.rb
@@ -0,0 +1,70 @@
+require 'rails/commands/commands_tasks'
+
+module Rails
+ class Command #:nodoc:
+ attr_reader :argv
+
+ def initialize(argv = [])
+ @argv = argv
+
+ @option_parser = build_option_parser
+ @options = {}
+ end
+
+ def self.run(task_name, argv)
+ command_name = command_name_for(task_name)
+
+ if command = command_for(command_name)
+ command.new(argv).run(command_name)
+ else
+ Rails::CommandsTasks.new(argv).run_command!(task_name)
+ end
+ end
+
+ def run(command_name)
+ parse_options_for(command_name)
+ @option_parser.parse! @argv
+
+ public_send(command_name)
+ end
+
+ def self.options_for(command_name, &options_to_parse)
+ @@command_options[command_name] = options_to_parse
+ end
+
+ def self.set_banner(command_name, banner)
+ options_for(command_name) { |opts, _| opts.banner = banner }
+ end
+
+ private
+ @@commands = []
+ @@command_options = {}
+
+ def parse_options_for(command_name)
+ @@command_options.fetch(command_name, proc {}).call(@option_parser, @options)
+ end
+
+ def build_option_parser
+ OptionParser.new do |opts|
+ opts.on('-h', '--help', 'Show this help.') do
+ puts opts
+ exit
+ end
+ end
+ end
+
+ def self.inherited(command)
+ @@commands << command
+ end
+
+ def self.command_name_for(task_name)
+ task_name.gsub(':', '_').to_sym
+ end
+
+ def self.command_for(command_name)
+ @@commands.find do |command|
+ command.public_instance_methods.include?(command_name)
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 12bd73db24..7627fcf5a0 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -7,12 +7,13 @@ aliases = {
"s" => "server",
"db" => "dbconsole",
"r" => "runner",
- "t" => "test",
+ "t" => "test"
}
command = ARGV.shift
command = aliases[command] || command
-require 'rails/commands/commands_tasks'
+require 'rails/command'
+require 'rails/commands/dev_cache'
-Rails::CommandsTasks.new(ARGV).run_command!(command)
+Rails::Command.run(command, ARGV)
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
index 685d55eea8..da3b9452d5 100644
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ b/railties/lib/rails/commands/commands_tasks.rb
@@ -1,3 +1,5 @@
+require 'rails/commands/rake_proxy'
+
module Rails
# This is a class which takes in a rails command and initiates the appropriate
# initiation sequence.
@@ -5,6 +7,8 @@ module Rails
# Warning: This class mutates ARGV because some commands require manipulating
# it before they are run.
class CommandsTasks # :nodoc:
+ include Rails::RakeProxy
+
attr_reader :argv
HELP_MESSAGE = <<-EOT
@@ -20,14 +24,18 @@ The most common rails commands are:
new Create a new Rails application. "rails new my_app" creates a
new application called MyApp in "./my_app"
-In addition to those, there are:
- destroy Undo code generated with "generate" (short-cut alias: "d")
- plugin new Generates skeleton for developing a Rails plugin
- runner Run a piece of code in the application environment (short-cut alias: "r")
-
All commands can be run with -h (or --help) for more information.
+
+In addition to those commands, there are:
EOT
+ ADDITIONAL_COMMANDS = [
+ [ 'destroy', 'Undo code generated with "generate" (short-cut alias: "d")' ],
+ [ 'plugin new', 'Generates skeleton for developing a Rails plugin' ],
+ [ 'runner',
+ 'Run a piece of code in the application environment (short-cut alias: "r")' ]
+ ]
+
COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help test)
def initialize(argv)
@@ -36,10 +44,11 @@ EOT
def run_command!(command)
command = parse_command(command)
+
if COMMAND_WHITELIST.include?(command)
send(command)
else
- write_error_message(command)
+ run_rake_task(command)
end
end
@@ -110,6 +119,7 @@ EOT
def help
write_help_message
+ write_commands ADDITIONAL_COMMANDS + formatted_rake_tasks
end
private
@@ -151,24 +161,9 @@ EOT
puts HELP_MESSAGE
end
- # Output an error message stating that the attempted command is not a valid rails command.
- # Run the attempted command as a rake command with the --dry-run flag. If successful, suggest
- # to the user that they possibly meant to run the given rails command as a rake command.
- # Append the help message.
- #
- # Example:
- # $ rails db:migrate
- # Error: Command 'db:migrate' not recognized
- # Did you mean: `$ rake db:migrate` ?
- # (Help message output)
- #
- def write_error_message(command)
- puts "Error: Command '#{command}' not recognized"
- if %x{rake #{command} --dry-run 2>&1 } && $?.success?
- puts "Did you mean: `$ rake #{command}` ?\n\n"
- end
- write_help_message
- exit(1)
+ def write_commands(commands)
+ width = commands.map { |name, _| name.size }.max || 10
+ commands.each { |command| printf(" %-#{width}s %s\n", *command) }
end
def parse_command(command)
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index dca60f948f..2c36edfa3f 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -65,7 +65,7 @@ module Rails
'sslca' => '--ssl-ca',
'sslcert' => '--ssl-cert',
'sslcapath' => '--ssl-capath',
- 'sslcipher' => '--ssh-cipher',
+ 'sslcipher' => '--ssl-cipher',
'sslkey' => '--ssl-key'
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
diff --git a/railties/lib/rails/commands/dev_cache.rb b/railties/lib/rails/commands/dev_cache.rb
new file mode 100644
index 0000000000..ec96e8f630
--- /dev/null
+++ b/railties/lib/rails/commands/dev_cache.rb
@@ -0,0 +1,21 @@
+require 'rails/command'
+
+module Rails
+ module Commands
+ # This is a wrapper around the Rails dev:cache command
+ class DevCache < Command
+ set_banner :dev_cache, 'Toggle development mode caching on/off'
+ def dev_cache
+ if File.exist? 'tmp/caching-dev.txt'
+ File.delete 'tmp/caching-dev.txt'
+ puts 'Development mode is no longer being cached.'
+ else
+ FileUtils.touch 'tmp/caching-dev.txt'
+ puts 'Development mode is now being cached.'
+ end
+
+ FileUtils.touch 'tmp/restart.txt'
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/rake_proxy.rb b/railties/lib/rails/commands/rake_proxy.rb
new file mode 100644
index 0000000000..f7d5df6b2f
--- /dev/null
+++ b/railties/lib/rails/commands/rake_proxy.rb
@@ -0,0 +1,34 @@
+require 'rake'
+require 'active_support'
+
+module Rails
+ module RakeProxy #:nodoc:
+ private
+ def run_rake_task(command)
+ ARGV.unshift(command) # Prepend the command, so Rake knows how to run it.
+
+ Rake.application.standard_exception_handling do
+ Rake.application.init('rails')
+ Rake.application.load_rakefile
+ Rake.application.top_level
+ end
+ end
+
+ def rake_tasks
+ return @rake_tasks if defined?(@rake_tasks)
+
+ ActiveSupport::Deprecation.silence do
+ require_application_and_environment!
+ end
+
+ Rake::TaskManager.record_task_metadata = true
+ Rake.application.instance_variable_set(:@name, 'rails')
+ Rails.application.load_tasks
+ @rake_tasks = Rake.application.tasks.select(&:comment)
+ end
+
+ def formatted_rake_tasks
+ rake_tasks.map { |t| [ t.name_with_args, t.comment ] }
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 86bce9b2fe..babb197ba1 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -58,5 +58,11 @@ elsif File.exist?(code_or_file)
$0 = code_or_file
Kernel.load code_or_file
else
- eval(code_or_file, binding, __FILE__, __LINE__)
+ begin
+ eval(code_or_file, binding, __FILE__, __LINE__)
+ rescue SyntaxError,NameError => err
+ $stderr.puts "Please specify a valid ruby command or the path of a script to run."
+ $stderr.puts "Run '#{$0} -h' for help."
+ exit 1
+ end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index d3ea441f8e..27cbaf360a 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -6,6 +6,8 @@ require 'rails'
module Rails
class Server < ::Rack::Server
class Options
+ DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze
+
def parse!(args)
args, options = args.dup, {}
@@ -91,7 +93,7 @@ module Rails
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
caching: false,
- pid: File.expand_path("tmp/pids/server.pid")
+ pid: Options::DEFAULT_PID_PATH
})
end
@@ -133,11 +135,13 @@ module Rails
def log_to_stdout
wrapped_app # touch the app so the logger is set up
- console = ActiveSupport::Logger.new($stdout)
+ console = ActiveSupport::Logger.new(STDOUT)
console.formatter = Rails.logger.formatter
console.level = Rails.logger.level
- Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ end
end
end
end
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 7d74b1bfe5..92230a74e4 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -8,7 +8,7 @@ module Rails
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 2645102619..e3d79521e7 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -208,6 +208,7 @@ module Rails
"#{test}:model",
"#{test}:scaffold",
"#{test}:view",
+ "#{test}:job",
"#{template}:controller",
"#{template}:scaffold",
"#{template}:mailer",
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 5bbd2f1aed..cd83175da8 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -75,7 +75,7 @@ module Rails
in_root do
if block
- append_file "Gemfile", "source #{quote(source)} do", force: true
+ append_file "Gemfile", "\nsource #{quote(source)} do", force: true
@in_group = true
instance_eval(&block)
@in_group = false
diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb
index cffdef6ec9..d664b07652 100644
--- a/railties/lib/rails/generators/actions/create_migration.rb
+++ b/railties/lib/rails/generators/actions/create_migration.rb
@@ -3,7 +3,7 @@ require 'thor/actions'
module Rails
module Generators
module Actions
- class CreateMigration < Thor::Actions::CreateFile
+ class CreateMigration < Thor::Actions::CreateFile #:nodoc:
def migration_dir
File.dirname(@destination)
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 56c9d3e354..297ccb1dbf 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -26,6 +26,12 @@ module Rails
class_option :template, type: :string, aliases: '-m',
desc: "Path to some #{name} template (can be a filesystem path or URL)"
+ class_option :database, type: :string, aliases: '-d', default: 'sqlite3',
+ desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
+
+ class_option :javascript, type: :string, aliases: '-j', default: 'jquery',
+ desc: 'Preconfigure for selected JavaScript library'
+
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
@@ -45,34 +51,31 @@ module Rails
class_option :skip_active_record, type: :boolean, aliases: '-O', default: false,
desc: 'Skip Active Record files'
+ class_option :skip_action_cable, type: :boolean, aliases: '-C', default: false,
+ desc: 'Skip Action Cable files'
+
class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false,
desc: 'Skip Sprockets files'
class_option :skip_spring, type: :boolean, default: false,
desc: "Don't install Spring application preloader"
- class_option :database, type: :string, aliases: '-d', default: 'sqlite3',
- desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
-
- class_option :javascript, type: :string, aliases: '-j', default: 'jquery',
- desc: 'Preconfigure for selected JavaScript library'
-
class_option :skip_javascript, type: :boolean, aliases: '-J', default: false,
desc: 'Skip JavaScript files'
- class_option :dev, type: :boolean, default: false,
- desc: "Setup the #{name} with Gemfile pointing to your Rails checkout"
-
- class_option :edge, type: :boolean, default: false,
- desc: "Setup the #{name} with Gemfile pointing to Rails repository"
-
class_option :skip_turbolinks, type: :boolean, default: false,
desc: 'Skip turbolinks gem'
class_option :skip_test, type: :boolean, aliases: '-T', default: false,
desc: 'Skip test files'
- class_option :rc, type: :string, default: false,
+ class_option :dev, type: :boolean, default: false,
+ desc: "Setup the #{name} with Gemfile pointing to your Rails checkout"
+
+ class_option :edge, type: :boolean, default: false,
+ desc: "Setup the #{name} with Gemfile pointing to Rails repository"
+
+ class_option :rc, type: :string, default: nil,
desc: "Path to file containing extra configuration options for rails command"
class_option :no_rc, type: :boolean, default: false,
@@ -168,7 +171,7 @@ module Rails
end
def include_all_railties?
- options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets).none?
+ options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets, :skip_action_cable).none?
end
def comment_if(value)
@@ -217,12 +220,7 @@ module Rails
def rails_gemfile_entry
dev_edge_common = [
- GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'),
- GemfileEntry.github('sprockets', 'rails/sprockets'),
- GemfileEntry.github('sass-rails', 'rails/sass-rails'),
- GemfileEntry.github('arel', 'rails/arel'),
- GemfileEntry.github('rack', 'rack/rack')
- ]
+ ]
if options.dev?
[
GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH)
@@ -233,11 +231,25 @@ module Rails
] + dev_edge_common
else
[GemfileEntry.version('rails',
- Rails::VERSION::STRING,
+ rails_version_specifier,
"Bundle edge Rails instead: gem 'rails', github: 'rails/rails'")]
end
end
+ def rails_version_specifier(gem_version = Rails.gem_version)
+ if gem_version.prerelease?
+ next_series = gem_version
+ next_series = next_series.bump while next_series.segments.size > 2
+
+ [">= #{gem_version}", "< #{next_series}"]
+ elsif gem_version.segments.size == 3
+ "~> #{gem_version}"
+ else
+ patch = gem_version.segments[0, 3].join(".")
+ ["~> #{patch}", ">= #{gem_version}"]
+ end
+ end
+
def gem_for_database
# %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql )
case options[:database]
@@ -269,6 +281,8 @@ module Rails
return [] if options[:skip_sprockets]
gems = []
+ gems << GemfileEntry.version('sass-rails', '~> 5.0',
+ 'Use SCSS for stylesheets')
gems << GemfileEntry.version('uglifier',
'>= 1.3.0',
@@ -278,10 +292,8 @@ module Rails
end
def jbuilder_gemfile_entry
- return [] if options[:api]
-
comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder'
- GemfileEntry.version('jbuilder', '~> 2.0', comment)
+ GemfileEntry.new 'jbuilder', '~> 2.0', comment, {}, options[:api]
end
def coffee_gemfile_entry
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 65563aa6db..bc249aa5e5 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -9,13 +9,6 @@ module Erb # :nodoc:
view_base_path = File.join("app/views", class_path, file_name + '_mailer')
empty_directory view_base_path
- if self.behavior == :invoke
- formats.each do |format|
- layout_path = File.join("app/views/layouts", filename_with_extensions("mailer", format))
- template filename_with_extensions(:layout, format), layout_path
- end
- end
-
actions.each do |action|
@action = action
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb
deleted file mode 100644
index 93110e74ad..0000000000
--- a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
- <body>
- <%%= yield %>
- </body>
-</html>
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 d99b27cb2d..519b6c8603 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -14,15 +14,15 @@
<% attributes.each do |attribute| -%>
<div class="field">
<% if attribute.password_digest? -%>
- <%%= f.label :password %><br>
+ <%%= f.label :password %>
<%%= f.password_field :password %>
</div>
<div class="field">
- <%%= f.label :password_confirmation %><br>
+ <%%= f.label :password_confirmation %>
<%%= f.password_field :password_confirmation %>
<% else -%>
- <%%= f.label :<%= attribute.column_name %> %><br>
+ <%%= f.label :<%= attribute.column_name %> %>
<%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %>
<% end -%>
</div>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 243694f38e..efbf51ddfb 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -129,6 +129,18 @@ module Rails
uncountable? ? "#{plural_table_name}_index" : plural_table_name
end
+ def show_helper
+ "#{singular_table_name}_url(@#{singular_table_name})"
+ end
+
+ def edit_helper
+ "edit_#{show_helper}"
+ end
+
+ def new_helper
+ "new_#{singular_table_name}_url"
+ end
+
def singular_table_name
@singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name)
end
@@ -149,6 +161,10 @@ module Rails
@route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name
end
+ def url_helper_prefix
+ @url_helper_prefix ||= (class_path + [file_name]).join('_')
+ end
+
# Tries to retrieve the application name or simply return application.
def application_name
if defined?(Rails) && Rails.application
diff --git a/railties/lib/rails/generators/rails/app/USAGE b/railties/lib/rails/generators/rails/app/USAGE
index 691095f33f..28df6ebf44 100644
--- a/railties/lib/rails/generators/rails/app/USAGE
+++ b/railties/lib/rails/generators/rails/app/USAGE
@@ -12,4 +12,3 @@ Example:
rails new ~/Code/Ruby/weblog
This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
- See the README in the newly created application to get going.
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index b813b083f4..f4deec7135 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -57,8 +57,7 @@ module Rails
directory 'app'
keep_file 'app/assets/images'
- keep_file 'app/mailers'
- keep_file 'app/models'
+ empty_directory_with_keep_file 'app/assets/javascripts/channels' unless options[:skip_action_cable]
keep_file 'app/controllers/concerns'
keep_file 'app/models/concerns'
@@ -83,6 +82,7 @@ module Rails
directory "environments"
directory "initializers"
directory "locales"
+ directory "redis" unless options[:skip_action_cable]
end
end
@@ -293,16 +293,45 @@ module Rails
end
end
+ def delete_application_record_skipping_active_record
+ if options[:skip_active_record]
+ remove_file 'app/models/application_record.rb'
+ end
+ end
+
+ def delete_action_mailer_files_skipping_action_mailer
+ if options[:skip_action_mailer]
+ remove_file 'app/mailers/application_mailer.rb'
+ remove_file 'app/views/layouts/mailer.html.erb'
+ remove_file 'app/views/layouts/mailer.text.erb'
+ end
+ end
+
def delete_active_record_initializers_skipping_active_record
if options[:skip_active_record]
remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb'
end
end
+ def delete_action_cable_files_skipping_action_cable
+ if options[:skip_action_cable]
+ remove_file 'config/redis/cable.yml'
+ remove_file 'app/assets/javascripts/cable.coffee'
+ remove_dir 'app/channels'
+ end
+ end
+
def delete_non_api_initializers_if_api_option
if options[:api]
remove_file 'config/initializers/session_store.rb'
remove_file 'config/initializers/cookies_serializer.rb'
+ remove_file 'config/initializers/request_forgery_protection.rb'
+ end
+ end
+
+ def delete_api_initializers
+ unless options[:api]
+ remove_file 'config/initializers/cors.rb'
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 4f3f59cc22..da5af4eefc 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -12,19 +12,16 @@ source 'https://rubygems.org'
<% end -%>
<% end -%>
+# Use Puma as the app server
+gem 'puma'
+
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
-# Use Unicorn as the app server
-# gem 'unicorn'
-
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
<%- if options.api? -%>
-# Use ActiveModelSerializers to serialize JSON responses
-gem 'active_model_serializers', '~> 0.10.0.rc2'
-
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'
@@ -41,17 +38,13 @@ group :development do
<%- if options.dev? || options.edge? -%>
gem 'web-console', github: 'rails/web-console'
<%- else -%>
- gem 'web-console', '~> 2.0'
+ gem 'web-console', '~> 3.0'
<%- end -%>
<%- end -%>
<% if spring_install? -%>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
<% end -%>
-
- # Loading the listen gem enables an evented file system monitor. Check
- # https://github.com/guard/listen#listen-adapters if on Windows or *BSD.
- # gem 'listen', '~> 3.0.4'
end
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt
index f80631bac6..f4ee1409af 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt
@@ -1,4 +1,3 @@
-
<% unless options.api? -%>
//= link_tree ../images
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
new file mode 100644
index 0000000000..07934d026f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
@@ -0,0 +1,11 @@
+# Action Cable provides the framework to deal with WebSockets in Rails.
+# You can generate new channels where WebSocket features live using the rails generate channel command.
+#
+# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb).
+#
+#= require action_cable
+#= require_self
+#= require_tree ./channels
+#
+# @App ||= {}
+# App.cable = ActionCable.createConsumer()
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
new file mode 100644
index 0000000000..438c84154d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
new file mode 100644
index 0000000000..965046f3c7
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ end
+end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb
index d25d8892dd..286b2239d1 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb
@@ -1,4 +1,4 @@
class ApplicationMailer < ActionMailer::Base
- default from: "from@example.com"
+ default from: 'from@example.com'
layout 'mailer'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb
new file mode 100644
index 0000000000..10a4cba84d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb
@@ -0,0 +1,3 @@
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index 8bb4440f55..68b5c051b2 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -2,6 +2,11 @@
<html>
<head>
<title><%= camelized %></title>
+ <%%= csrf_meta_tags %>
+ <%- unless options[:skip_action_cable] -%>
+ <%%= action_cable_meta_tag %>
+ <%- end -%>
+
<%- if options[:skip_javascript] -%>
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%- else -%>
@@ -13,12 +18,9 @@
<%%= javascript_include_tag 'application' %>
<%- end -%>
<%- end -%>
- <%%= csrf_meta_tags %>
</head>
<body>
-
<%%= yield %>
-
</body>
</html>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.html.erb.tt
new file mode 100644
index 0000000000..55f3675d49
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.html.erb.tt
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <style>
+ /* Email styles need to be inline */
+ </style>
+ </head>
+
+ <body>
+ <%%= yield %>
+ </body>
+</html>
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.text.erb.tt
index 6363733e6e..6363733e6e 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.text.erb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
index 0c8b179827..df88bfd3bc 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -23,11 +23,11 @@ chdir APP_ROOT do
# end
puts "\n== Preparing database =="
- system! 'ruby bin/rake db:setup'
+ system! 'bin/rails db:setup'
puts "\n== Removing old logs and tempfiles =="
- system! 'ruby bin/rake log:clear tmp:clear'
+ system! 'bin/rails log:clear tmp:clear'
puts "\n== Restarting application server =="
- system! 'ruby bin/rake restart'
+ system! 'bin/rails restart'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update
index 9830e6b29a..c6ed3ae64b 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update
@@ -18,11 +18,11 @@ chdir APP_ROOT do
system 'bundle check' or system! 'bundle install'
puts "\n== Updating database =="
- system! 'bin/rake db:migrate'
+ system! 'bin/rails db:migrate'
puts "\n== Removing old logs and tempfiles =="
- system! 'bin/rake log:clear tmp:clear'
+ system! 'bin/rails log:clear tmp:clear'
puts "\n== Restarting application server =="
- system! 'bin/rake restart'
+ system! 'bin/rails restart'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
deleted file mode 100644
index bd83b25412..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ /dev/null
@@ -1,4 +0,0 @@
-# This file is used by Rack-based servers to start the application.
-
-require ::File.expand_path('../config/environment', __FILE__)
-run Rails.application
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
new file mode 100644
index 0000000000..70556fcc99
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
@@ -0,0 +1,11 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+<%- unless options[:skip_action_cable] -%>
+
+# Action Cable uses EventMachine which requires that all classes are loaded in advance
+Rails.application.eager_load!
+require 'action_cable/process/logging'
+<%- end -%>
+
+run Rails.application
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 ddd0fcade1..cb83364360 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -11,6 +11,7 @@ require "active_job/railtie"
require "action_controller/railtie"
<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
+<%= comment_if :skip_action_cable %>require "action_cable/engine"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
<%= comment_if :skip_test %>require "rails/test_unit/railtie"
<% end -%>
@@ -24,14 +25,6 @@ module <%= app_const_base %>
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
-
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
- # Run "rake time:zones:all" for a time zone names list. Default is UTC.
- # config.time_zone = 'Central Time (US & Canada)'
-
- # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
- # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
- # config.i18n.default_locale = :de
<%- if options[:api] -%>
# Only loads a smaller set of middleware suitable for API only apps.
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 4dd20a9d2e..fd41372d9c 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
@@ -10,7 +10,7 @@ Rails.application.configure do
config.eager_load = false
# Show full error reports.
- config.consider_all_requests_local = true
+ config.consider_all_requests_local = true
# Enable/disable caching. By default caching is disabled.
if Rails.root.join('tmp/caching-dev.txt').exist?
@@ -23,7 +23,6 @@ Rails.application.configure do
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
-
<%- unless options.skip_action_mailer? -%>
# Don't care if the mailer can't send.
@@ -56,4 +55,8 @@ Rails.application.configure do
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
+
+ # Use an evented file watcher to asynchronously detect changes in source code,
+ # routes, locales, etc. This feature depends on the listen gem.
+ # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index a5302550fa..82509f5ef5 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
@@ -40,6 +40,12 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
+ <%- unless options[:skip_action_cable] -%>
+ # Action Cable endpoint configuration
+ # config.action_cable.url = 'wss://example.com/cable'
+ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
+ <%- end -%>
+
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
@@ -48,7 +54,7 @@ Rails.application.configure do
config.log_level = :debug
# Prepend all log lines with the following tags.
- # config.log_tags = [ :subdomain, :request_id ]
+ config.log_tags = [ :request_id ]
# Use a different logger for distributed setups.
# require 'syslog/logger'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
index 30c4f89792..f613b40f80 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
@@ -1,4 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Require `belongs_to` associations by default.
+# Require `belongs_to` associations by default. This is a new Rails 5.0
+# default, so it is introduced as a configuration option to ensure that apps
+# made on earlier versions of Rails are not affected when upgrading.
Rails.application.config.active_record.belongs_to_required_by_default = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
index ea930f54da..51639b67a0 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
@@ -1,5 +1,5 @@
-## Change renderer defaults here.
-#
+# Be sure to restart your server when you modify this file.
+
# ApplicationController.renderer.defaults.merge!(
# http_host: 'example.org',
# https: false
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
index a70a1b9cde..649e82280e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
@@ -1,4 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Do not halt callback chains when a callback returns false.
+# Do not halt callback chains when a callback returns false. This is a new
+# Rails 5.0 default, so it is introduced as a configuration option to ensure
+# that apps made with earlier versions of Rails are not affected when upgrading.
ActiveSupport.halt_callback_chains_on_return_false = false
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
index 7f70458dee..5a6a32d371 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
@@ -1,3 +1,5 @@
# Be sure to restart your server when you modify this file.
+# Specify a serializer for the signed and encrypted cookie jars.
+# Valid options are :json, :marshal, and :hybrid.
Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb
new file mode 100644
index 0000000000..1f569dedfd
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Enable per-form CSRF tokens.
+Rails.application.config.action_controller.per_form_csrf_tokens = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb
new file mode 100644
index 0000000000..3eab78a885
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Enable origin-checking CSRF mitigation.
+Rails.application.config.action_controller.forgery_protection_origin_check = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml
new file mode 100644
index 0000000000..0176be24f9
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml
@@ -0,0 +1,9 @@
+# Action Cable uses Redis to administer connections, channels, and sending/receiving messages over the WebSocket.
+production:
+ url: redis://localhost:6379/1
+
+development:
+ url: redis://localhost:6379/2
+
+test:
+ url: redis://localhost:6379/3
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 787824f888..8293c8a483 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,3 +1,6 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+
+ # Serve websocket cable requests in-process
+ # mount ActionCable.server => '/cable'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
index 4edb1e857e..1beea2accd 100644
--- a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
@@ -1,7 +1,7 @@
# This file should contain all the record creation needed to seed the database with its default values.
-# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
+# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
-# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
-# Mayor.create(name: 'Emanuel', city: cities.first)
+# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
+# Character.create(name: 'Luke', movie: movies.first)
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 1b8cf8a9fa..0e66cc4237 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -20,3 +20,6 @@
!/log/.keep
!/tmp/.keep
<% end -%>
+
+# Ignore Byebug command history file.
+.byebug_history
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 11daa5c3cb..025bcf4774 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -8,14 +8,14 @@ Description:
As a special case, specifying 'password:digest' will generate a
password_digest field of string type, and configure your generated model and
- tests for use with ActiveModel has_secure_password (assuming the default ORM
+ tests for use with Active Model has_secure_password (assuming the default ORM
and test framework are being used).
You don't have to think up every attribute up front, but it helps to
sketch out a few so you can start working with the model immediately.
This generator invokes your configured ORM and test framework, which
- defaults to ActiveRecord and TestUnit.
+ defaults to Active Record and TestUnit.
Finally, if --parent option is given, it's used as superclass of the
created model. This allows you create Single Table Inheritance models.
@@ -91,7 +91,7 @@ Available field types:
Examples:
`rails generate model account`
- For ActiveRecord and TestUnit it creates:
+ For Active Record and TestUnit it creates:
Model: app/models/account.rb
Test: test/models/account_test.rb
@@ -104,7 +104,7 @@ Examples:
`rails generate model admin/account`
- For ActiveRecord and TestUnit it creates:
+ For Active Record and TestUnit it creates:
Module: app/models/admin.rb
Model: app/models/admin/account.rb
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index eeeef430bb..7d7477de75 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -37,7 +37,7 @@ module Rails
end
def readme
- template "README.rdoc"
+ template "README.md"
end
def gemfile
@@ -148,9 +148,8 @@ task default: :test
end
def bin(force = false)
- return unless engine?
-
- directory "bin", force: force do |content|
+ bin_file = engine? ? 'bin/rails.tt' : 'bin/test.tt'
+ template bin_file, force: force do |content|
"#{shebang}\n" + content
end
chmod "bin", 0755, verbose: false
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
index 3f5682b0c1..d84d1aabdb 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
@@ -14,9 +14,9 @@ Gem::Specification.new do |s|
s.description = "TODO: Description of <%= camelized_modules %>."
s.license = "MIT"
- s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
+ s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
- <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>"
+ <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "<%= Array(rails_version_specifier).join('", "') %>"
<% unless options[:skip_active_record] -%>
s.add_development_dependency "<%= gem_for_database[0] %>"
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index f085a2577a..22a4548ff2 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -1,7 +1,7 @@
source 'https://rubygems.org'
<% if options[:skip_gemspec] -%>
-<%= '# ' if options.dev? || options.edge? -%>gem 'rails', '~> <%= Rails::VERSION::STRING %>'
+<%= '# ' if options.dev? || options.edge? -%>gem 'rails', '<%= Array(rails_version_specifier).join("', '") %>'
<% 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/templates/README.md b/railties/lib/rails/generators/rails/plugin/templates/README.md
new file mode 100644
index 0000000000..9d2b74416e
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/README.md
@@ -0,0 +1,28 @@
+# <%= camelized_modules %>
+Short description and motivation.
+
+## Usage
+How to use my plugin.
+
+## Installation
+Add this line to your application's Gemfile:
+
+```ruby
+gem '<%= name %>'
+```
+
+And then execute:
+```bash
+$ bundle
+```
+
+Or install it yourself as:
+```bash
+$ gem install <%= name %>
+```
+
+## Contributing
+Contribution directions go here.
+
+## License
+The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
deleted file mode 100644
index 25983ca5da..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
+++ /dev/null
@@ -1,3 +0,0 @@
-= <%= camelized_modules %>
-
-This project rocks and uses MIT-LICENSE. \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
index bda55bae29..f1943644e4 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
@@ -10,7 +10,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = '<%= camelized_modules %>'
rdoc.options << '--line-numbers'
- rdoc.rdoc_files.include('README.rdoc')
+ rdoc.rdoc_files.include('README.md')
rdoc.rdoc_files.include('lib/**/*.rb')
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt
new file mode 100644
index 0000000000..8aa3de78f1
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt
@@ -0,0 +1,6 @@
+<%= wrap_in_modules <<-rb.strip_heredoc
+ class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+ end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
new file mode 100644
index 0000000000..62b94618fd
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
@@ -0,0 +1,8 @@
+$: << File.expand_path(File.expand_path('../../test', __FILE__))
+
+require 'bundler/setup'
+require 'rails/test_unit/minitest_plugin'
+
+Rails::TestUnitReporter.executable = 'bin/test'
+
+exit Minitest.run(ARGV)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index f315144723..a5eebcb19f 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -1,7 +1,7 @@
# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
-require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
+require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
<% unless options[:skip_active_record] -%>
ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)]
<% if options[:mountable] -%>
@@ -14,6 +14,10 @@ require "rails/test_help"
# to be shown.
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
+<% unless engine? -%>
+Rails::TestUnitReporter.executable = 'bin/test'
+<% end -%>
+
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE
index d2e495758d..c9283eda87 100644
--- a/railties/lib/rails/generators/rails/scaffold/USAGE
+++ b/railties/lib/rails/generators/rails/scaffold/USAGE
@@ -16,7 +16,7 @@ Description:
As a special case, specifying 'password:digest' will generate a
password_digest field of string type, and configure your generated model,
- controller, views, and test suite for use with ActiveModel
+ controller, views, and test suite for use with Active Model
has_secure_password (assuming they are using Rails defaults).
Timestamps are added by default, so you don't have to specify them by hand
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index b7818883d1..79f8b7f96f 100644
--- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
@@ -78,3 +78,7 @@ div.actions {
font-size: 12px;
list-style: square;
}
+
+label {
+ display: block;
+}
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
index 17a86f376b..400afec6dc 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
@@ -52,7 +52,7 @@ class <%= controller_class_name %>Controller < ApplicationController
# Only allow a trusted parameter "white list" through.
def <%= "#{singular_table_name}_params" %>
<%- if attributes_names.empty? -%>
- params[:<%= singular_table_name %>]
+ params.fetch(:<%= singular_table_name %>, {})
<%- else -%>
params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index f73e9a96ba..42b9e34274 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -59,7 +59,7 @@ class <%= controller_class_name %>Controller < ApplicationController
# Only allow a trusted parameter "white list" through.
def <%= "#{singular_table_name}_params" %>
<%- if attributes_names.empty? -%>
- params[:<%= singular_table_name %>]
+ params.fetch(:<%= singular_table_name %>, {})
<%- else -%>
params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
<%- end -%>
diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
index 5a8a3ca5e0..ff41fef9e9 100644
--- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
@@ -1,11 +1,9 @@
require 'test_helper'
<% module_namespacing do -%>
-class <%= class_name %>ControllerTest < ActionController::TestCase
+class <%= class_name %>ControllerTest < ActionDispatch::IntegrationTest
<% if mountable_engine? -%>
- setup do
- @routes = Engine.routes
- end
+ include Engine.routes.url_helpers
<% end -%>
<% if actions.empty? -%>
@@ -15,7 +13,7 @@ class <%= class_name %>ControllerTest < ActionController::TestCase
<% else -%>
<% actions.each do |action| -%>
test "should get <%= action %>" do
- get :<%= action %>
+ get <%= url_helper_prefix %>_<%= action %>_url
assert_response :success
end
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
index 50ca61a35b..2656767eb4 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
@@ -17,7 +17,7 @@
<% end -%>
<% else -%>
-# This model initially had no columns defined. If you add columns to the
+# This model initially had no columns defined. If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
@@ -25,5 +25,5 @@ one: {}
# column: value
#
two: {}
-# column: value
+# column: value
<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
index f302cd6c3d..0d18478043 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
@@ -1,40 +1,41 @@
require 'test_helper'
<% module_namespacing do -%>
-class <%= controller_class_name %>ControllerTest < ActionController::TestCase
+class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTest
+ <%- if mountable_engine? -%>
+ include Engine.routes.url_helpers
+
+ <%- end -%>
setup do
@<%= singular_table_name %> = <%= fixture_name %>(:one)
-<% if mountable_engine? -%>
- @routes = Engine.routes
-<% end -%>
end
test "should get index" do
- get :index
+ get <%= index_helper %>_url
assert_response :success
end
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
assert_response 201
end
test "should show <%= singular_table_name %>" do
- get :show, params: { id: <%= "@#{singular_table_name}" %> }
+ get <%= show_helper %>
assert_response :success
end
test "should update <%= singular_table_name %>" do
- patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
assert_response 200
end
test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete :destroy, params: { id: <%= "@#{singular_table_name}" %> }
+ delete <%= show_helper %>
end
assert_response 204
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 50b98b2631..0e6bef12fc 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -1,50 +1,51 @@
require 'test_helper'
<% module_namespacing do -%>
-class <%= controller_class_name %>ControllerTest < ActionController::TestCase
+class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTest
+ <%- if mountable_engine? -%>
+ include Engine.routes.url_helpers
+
+ <%- end -%>
setup do
@<%= singular_table_name %> = <%= fixture_name %>(:one)
-<% if mountable_engine? -%>
- @routes = Engine.routes
-<% end -%>
end
test "should get index" do
- get :index
+ get <%= index_helper %>_url
assert_response :success
end
test "should get new" do
- get :new
+ get <%= new_helper %>
assert_response :success
end
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
assert_redirected_to <%= singular_table_name %>_path(<%= class_name %>.last)
end
test "should show <%= singular_table_name %>" do
- get :show, params: { id: <%= "@#{singular_table_name}" %> }
+ get <%= show_helper %>
assert_response :success
end
test "should get edit" do
- get :edit, params: { id: <%= "@#{singular_table_name}" %> }
+ get <%= edit_helper %>
assert_response :success
end
test "should update <%= singular_table_name %>" do
- patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
assert_redirected_to <%= singular_table_name %>_path(<%= "@#{singular_table_name}" %>)
end
test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete :destroy, params: { id: <%= "@#{singular_table_name}" %> }
+ delete <%= show_helper %>
end
assert_redirected_to <%= index_helper %>_path
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index d3e33584d7..d60eaf6f4f 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -3,7 +3,6 @@ require 'rake'
# Load Rails Rakefile extensions
%w(
annotations
- dev
framework
initializers
log
diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake
deleted file mode 100644
index 4593100465..0000000000
--- a/railties/lib/rails/tasks/dev.rake
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace :dev do
- desc 'Toggle development mode caching on/off'
- task :cache do
- if File.exist? 'tmp/caching-dev.txt'
- File.delete 'tmp/caching-dev.txt'
- puts 'Development mode is no longer being cached.'
- else
- FileUtils.touch 'tmp/caching-dev.txt'
- puts 'Development mode is now being cached.'
- end
-
- FileUtils.touch 'tmp/restart.txt'
- end
-end
diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake
index 877f175ef3..073f235ec5 100644
--- a/railties/lib/rails/tasks/log.rake
+++ b/railties/lib/rails/tasks/log.rake
@@ -1,5 +1,12 @@
namespace :log do
- desc "Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)"
+
+ ##
+ # Truncates all/specified log files
+ # ENV['LOGS']
+ # - defaults to standard environment log files i.e. 'development,test,production'
+ # - ENV['LOGS']=all truncates all files i.e. log/*.log
+ # - ENV['LOGS']='test,development' truncates only specified files
+ desc "Truncates all/specified *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)"
task :clear do
log_files.each do |file|
clear_log_file(file)
@@ -7,15 +14,21 @@ namespace :log do
end
def log_files
- if ENV['LOGS']
- ENV['LOGS'].split(',')
- .map { |file| "log/#{file.strip}.log" }
- .select { |file| File.exist?(file) }
- else
+ if ENV['LOGS'] == 'all'
FileList["log/*.log"]
+ elsif ENV['LOGS']
+ log_files_to_truncate(ENV['LOGS'])
+ else
+ log_files_to_truncate("development,test,production")
end
end
+ def log_files_to_truncate(envs)
+ envs.split(',')
+ .map { |file| "log/#{file.strip}.log" }
+ .select { |file| File.exist?(file) }
+ end
+
def clear_log_file(file)
f = File.open(file, "w")
f.close
diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb
new file mode 100644
index 0000000000..dab4d3631d
--- /dev/null
+++ b/railties/lib/rails/test_unit/line_filtering.rb
@@ -0,0 +1,70 @@
+require 'method_source'
+
+module Rails
+ module LineFiltering # :nodoc:
+ def run(reporter, options = {})
+ if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
+ options[:filter] = \
+ CompositeFilter.new(self, options[:filter], options[:patterns])
+ end
+
+ super
+ end
+ end
+
+ class CompositeFilter # :nodoc:
+ def initialize(runnable, filter, patterns)
+ @runnable = runnable
+ @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
+ end
+
+ # Minitest uses === to find matching filters.
+ def ===(method)
+ @filters.any? { |filter| filter === method }
+ end
+
+ private
+ def derive_regexp(filter)
+ # Regexp filtering copied from Minitest.
+ filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
+ end
+
+ def derive_line_filters(patterns)
+ patterns.flat_map do |file_and_line|
+ file, *lines = file_and_line.split(':')
+
+ if lines.empty?
+ Filter.new(@runnable, file, nil) if file
+ else
+ lines.map { |line| Filter.new(@runnable, file, line) }
+ end
+ end
+ end
+ end
+
+ class Filter # :nodoc:
+ def initialize(runnable, file, line)
+ @runnable, @file = runnable, File.expand_path(file)
+ @line = line.to_i if line
+ end
+
+ def ===(method)
+ return unless @runnable.method_defined?(method)
+
+ if @line
+ test_file, test_range = definition_for(@runnable.instance_method(method))
+ test_file == @file && test_range.include?(@line)
+ else
+ @runnable.instance_method(method).source_location.first == @file
+ end
+ end
+
+ private
+ def definition_for(method)
+ file, start_line = method.source_location
+ end_line = method.source.count("\n") + start_line - 1
+
+ return file, start_line..end_line
+ end
+ end
+end
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index d1ba35a5ec..29a3d991b8 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -3,26 +3,24 @@ require "rails/test_unit/reporter"
require "rails/test_unit/test_requirer"
module Minitest
- mattr_accessor(:hide_aggregated_results) { false }
-
- module AggregatedResultSuppresion
+ class SuppressedSummaryReporter < SummaryReporter
+ # Disable extra failure output after a run if output is inline.
def aggregated_results
- super unless Minitest.hide_aggregated_results
+ super unless options[:output_inline]
end
end
- SummaryReporter.prepend AggregatedResultSuppresion
-
def self.plugin_rails_options(opts, options)
+ executable = ::Rails::TestUnitReporter.executable
opts.separator ""
- opts.separator "Usage: bin/rails test [options] [files or directories]"
+ opts.separator "Usage: #{executable} [options] [files or directories]"
opts.separator "You can run a single test by appending a line number to a filename:"
opts.separator ""
- opts.separator " bin/rails test test/models/user_test.rb:27"
+ opts.separator " #{executable} test/models/user_test.rb:27"
opts.separator ""
opts.separator "You can run multiple files and directories at the same time:"
opts.separator ""
- opts.separator " bin/rails test test/controllers test/integration/login_test.rb"
+ opts.separator " #{executable} test/controllers test/integration/login_test.rb"
opts.separator ""
opts.separator "By default test failures and errors are reported inline during a run."
opts.separator ""
@@ -48,6 +46,12 @@ module Minitest
options[:fail_fast] = true
end
+ opts.on("-c", "--[no-]color",
+ "Enable color in the output") do |value|
+ options[:color] = value
+ end
+
+ options[:color] = true
options[:output_inline] = true
options[:patterns] = opts.order!
end
@@ -56,7 +60,9 @@ module Minitest
# as the patterns would also contain the other Rake tasks.
def self.rake_run(patterns) # :nodoc:
@rake_patterns = patterns
- run
+ passed = run
+ exit passed unless passed
+ passed
end
def self.plugin_rails_init(options)
@@ -74,9 +80,9 @@ module Minitest
Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner)
end
- # Disable the extra failure output after a run, unless output is deferred.
- self.hide_aggregated_results = options[:output_inline]
-
+ # Replace progress reporter for colors.
+ self.reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) }
+ self.reporter << SuppressedSummaryReporter.new(options[:io], options)
self.reporter << ::Rails::TestUnitReporter.new(options[:io], options)
end
diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb
index 75180ff978..511cee33bd 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -1,3 +1,5 @@
+require 'rails/test_unit/line_filtering'
+
if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
ENV['RAILS_ENV'] ||= 'test'
end
@@ -11,6 +13,10 @@ module Rails
c.integration_tool :test_unit
end
+ initializer "test_unit.line_filtering" do
+ ActiveSupport::TestCase.extend Rails::LineFiltering
+ end
+
rake_tasks do
load "rails/test_unit/testing.rake"
end
diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb
index de4b002e55..73b8d7d27b 100644
--- a/railties/lib/rails/test_unit/reporter.rb
+++ b/railties/lib/rails/test_unit/reporter.rb
@@ -9,10 +9,16 @@ module Rails
def record(result)
super
+ if options[:verbose]
+ io.puts color_output(format_line(result), by: result)
+ else
+ io.print color_output(result.result_code, by: result)
+ end
+
if output_inline? && result.failure && (!result.skipped? || options[:verbose])
io.puts
io.puts
- io.puts result.failures.map(&:message)
+ io.puts format_failures(result).map { |line| color_output(line, by: result) }
io.puts
io.puts format_rerun_snippet(result)
io.puts
@@ -44,7 +50,7 @@ module Rails
end
def relative_path_for(file)
- file.sub(/^#{Rails.root}\/?/, '')
+ file.sub(/^#{app_root}\/?/, '')
end
private
@@ -56,6 +62,16 @@ module Rails
options[:fail_fast]
end
+ def format_line(result)
+ "%s#%s = %.2f s = %s" % [result.class, result.name, result.time, result.result_code]
+ end
+
+ def format_failures(result)
+ result.failures.map do |failure|
+ "#{failure.result_label}:\n#{result.class}##{result.name}:\n#{failure.message}\n"
+ end
+ end
+
def format_rerun_snippet(result)
# Try to extract path to assertion from backtrace.
if result.location =~ /\[(.*)\]\z/
@@ -66,5 +82,29 @@ module Rails
"#{self.executable} #{relative_path_for(assertion_path)}"
end
+
+ def app_root
+ @app_root ||= defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
+ end
+
+ def colored_output?
+ options[:color] && io.respond_to?(:tty?) && io.tty?
+ end
+
+ codes = { red: 31, green: 32, yellow: 33 }
+ COLOR_BY_RESULT_CODE = {
+ "." => codes[:green],
+ "E" => codes[:red],
+ "F" => codes[:red],
+ "S" => codes[:yellow]
+ }
+
+ def color_output(string, by:)
+ if colored_output?
+ "\e[#{COLOR_BY_RESULT_CODE[by.result_code]}m#{string}\e[0m"
+ else
+ string
+ end
+ end
end
end
diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb
index 83d2c55ffd..8b9933bed4 100644
--- a/railties/lib/rails/test_unit/test_requirer.rb
+++ b/railties/lib/rails/test_unit/test_requirer.rb
@@ -15,7 +15,7 @@ module Rails
private
def expand_patterns(patterns)
patterns.map do |arg|
- arg = arg.gsub(/:(\d+)?$/, '')
+ arg = arg.gsub(/(:\d*)+?$/, '')
if Dir.exist?(arg)
"#{arg}/**/*_test.rb"
else
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 6676c6a079..41921e43f3 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -7,7 +7,12 @@ task default: :test
desc "Runs all tests in test folder"
task :test do
$: << "test"
- Minitest.rake_run(["test"])
+ pattern = if ENV.key?('TEST')
+ ENV['TEST']
+ else
+ "test"
+ end
+ Minitest.rake_run([pattern])
end
namespace :test do
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 8b83784ed6..0659110ac0 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -58,7 +58,7 @@ module ApplicationTests
assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
- test "assets are served with sourcemaps when compile is true and debug_assets params is true" do
+ test "assets aren't concatenated when compile is true is on and debug_assets params is true" do
add_to_env_config "production", "config.assets.compile = true"
# Load app env
@@ -67,7 +67,8 @@ module ApplicationTests
class ::PostsController < ActionController::Base ; end
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application(\.debug)?-([0-z]+)\.js"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/xmlhr(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 18882e1855..5f3b364f97 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -174,7 +174,7 @@ module ApplicationTests
precompile!
- assert_file_exists("#{app_path}/public/assets/something-*.js")
+ assert_file_exists("#{app_path}/public/assets/something/index-*.js")
end
test 'precompile use assets defined in app env config' do
@@ -410,7 +410,7 @@ module ApplicationTests
precompile!
- assert_equal "Post\n;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first)
+ assert_match(/Post;/, File.read(Dir["#{app_path}/public/assets/application-*.js"].first))
end
test "initialization on the assets group should set assets_dir" do
@@ -458,9 +458,9 @@ module ApplicationTests
class ::PostsController < ActionController::Base; end
get '/posts', {}, {'HTTPS'=>'off'}
- assert_match('src="http://example.com/assets/application.debug.js', last_response.body)
+ assert_match('src="http://example.com/assets/application.self.js', last_response.body)
get '/posts', {}, {'HTTPS'=>'on'}
- assert_match('src="https://example.com/assets/application.debug.js', last_response.body)
+ assert_match('src="https://example.com/assets/application.self.js', last_response.body)
end
test "asset urls should be protocol-relative if no request is in scope" do
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index 1bdced02e9..8c3ab65c51 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -21,14 +21,14 @@ module ApplicationTests
RUBY
list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
- File.write("log/my.log", "zomg!")
+ File.write("log/test.log", "zomg!")
assert_equal '[]', list_tables.call
- assert_equal 5, File.size("log/my.log")
+ assert_equal 5, File.size("log/test.log")
assert_not File.exist?("tmp/restart.txt")
`bin/setup 2>&1`
- assert_equal 0, File.size("log/my.log")
- assert_equal '["articles", "schema_migrations"]', list_tables.call
+ assert_equal 0, File.size("log/test.log")
+ assert_equal '["articles", "schema_migrations", "active_record_internal_metadatas"]', list_tables.call
assert File.exist?("tmp/restart.txt")
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 5f3d1879eb..7bcfc86d03 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -79,6 +79,24 @@ module ApplicationTests
end
end
+ test "By default logs tags are not set in development" do
+ restore_default_config
+
+ with_rails_env "development" do
+ app 'development'
+ assert Rails.application.config.log_tags.blank?
+ end
+ end
+
+ test "By default logs are tagged with :request_id in production" do
+ restore_default_config
+
+ with_rails_env "production" do
+ app 'production'
+ assert_equal [:request_id], Rails.application.config.log_tags
+ end
+ end
+
test "lib dir is on LOAD_PATH during config" do
app_file 'lib/my_logger.rb', <<-RUBY
require "logger"
@@ -103,7 +121,7 @@ module ApplicationTests
RUBY
app_file 'db/migrate/20140708012246_create_user.rb', <<-RUBY
- class CreateUser < ActiveRecord::Migration
+ class CreateUser < ActiveRecord::Migration::Current
def change
create_table :users
end
@@ -228,6 +246,8 @@ module ApplicationTests
end
test "the application can be eager loaded even when there are no frameworks" do
+ FileUtils.rm_rf("#{app_path}/app/models/application_record.rb")
+ FileUtils.rm_rf("#{app_path}/app/mailers/application_mailer.rb")
FileUtils.rm_rf("#{app_path}/config/environments")
add_to_config <<-RUBY
config.eager_load = true
@@ -655,7 +675,7 @@ module ApplicationTests
private
- def form_authenticity_token; token; end # stub the authenticy token
+ def form_authenticity_token(*args); token; end # stub the authenticy token
end
RUBY
@@ -1306,6 +1326,21 @@ module ApplicationTests
assert_equal 'custom key', Rails.application.config.my_custom_config['key']
end
+ test "config_for use the Pathname object if it is provided" do
+ app_file 'config/custom.yml', <<-RUBY
+ development:
+ key: 'custom key'
+ RUBY
+
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for(Pathname.new(Rails.root.join("config/custom.yml")))
+ RUBY
+
+ app 'development'
+
+ assert_equal 'custom key', Rails.application.config.my_custom_config['key']
+ end
+
test "config_for raises an exception if the file does not exist" do
add_to_config <<-RUBY
config.my_custom_config = config_for('custom')
@@ -1393,5 +1428,45 @@ module ApplicationTests
assert_equal 'unicorn', Rails.application.config.my_custom_config['key']
end
+
+ test "api_only is false by default" do
+ app 'development'
+ refute Rails.application.config.api_only
+ end
+
+ test "api_only generator config is set when api_only is set" do
+ add_to_config <<-RUBY
+ config.api_only = true
+ RUBY
+ app 'development'
+
+ Rails.application.load_generators
+ assert Rails.configuration.api_only
+ end
+
+ test "debug_exception_response_format is :api by default if only_api is enabled" do
+ add_to_config <<-RUBY
+ config.api_only = true
+ RUBY
+ app 'development'
+
+ assert_equal :api, Rails.configuration.debug_exception_response_format
+ end
+
+ test "debug_exception_response_format can be override" do
+ add_to_config <<-RUBY
+ config.api_only = true
+ RUBY
+
+ app_file 'config/environments/development.rb', <<-RUBY
+ Rails.application.configure do
+ config.debug_exception_response_format = :default
+ end
+ RUBY
+
+ app 'development'
+
+ assert_equal :default, Rails.configuration.debug_exception_response_format
+ end
end
end
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 1027bca2c1..40abaf860d 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -116,11 +116,11 @@ class LoadingTest < ActiveSupport::TestCase
require "#{rails_root}/config/environment"
setup_ar!
- assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata], ActiveRecord::Base.descendants
get "/load"
- assert_equal [ActiveRecord::SchemaMigration, Post], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, Post], ActiveRecord::Base.descendants
get "/unload"
- assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata], ActiveRecord::Base.descendants
end
test "initialize cant be called twice" do
@@ -169,6 +169,8 @@ class LoadingTest < ActiveSupport::TestCase
config.file_watcher = Class.new do
def initialize(*); end
def updated?; false; end
+ def execute; end
+ def execute_if_updated; false; end
end
RUBY
@@ -288,7 +290,7 @@ class LoadingTest < ActiveSupport::TestCase
extend Rack::Test::Methods
app_file "db/migrate/1_create_posts.rb", <<-MIGRATION
- class CreatePosts < ActiveRecord::Migration
+ class CreatePosts < ActiveRecord::Migration::Current
def change
create_table :posts do |t|
t.string :title, default: "TITLE"
@@ -304,7 +306,7 @@ class LoadingTest < ActiveSupport::TestCase
assert_equal "TITLE", last_response.body
app_file "db/migrate/2_add_body_to_posts.rb", <<-MIGRATION
- class AddBodyToPosts < ActiveRecord::Migration
+ class AddBodyToPosts < ActiveRecord::Migration::Current
def change
add_column :posts, :body, :text, default: "BODY"
end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 25eadfc387..f847e80471 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -20,12 +20,19 @@ module ApplicationTests
@app ||= Rails.application
end
- test "config.force_ssl sets cookie to secure only" do
+ test "config.force_ssl sets cookie to secure only by default" do
add_to_config "config.force_ssl = true"
require "#{app_path}/config/environment"
assert app.config.session_options[:secure], "Expected session to be marked as secure"
end
+ test "config.force_ssl doesn't set cookie to secure only when changed from default" do
+ add_to_config "config.force_ssl = true"
+ add_to_config "config.ssl_options = { secure_cookies: false }"
+ require "#{app_path}/config/environment"
+ assert !app.config.session_options[:secure]
+ end
+
test "session is not loaded if it's not used" do
make_basic_app
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index f94d08673a..1304e73378 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -61,7 +61,7 @@ module ApplicationTests
test 'db:create failure because database exists' do
with_database_existing do
output = `bin/rake db:create 2>&1`
- assert_match /already exists/, output
+ assert_match(/already exists/, output)
assert_equal 0, $?.exitstatus
end
end
@@ -78,15 +78,15 @@ module ApplicationTests
test 'db:create failure because bad permissions' do
with_bad_permissions do
output = `bin/rake db:create 2>&1`
- assert_match /Couldn't create database/, output
+ assert_match(/Couldn't create database/, output)
assert_equal 1, $?.exitstatus
end
end
test 'db:drop failure because database does not exist' do
Dir.chdir(app_path) do
- output = `bin/rake db:drop 2>&1`
- assert_match /does not exist/, output
+ output = `bin/rake db:drop:_unsafe --trace 2>&1`
+ assert_match(/does not exist/, output)
assert_equal 0, $?.exitstatus
end
end
@@ -95,7 +95,7 @@ module ApplicationTests
with_database_existing do
with_bad_permissions do
output = `bin/rake db:drop 2>&1`
- assert_match /Couldn't drop/, output
+ assert_match(/Couldn't drop/, output)
assert_equal 1, $?.exitstatus
end
end
@@ -222,14 +222,14 @@ module ApplicationTests
assert_equal '["posts"]', list_tables[]
`bin/rake db:schema:load`
- assert_equal '["posts", "comments", "schema_migrations"]', list_tables[]
+ assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas"]', list_tables[]
app_file 'db/structure.sql', <<-SQL
CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
SQL
`bin/rake db:structure:load`
- assert_equal '["posts", "comments", "schema_migrations", "users"]', list_tables[]
+ assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas", "users"]', list_tables[]
end
end
diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb
deleted file mode 100644
index 28d8b22a37..0000000000
--- a/railties/test/application/rake/dev_test.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'isolation/abstract_unit'
-
-module ApplicationTests
- module RakeTests
- class RakeDevTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- end
-
- def teardown
- teardown_app
- end
-
- test 'dev:cache creates file and outputs message' do
- Dir.chdir(app_path) do
- output = `rake dev:cache`
- assert File.exist?('tmp/caching-dev.txt')
- assert_match(/Development mode is now being cached/, output)
- end
- end
-
- test 'dev:cache deletes file and outputs message' do
- Dir.chdir(app_path) do
- output = `rake dev:cache`
- output = `rake dev:cache`
- assert_not File.exist?('tmp/caching-dev.txt')
- assert_match(/Development mode is no longer being cached/, output)
- end
- end
- end
- end
-end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 6b74707959..580ed269cb 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -18,7 +18,7 @@ module ApplicationTests
`bin/rails generate model user username:string password:string`
app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION
- class AMigration < ActiveRecord::Migration
+ class AMigration < ActiveRecord::Migration::Current
end
MIGRATION
@@ -158,12 +158,12 @@ module ApplicationTests
Dir.chdir(app_path) do
app_file "db/migrate/1_one_migration.rb", <<-MIGRATION
- class OneMigration < ActiveRecord::Migration
+ class OneMigration < ActiveRecord::Migration::Current
end
MIGRATION
app_file "db/migrate/02_two_migration.rb", <<-MIGRATION
- class TwoMigration < ActiveRecord::Migration
+ class TwoMigration < ActiveRecord::Migration::Current
end
MIGRATION
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 0da0928b48..639875dd6e 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -24,6 +24,26 @@ module ApplicationTests
assert $task_loaded
end
+ def test_the_test_rake_task_is_protected_when_previous_migration_was_production
+ Dir.chdir(app_path) do
+ output = `bin/rails generate model product name:string;
+ env RAILS_ENV=production bin/rake db:create db:migrate;
+ env RAILS_ENV=production bin/rake db:test:prepare test 2>&1`
+
+ assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
+ end
+ end
+
+ def test_not_protected_when_previous_migration_was_not_production
+ Dir.chdir(app_path) do
+ output = `bin/rails generate model product name:string;
+ env RAILS_ENV=test bin/rake db:create db:migrate;
+ env RAILS_ENV=test bin/rake db:test:prepare test 2>&1`
+
+ refute_match(/ActiveRecord::ProtectedEnvironmentError/, output)
+ end
+ end
+
def test_environment_is_required_in_rake_tasks
app_file "config/environment.rb", <<-RUBY
SuperMiddleware = Struct.new(:app)
@@ -98,7 +118,7 @@ module ApplicationTests
end
def test_code_statistics_sanity
- assert_match "Code LOC: 7 Test LOC: 0 Code to Test Ratio: 1:0.0",
+ assert_match "Code LOC: 14 Test LOC: 0 Code to Test Ratio: 1:0.0",
Dir.chdir(app_path){ `bin/rake stats` }
end
@@ -186,7 +206,7 @@ module ApplicationTests
def test_scaffold_tests_pass_by_default
output = Dir.chdir(app_path) do
`bin/rails generate scaffold user username:string password:string;
- bin/rake db:migrate test`
+ RAILS_ENV=test bin/rake db:migrate test`
end
assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
@@ -205,7 +225,7 @@ module ApplicationTests
output = Dir.chdir(app_path) do
`bin/rails generate scaffold user username:string password:string;
- bin/rake db:migrate test`
+ RAILS_ENV=test bin/rake db:migrate test`
end
assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output)
@@ -218,7 +238,7 @@ module ApplicationTests
output = Dir.chdir(app_path) do
`bin/rails generate scaffold LineItems product:references cart:belongs_to;
- bin/rake db:migrate test`
+ RAILS_ENV=test bin/rake db:migrate test`
end
assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index 0c180339b4..9f15ce5e85 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -74,6 +74,16 @@ module ApplicationTests
assert_match "development", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
end
+ def test_runner_detects_syntax_errors
+ Dir.chdir(app_path) { `bin/rails runner "puts 'hello world" 2>&1` }
+ refute $?.success?
+ end
+
+ def test_runner_detects_bad_script_name
+ Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` }
+ refute $?.success?
+ end
+
def test_environment_with_rails_env
with_rails_env "production" do
assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 4965ab7da0..bb6c6574c5 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -234,6 +234,11 @@ module ApplicationTests
assert_match "0 failures, 0 errors, 0 skips", run_test_command('')
end
+ def test_generated_controller_works_with_rails_test
+ create_controller
+ assert_match "0 failures, 0 errors, 0 skips", run_test_command('')
+ end
+
def test_run_multiple_folders
create_test_file :models, 'account'
create_test_file :controllers, 'accounts_controller'
@@ -289,6 +294,84 @@ module ApplicationTests
end
end
+ def test_more_than_one_line_filter
+ app_file 'test/models/post_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostTest < ActiveSupport::TestCase
+ test "first filter" do
+ puts 'PostTest:FirstFilter'
+ assert true
+ end
+
+ test "second filter" do
+ puts 'PostTest:SecondFilter'
+ assert true
+ end
+
+ test "test line filter does not run this" do
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command('test/models/post_test.rb:4:9').tap do |output|
+ assert_match 'PostTest:FirstFilter', output
+ assert_match 'PostTest:SecondFilter', output
+ assert_match '2 runs, 2 assertions', output
+ end
+ end
+
+ def test_more_than_one_line_filter_with_multiple_files
+ app_file 'test/models/account_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class AccountTest < ActiveSupport::TestCase
+ test "first filter" do
+ puts 'AccountTest:FirstFilter'
+ assert true
+ end
+
+ test "second filter" do
+ puts 'AccountTest:SecondFilter'
+ assert true
+ end
+
+ test "line filter does not run this" do
+ assert true
+ end
+ end
+ RUBY
+
+ app_file 'test/models/post_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostTest < ActiveSupport::TestCase
+ test "first filter" do
+ puts 'PostTest:FirstFilter'
+ assert true
+ end
+
+ test "second filter" do
+ puts 'PostTest:SecondFilter'
+ assert true
+ end
+
+ test "line filter does not run this" do
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command('test/models/account_test.rb:4:9 test/models/post_test:4:9').tap do |output|
+ assert_match 'AccountTest:FirstFilter', output
+ assert_match 'AccountTest:SecondFilter', output
+ assert_match 'PostTest:FirstFilter', output
+ assert_match 'PostTest:SecondFilter', output
+ assert_match '4 runs, 4 assertions', output
+ end
+ end
+
def test_multiple_line_filters
create_test_file :models, 'account'
create_test_file :models, 'post'
@@ -344,7 +427,8 @@ module ApplicationTests
create_test_file :models, 'post', pass: false
output = run_test_command('test/models/post_test.rb')
- assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/rails test test/models/post_test.rb:6}, output
+ expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/rails test test/models/post_test.rb:6\n\n\n\n}
+ assert_match expect, output
end
def test_only_inline_failure_output
@@ -366,6 +450,17 @@ module ApplicationTests
assert_match(%r{cannot load such file.+test/not_exists\.rb}, error)
end
+ def test_pass_TEST_env_on_rake_test
+ create_test_file :models, 'account'
+ create_test_file :models, 'post', pass: false
+
+ output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` }
+
+ assert_match "PostTest", output, "passing TEST= should run selected test"
+ assert_no_match "AccountTest", output, "passing TEST= should only run selected test"
+ assert_match '1 runs, 1 assertions', output
+ end
+
private
def run_test_command(arguments = 'test/unit/test_test.rb')
Dir.chdir(app_path) { `bin/rails t #{arguments}` }
@@ -448,6 +543,10 @@ module ApplicationTests
run_migration
end
+ def create_controller
+ script 'generate controller admin/dashboard index'
+ end
+
def run_migration
Dir.chdir(app_path) { `bin/rake db:migrate` }
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index 7950ed6aa7..a5aa6c14a2 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -113,19 +113,19 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_mysql
- start(adapter: 'mysql', database: 'db')
+ start(adapter: 'mysql2', database: 'db')
assert !aborted
assert_equal [%w[mysql mysql5], 'db'], dbconsole.find_cmd_and_exec_args
end
def test_mysql_full
- start(adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8')
+ start(adapter: 'mysql2', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8')
assert !aborted
assert_equal [%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db'], dbconsole.find_cmd_and_exec_args
end
def test_mysql_include_password
- start({adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}, ['-p'])
+ start({adapter: 'mysql2', database: 'db', username: 'user', password: 'qwerty'}, ['-p'])
assert !aborted
assert_equal [%w[mysql mysql5], '--user=user', '--password=qwerty', 'db'], dbconsole.find_cmd_and_exec_args
end
diff --git a/railties/test/commands/dev_cache_test.rb b/railties/test/commands/dev_cache_test.rb
new file mode 100644
index 0000000000..1b7a72e7fc
--- /dev/null
+++ b/railties/test/commands/dev_cache_test.rb
@@ -0,0 +1,32 @@
+require_relative '../isolation/abstract_unit'
+
+module CommandsTests
+ class DevCacheTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test 'dev:cache creates file and outputs message' do
+ Dir.chdir(app_path) do
+ output = `rails dev:cache`
+ assert File.exist?('tmp/caching-dev.txt')
+ assert_match(%r{Development mode is now being cached}, output)
+ end
+ end
+
+ test 'dev:cache deletes file and outputs message' do
+ Dir.chdir(app_path) do
+ `rails dev:cache` # Create caching file.
+ output = `rails dev:cache` # Delete caching file.
+ assert_not File.exist?('tmp/caching-dev.txt')
+ assert_match(%r{Development mode is no longer being cached}, output)
+ end
+ end
+ end
+end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 3be4a74f74..0c49bd9c53 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -108,4 +108,13 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
end
end
+
+ def test_default_options
+ server = Rails::Server.new
+ old_default_options = server.default_options
+
+ Dir.chdir("..") do
+ assert_equal old_default_options, server.default_options
+ end
+ end
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index b4fbea4af4..a1a17d90d8 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -52,6 +52,15 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/
end
+ def test_add_source_with_block_adds_source_to_gemfile_after_gem
+ run_generator
+ action :gem, 'will-paginate'
+ action :add_source, 'http://gems.github.com' do
+ gem 'rspec-rails'
+ end
+ assert_file 'Gemfile', /gem 'will-paginate'\nsource 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/
+ end
+
def test_gem_should_put_gem_dependency_in_gemfile
run_generator
action :gem, 'will-paginate'
@@ -261,7 +270,14 @@ class ActionsTest < Rails::Generators::TestCase
content.gsub!(/^\n/, '')
File.open(route_path, "wb") { |file| file.write(content) }
- assert_file "config/routes.rb", /\.routes\.draw do\n root 'welcome#index'\nend\n\z/
+
+ routes = <<-F
+Rails.application.routes.draw do
+ root 'welcome#index'
+end
+F
+
+ assert_file "config/routes.rb", routes
action :route, "resources :product_lines"
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index 998da3ef84..2c24a6e46a 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -37,9 +37,8 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/gem 'coffee-rails'/, content)
assert_no_match(/gem 'jquery-rails'/, content)
assert_no_match(/gem 'sass-rails'/, content)
- assert_no_match(/gem 'jbuilder'/, content)
assert_no_match(/gem 'web-console'/, content)
- assert_match(/gem 'active_model_serializers'/, content)
+ assert_match(/# gem 'jbuilder'/, content)
end
assert_file "config/application.rb" do |content|
@@ -89,6 +88,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
config/initializers/assets.rb
config/initializers/cookies_serializer.rb
config/initializers/session_store.rb
+ config/initializers/request_forgery_protection.rb
lib/assets
vendor/assets
test/helpers
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 446fef562b..e5480180ce 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -151,6 +151,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file("config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
end
+ def test_new_application_not_include_api_initializers
+ run_generator
+
+ assert_no_file 'config/initializers/cors.rb'
+ end
+
def test_rails_update_keep_the_cookie_serializer_if_it_is_already_configured
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
@@ -334,6 +340,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
assert_no_file "config/initializers/active_record_belongs_to_required_by_default.rb"
+ assert_no_file "app/models/application_record.rb"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
@@ -354,6 +361,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_has_assets_gems
+ run_generator
+
+ assert_gem 'sass-rails'
+ assert_gem 'uglifier'
+ end
+
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_no_file "config/initializers/assets.rb"
@@ -375,6 +389,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_if_skip_action_cable_is_given
+ run_generator [destination_root, "--skip-action-cable"]
+ assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
+ assert_no_file "config/redis/cable.yml"
+ assert_no_file "app/assets/javascripts/cable.coffee"
+ assert_no_file "app/channels"
+ end
+
def test_inclusion_of_javascript_runtime
run_generator
if defined?(JRUBY_VERSION)
@@ -505,7 +527,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
- assert_no_match(/gem 'web-console', '~> 2.0'/, content)
+ assert_no_match(/gem 'web-console', '~> 3.0'/, content)
end
end
@@ -514,7 +536,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
- assert_no_match(/gem 'web-console', '~> 2.0'/, content)
+ assert_no_match(/gem 'web-console', '~> 3.0'/, content)
end
end
@@ -611,8 +633,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator
folders_with_keep = %w(
app/assets/images
- app/mailers
- app/models
app/controllers/concerns
app/models/concerns
lib/tasks
diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb
new file mode 100644
index 0000000000..c1f0c03fbf
--- /dev/null
+++ b/railties/test/generators/channel_generator_test.rb
@@ -0,0 +1,29 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/channel/channel_generator'
+
+class ChannelGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ tests Rails::Generators::ChannelGenerator
+
+ def test_channel_is_created
+ run_generator ['chat']
+
+ assert_file "app/channels/chat_channel.rb" do |channel|
+ assert_match(/class ChatChannel < ApplicationCable::Channel/, channel)
+ end
+
+ assert_file "app/assets/javascripts/channels/chat.coffee" do |channel|
+ assert_match(/App.cable.subscriptions.create "ChatChannel"/, channel)
+ end
+ end
+
+ def test_channel_asset_is_not_created_when_skip_assets_is_passed
+ run_generator ['chat', '--skip-assets']
+
+ assert_file "app/channels/chat_channel.rb" do |channel|
+ assert_match(/class ChatChannel < ApplicationCable::Channel/, channel)
+ end
+
+ assert_no_file "app/assets/javascripts/channels/chat.coffee"
+ end
+end
diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb
index 7871399dd7..8ef44a8dcb 100644
--- a/railties/test/generators/generator_test.rb
+++ b/railties/test/generators/generator_test.rb
@@ -80,6 +80,21 @@ module Rails
}
assert_equal gems.drop(2), generator.gemfile_entries
end
+
+ def test_recommended_rails_versions
+ klass = make_builder_class
+ generator = klass.start(['new', 'blah'])
+
+ specifier_for = -> v { generator.send(:rails_version_specifier, Gem::Version.new(v)) }
+
+ assert_equal '~> 4.1.13', specifier_for['4.1.13']
+ assert_equal ['>= 4.1.6.rc1', '< 4.2'], specifier_for['4.1.6.rc1']
+ assert_equal ['~> 4.1.7', '>= 4.1.7.1'], specifier_for['4.1.7.1']
+ assert_equal ['~> 4.1.7', '>= 4.1.7.1.2'], specifier_for['4.1.7.1.2']
+ assert_equal ['>= 4.1.7.1.rc2', '< 4.2'], specifier_for['4.1.7.1.rc2']
+ assert_equal ['>= 4.2.0.beta1', '< 4.3'], specifier_for['4.2.0.beta1']
+ assert_equal ['>= 5.0.0.beta1', '< 5.1'], specifier_for['5.0.0.beta1']
+ end
end
end
end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index f01e8cd2d9..f8d9ccacb4 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -14,15 +14,6 @@ class MailerGeneratorTest < Rails::Generators::TestCase
end
end
- def test_application_mailer_skeleton_is_created
- run_generator
- assert_file "app/mailers/application_mailer.rb" do |mailer|
- assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer)
- assert_match(/default from: "from@example.com"/, mailer)
- assert_match(/layout 'mailer'/, mailer)
- end
- end
-
def test_mailer_with_i18n_helper
run_generator
assert_file "app/mailers/notifier_mailer.rb" do |mailer|
@@ -87,10 +78,6 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(%r(\sapp/views/notifier_mailer/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
-
- assert_file "app/views/layouts/mailer.text.erb" do |view|
- assert_match(/<%= yield %>/, view)
- end
end
def test_invokes_default_html_template_engine
@@ -104,17 +91,11 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(%r(\sapp/views/notifier_mailer/bar\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
-
- assert_file "app/views/layouts/mailer.html.erb" do |view|
- assert_match(%r{<html>\n <body>\n <%= yield %>\n </body>\n</html>}, view)
- end
end
def test_invokes_default_template_engine_even_with_no_action
run_generator ["notifier"]
assert_file "app/views/notifier_mailer"
- assert_file "app/views/layouts/mailer.text.erb"
- assert_file "app/views/layouts/mailer.html.erb"
end
def test_logs_if_the_template_engine_cannot_be_found
@@ -162,10 +143,6 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/views/notifier/bar.text.erb"
assert_no_file "app/views/notifier/foo.html.erb"
assert_no_file "app/views/notifier/bar.html.erb"
-
- assert_file "app/mailers/application_mailer.rb"
- assert_file "app/views/layouts/mailer.text.erb"
- assert_file "app/views/layouts/mailer.html.erb"
end
def test_mailer_suffix_is_not_duplicated
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 199743a396..80f284674d 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -7,7 +7,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
def test_migration
migration = "change_title_body_from_posts"
run_generator [migration]
- assert_migration "db/migrate/#{migration}.rb", /class ChangeTitleBodyFromPosts < ActiveRecord::Migration/
+ assert_migration "db/migrate/#{migration}.rb", /class ChangeTitleBodyFromPosts < ActiveRecord::Migration\[[0-9.]+\]/
end
def test_migrations_generated_simultaneously
@@ -26,7 +26,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
def test_migration_with_class_name
migration = "ChangeTitleBodyFromPosts"
run_generator [migration]
- assert_migration "db/migrate/change_title_body_from_posts.rb", /class #{migration} < ActiveRecord::Migration/
+ assert_migration "db/migrate/change_title_body_from_posts.rb", /class #{migration} < ActiveRecord::Migration\[[0-9.]+\]/
end
def test_migration_with_invalid_file_name
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 64b9a480f3..fb502ec0c5 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -35,6 +35,17 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_no_migration "db/migrate/create_accounts.rb"
end
+ def test_model_with_existent_application_record
+ mkdir_p "#{destination_root}/app/models"
+ touch "#{destination_root}/app/models/application_record.rb"
+
+ Dir.chdir(destination_root) do
+ run_generator ["account"]
+ end
+
+ assert_file "app/models/account.rb", /class Account < ApplicationRecord/
+ end
+
def test_plural_names_are_singularized
content = run_generator ["accounts".freeze]
assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/
@@ -57,12 +68,12 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration
run_generator
- assert_migration "db/migrate/create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration\[[0-9.]+\]/
end
def test_migration_with_namespace
run_generator ["Gallery::Image"]
- assert_migration "db/migrate/create_gallery_images", /class CreateGalleryImages < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_gallery_images", /class CreateGalleryImages < ActiveRecord::Migration\[[0-9.]+\]/
assert_no_migration "db/migrate/create_images"
end
@@ -70,7 +81,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator ["Admin::Gallery::Image"]
assert_no_migration "db/migrate/create_images"
assert_no_migration "db/migrate/create_gallery_images"
- assert_migration "db/migrate/create_admin_gallery_images", /class CreateAdminGalleryImages < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_admin_gallery_images", /class CreateAdminGalleryImages < ActiveRecord::Migration\[[0-9.]+\]/
assert_migration "db/migrate/create_admin_gallery_images", /create_table :admin_gallery_images/
end
@@ -80,7 +91,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_no_migration "db/migrate/create_images"
assert_no_migration "db/migrate/create_gallery_images"
assert_no_migration "db/migrate/create_admin_gallery_images"
- assert_migration "db/migrate/create_admin_gallery_image", /class CreateAdminGalleryImage < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_admin_gallery_image", /class CreateAdminGalleryImage < ActiveRecord::Migration\[[0-9.]+\]/
assert_migration "db/migrate/create_admin_gallery_image", /create_table :admin_gallery_image/
ensure
ActiveRecord::Base.pluralize_table_names = true
@@ -89,7 +100,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_with_namespaces_in_model_name_without_plurization
ActiveRecord::Base.pluralize_table_names = false
run_generator ["Gallery::Image"]
- assert_migration "db/migrate/create_gallery_image", /class CreateGalleryImage < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_gallery_image", /class CreateGalleryImage < ActiveRecord::Migration\[[0-9.]+\]/
assert_no_migration "db/migrate/create_gallery_images"
ensure
ActiveRecord::Base.pluralize_table_names = true
@@ -98,7 +109,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_without_pluralization
ActiveRecord::Base.pluralize_table_names = false
run_generator
- assert_migration "db/migrate/create_account", /class CreateAccount < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_account", /class CreateAccount < ActiveRecord::Migration\[[0-9.]+\]/
assert_no_migration "db/migrate/create_accounts"
ensure
ActiveRecord::Base.pluralize_table_names = true
@@ -193,10 +204,10 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_without_timestamps
ActiveRecord::Base.timestamped_migrations = false
run_generator ["account"]
- assert_file "db/migrate/001_create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration/
+ assert_file "db/migrate/001_create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration\[[0-9.]+\]/
run_generator ["project"]
- assert_file "db/migrate/002_create_projects.rb", /class CreateProjects < ActiveRecord::Migration/
+ assert_file "db/migrate/002_create_projects.rb", /class CreateProjects < ActiveRecord::Migration\[[0-9.]+\]/
ensure
ActiveRecord::Base.timestamped_migrations = true
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 590f06e19a..d76759a7d1 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -104,12 +104,12 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
def test_migration
run_generator
- assert_migration "db/migrate/create_test_app_accounts.rb", /create_table :test_app_accounts/, /class CreateTestAppAccounts < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_test_app_accounts.rb", /create_table :test_app_accounts/, /class CreateTestAppAccounts < ActiveRecord::Migration\[[0-9.]+\]/
end
def test_migration_with_namespace
run_generator ["Gallery::Image"]
- assert_migration "db/migrate/create_test_app_gallery_images", /class CreateTestAppGalleryImages < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_test_app_gallery_images", /class CreateTestAppGalleryImages < ActiveRecord::Migration\[[0-9.]+\]/
assert_no_migration "db/migrate/create_test_app_images"
end
@@ -117,7 +117,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
run_generator ["Admin::Gallery::Image"]
assert_no_migration "db/migrate/create_images"
assert_no_migration "db/migrate/create_gallery_images"
- assert_migration "db/migrate/create_test_app_admin_gallery_images", /class CreateTestAppAdminGalleryImages < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_test_app_admin_gallery_images", /class CreateTestAppAdminGalleryImages < ActiveRecord::Migration\[[0-9.]+\]/
assert_migration "db/migrate/create_test_app_admin_gallery_images", /create_table :test_app_admin_gallery_images/
end
@@ -127,7 +127,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
assert_no_migration "db/migrate/create_images"
assert_no_migration "db/migrate/create_gallery_images"
assert_no_migration "db/migrate/create_test_app_admin_gallery_images"
- assert_migration "db/migrate/create_test_app_admin_gallery_image", /class CreateTestAppAdminGalleryImage < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_test_app_admin_gallery_image", /class CreateTestAppAdminGalleryImage < ActiveRecord::Migration\[[0-9.]+\]/
assert_migration "db/migrate/create_test_app_admin_gallery_image", /create_table :test_app_admin_gallery_image/
ensure
ActiveRecord::Base.pluralize_table_names = true
@@ -218,7 +218,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
/class ProductLinesController < ApplicationController/
assert_file "test/controllers/test_app/product_lines_controller_test.rb",
- /module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/
+ /module TestApp\n class ProductLinesControllerTest < ActionDispatch::IntegrationTest/
# Views
%w(index edit new show _form).each do |view|
@@ -285,7 +285,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
end
assert_file "test/controllers/test_app/admin/roles_controller_test.rb",
- /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/
+ /module TestApp\n class Admin::RolesControllerTest < ActionDispatch::IntegrationTest/
# Views
%w(index edit new show _form).each do |view|
@@ -352,7 +352,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
end
assert_file "test/controllers/test_app/admin/user/special/roles_controller_test.rb",
- /module TestApp\n class Admin::User::Special::RolesControllerTest < ActionController::TestCase/
+ /module TestApp\n class Admin::User::Special::RolesControllerTest < ActionDispatch::IntegrationTest/
# Views
%w(index edit new show _form).each do |view|
@@ -418,6 +418,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
assert_match(%r(require_dependency "test_app/application_controller"), content)
end
assert_file "test/controllers/test_app/admin/roles_controller_test.rb",
- /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/
+ /module TestApp\n class Admin::RolesControllerTest < ActionDispatch::IntegrationTest/
end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 715debf344..c5723e364d 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -6,7 +6,7 @@ DEFAULT_PLUGIN_FILES = %w(
.gitignore
Gemfile
Rakefile
- README.rdoc
+ README.md
bukkits.gemspec
MIT-LICENSE
lib
@@ -58,15 +58,18 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_generating_without_options
run_generator
- assert_file "README.rdoc", /Bukkits/
+ assert_file "README.md", /Bukkits/
assert_no_file "config/routes.rb"
assert_no_file "app/assets/config/bukkits_manifest.js"
assert_file "test/test_helper.rb" do |content|
assert_match(/require.+test\/dummy\/config\/environment/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+test\/dummy\/db\/migrate/, content)
assert_match(/Minitest\.backtrace_filter = Minitest::BacktraceFilter\.new/, content)
+ assert_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content)
end
assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/
+ assert_file 'bin/test'
+ assert_no_file 'bin/rails'
end
def test_generating_test_files_in_full_mode
@@ -223,7 +226,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator
FileUtils.cd destination_root
quietly { system 'bundle install' }
- assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`)
+ assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bin/test 2>&1`)
end
def test_ensure_that_tests_works_in_full_mode
@@ -304,6 +307,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/
assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/
assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/
+ assert_file "app/models/bukkits/application_record.rb", /module Bukkits\n class ApplicationRecord < ActiveRecord::Base/
assert_file "app/jobs/bukkits/application_job.rb", /module Bukkits\n class ApplicationJob < ActiveJob::Base/
assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/
assert_file "app/views/layouts/bukkits/application.html.erb" do |contents|
@@ -315,7 +319,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_match(/ActiveRecord::Migrator\.migrations_paths.+\.\.\/test\/dummy\/db\/migrate/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+<<.+\.\.\/db\/migrate/, content)
assert_match(/ActionDispatch::IntegrationTest\.fixture_path = ActiveSupport::TestCase\.fixture_pat/, content)
+ assert_no_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content)
end
+ assert_no_file 'bin/test'
end
def test_create_mountable_application_with_mountable_option_and_hypenated_name
@@ -329,7 +335,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/
assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
- assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "hyphenated-name/app/models/hyphenated/name/application_record.rb", /module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
+ assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents|
assert_match "<title>Hyphenated name</title>", contents
@@ -349,7 +356,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/
assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
- assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "my_hyphenated-name/app/models/my_hyphenated/name/application_record.rb", /module MyHyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
+ assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents|
assert_match "<title>My hyphenated name</title>", contents
@@ -369,7 +377,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/
assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/
assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/
- assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "deep-hyphenated-name/app/models/deep/hyphenated/name/application_record.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents|
assert_match "<title>Deep hyphenated name</title>", contents
@@ -381,8 +390,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_creating_gemspec
run_generator
assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
- assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.rdoc"\]/
- assert_file "bukkits.gemspec", /s.test_files = Dir\["test\/\*\*\/\*"\]/
+ assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.md"\]/
assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/
end
@@ -456,9 +464,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_skipping_test_files
run_generator [destination_root, "--skip-test"]
assert_no_file "test"
- assert_file "bukkits.gemspec" do |contents|
- assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents)
- end
assert_file '.gitignore' do |contents|
assert_no_match(/test\dummy/, contents)
end
@@ -469,7 +474,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem 'rails', '~> #{Rails.version}'/, contents)
+ assert_match(/gem 'rails'/, contents)
assert_match_sqlite3(contents)
assert_no_match(/# gem "jquery-rails"/, contents)
end
@@ -480,7 +485,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem 'rails', '~> #{Rails.version}'/, contents)
+ assert_match(/gem 'rails'/, contents)
assert_match_sqlite3(contents)
end
end
@@ -616,6 +621,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_directory "app/views"
end
+ def test_model_with_existent_application_record_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g model article`
+ end
+
+ assert_file "app/models/bukkits/article.rb", /class Article < ApplicationRecord/
+ end
+
protected
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
diff --git a/railties/test/generators/plugin_test_helper.rb b/railties/test/generators/plugin_test_helper.rb
new file mode 100644
index 0000000000..96c1b1d31f
--- /dev/null
+++ b/railties/test/generators/plugin_test_helper.rb
@@ -0,0 +1,24 @@
+require 'abstract_unit'
+require 'tmpdir'
+
+module PluginTestHelper
+ def create_test_file(name, pass: true)
+ plugin_file "test/#{name}_test.rb", <<-RUBY
+ require 'test_helper'
+
+ class #{name.camelize}Test < ActiveSupport::TestCase
+ def test_truth
+ puts "#{name.camelize}Test"
+ assert #{pass}, 'wups!'
+ end
+ end
+ RUBY
+ end
+
+ def plugin_file(path, contents, mode: 'w')
+ FileUtils.mkdir_p File.dirname("#{plugin_path}/#{path}")
+ File.open("#{plugin_path}/#{path}", mode) do |f|
+ f.puts contents
+ end
+ end
+end
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
new file mode 100644
index 0000000000..f0fb63c208
--- /dev/null
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -0,0 +1,105 @@
+require 'generators/plugin_test_helper'
+
+class PluginTestRunnerTest < ActiveSupport::TestCase
+ include PluginTestHelper
+
+ def setup
+ @destination_root = Dir.mktmpdir('bukkits')
+ Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --skip-bundle` }
+ plugin_file 'test/dummy/db/schema.rb', ''
+ end
+
+ def teardown
+ FileUtils.rm_rf(@destination_root)
+ end
+
+ def test_run_single_file
+ create_test_file 'foo'
+ create_test_file 'bar'
+ assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/foo_test.rb")
+ end
+
+ def test_run_multiple_files
+ create_test_file 'foo'
+ create_test_file 'bar'
+ assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/foo_test.rb test/bar_test.rb")
+ end
+
+ def test_mix_files_and_line_filters
+ create_test_file 'account'
+ plugin_file 'test/post_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostTest < ActiveSupport::TestCase
+ def test_post
+ puts 'PostTest'
+ assert true
+ end
+
+ def test_line_filter_does_not_run_this
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command('test/account_test.rb test/post_test.rb:4').tap do |output|
+ assert_match 'AccountTest', output
+ assert_match 'PostTest', output
+ assert_match '2 runs, 2 assertions', output
+ end
+ end
+
+ def test_multiple_line_filters
+ create_test_file 'account'
+ create_test_file 'post'
+
+ run_test_command('test/account_test.rb:4 test/post_test.rb:4').tap do |output|
+ assert_match 'AccountTest', output
+ assert_match 'PostTest', output
+ end
+ end
+
+ def test_line_filter_without_line_runs_all_tests
+ create_test_file 'account'
+
+ run_test_command('test/account_test.rb:').tap do |output|
+ assert_match 'AccountTest', output
+ end
+ end
+
+ def test_output_inline_by_default
+ create_test_file 'post', pass: false
+
+ output = run_test_command('test/post_test.rb')
+ expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/test (/private)?#{plugin_path}/test/post_test.rb:6}
+ assert_match expect, output
+ end
+
+ def test_only_inline_failure_output
+ create_test_file 'post', pass: false
+
+ output = run_test_command('test/post_test.rb')
+ assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output
+ end
+
+ def test_fail_fast
+ create_test_file 'post', pass: false
+
+ assert_match(/Interrupt/,
+ capture(:stderr) { run_test_command('test/post_test.rb --fail-fast') })
+ end
+
+ def test_raise_error_when_specified_file_does_not_exist
+ error = capture(:stderr) { run_test_command('test/not_exists.rb') }
+ assert_match(%r{cannot load such file.+test/not_exists\.rb}, error)
+ end
+
+ private
+ def plugin_path
+ "#{@destination_root}/bukkits"
+ end
+
+ def run_test_command(arguments)
+ Dir.chdir(plugin_path) { `bin/test #{arguments}` }
+ end
+end
diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb
index 581d80d60e..addaf83bc8 100644
--- a/railties/test/generators/resource_generator_test.rb
+++ b/railties/test/generators/resource_generator_test.rb
@@ -33,7 +33,7 @@ class ResourceGeneratorTest < Rails::Generators::TestCase
def test_resource_controller_with_pluralized_class_name
run_generator
assert_file "app/controllers/accounts_controller.rb", /class AccountsController < ApplicationController/
- assert_file "test/controllers/accounts_controller_test.rb", /class AccountsControllerTest < ActionController::TestCase/
+ assert_file "test/controllers/accounts_controller_test.rb", /class AccountsControllerTest < ActionDispatch::IntegrationTest/
assert_file "app/helpers/accounts_helper.rb", /module AccountsHelper/
end
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 95ef853a11..c37e289f4b 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -56,7 +56,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_file "app/controllers/users_controller.rb" do |content|
assert_match(/def user_params/, content)
- assert_match(/params\[:user\]/, content)
+ assert_match(/params\.fetch\(:user, \{\}\)/, content)
end
end
@@ -104,10 +104,10 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}"]
assert_file "test/controllers/users_controller_test.rb" do |content|
- assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
+ assert_match(/class UsersControllerTest < ActionDispatch::IntegrationTest/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
- assert_match(/patch :update, params: \{ id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_match(/post users_url, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_match(/patch user_url\(@user\), params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
end
end
@@ -115,10 +115,10 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
run_generator ["User"]
assert_file "test/controllers/users_controller_test.rb" do |content|
- assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
+ assert_match(/class UsersControllerTest < ActionDispatch::IntegrationTest/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, params: \{ user: \{ \} \}/, content)
- assert_match(/patch :update, params: \{ id: @user, user: \{ \} \}/, content)
+ assert_match(/post users_url, params: \{ user: \{ \} \}/, content)
+ assert_match(/patch user_url\(@user\), params: \{ user: \{ \} \}/, content)
end
end
@@ -236,10 +236,10 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}", "--api"]
assert_file "test/controllers/users_controller_test.rb" do |content|
- assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
+ assert_match(/class UsersControllerTest < ActionDispatch::IntegrationTest/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
- assert_match(/patch :update, params: \{ id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_match(/post users_url, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_match(/patch user_url\(@user\), params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
assert_no_match(/assert_redirected_to/, content)
end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 0c3808a9a0..eb81ea3d0e 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -57,9 +57,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "test/controllers/product_lines_controller_test.rb" do |test|
- assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test)
- assert_match(/post :create, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
- assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
+ assert_match(/class ProductLinesControllerTest < ActionDispatch::IntegrationTest/, test)
+ assert_match(/post product_lines_url, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
+ assert_match(/patch product_line_url\(@product_line\), params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
end
# Views
@@ -135,9 +135,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "test/controllers/product_lines_controller_test.rb" do |test|
- assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test)
- assert_match(/post :create, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
- assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
+ assert_match(/class ProductLinesControllerTest < ActionDispatch::IntegrationTest/, test)
+ assert_match(/post product_lines_url, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
+ assert_match(/patch product_line_url\(@product_line\), params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
assert_no_match(/assert_redirected_to/, test)
end
@@ -161,10 +161,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
run_generator ["product_line"]
assert_file "test/controllers/product_lines_controller_test.rb" do |content|
- assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content)
+ assert_match(/class ProductLinesControllerTest < ActionDispatch::IntegrationTest/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, params: \{ product_line: \{ \} \}/, content)
- assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ \} \}/, content)
+ assert_match(/post product_lines_url, params: \{ product_line: \{ \} \}/, content)
+ assert_match(/patch product_line_url\(@product_line\), params: \{ product_line: \{ \} \}/, content)
end
end
@@ -250,7 +250,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "test/controllers/admin/roles_controller_test.rb",
- /class Admin::RolesControllerTest < ActionController::TestCase/
+ /class Admin::RolesControllerTest < ActionDispatch::IntegrationTest/
# Views
%w(index edit new show _form).each do |view|
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index acb78ec888..e83d54890a 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -144,7 +144,6 @@ module SharedGeneratorTests
def test_skip_git
run_generator [destination_root, '--skip-git', '--full']
assert_no_file('.gitignore')
- assert_file('app/mailers/.keep')
end
def test_skip_keeps
@@ -154,6 +153,6 @@ module SharedGeneratorTests
assert_no_match(/\.keep/, content)
end
- assert_no_file('app/mailers/.keep')
+ assert_no_file('app/models/concerns/.keep')
end
end
diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb
new file mode 100644
index 0000000000..69906b962b
--- /dev/null
+++ b/railties/test/generators/test_runner_in_engine_test.rb
@@ -0,0 +1,32 @@
+require 'generators/plugin_test_helper'
+
+class TestRunnerInEngineTest < ActiveSupport::TestCase
+ include PluginTestHelper
+
+ def setup
+ @destination_root = Dir.mktmpdir('bukkits')
+ Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --full --skip-bundle` }
+ plugin_file 'test/dummy/db/schema.rb', ''
+ end
+
+ def teardown
+ FileUtils.rm_rf(@destination_root)
+ end
+
+ def test_rerun_snippet_is_relative_path
+ create_test_file 'post', pass: false
+
+ output = run_test_command('test/post_test.rb')
+ expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/rails test test/post_test.rb:6}
+ assert_match expect, output
+ end
+
+ private
+ def plugin_path
+ "#{@destination_root}/bukkits"
+ end
+
+ def run_test_command(arguments)
+ Dir.chdir(plugin_path) { `bin/rails test #{arguments}` }
+ end
+end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 24386de82a..4a47ab32b4 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -63,22 +63,22 @@ module RailtiesTest
test "copying migrations" do
@plugin.write "db/migrate/1_create_users.rb", <<-RUBY
- class CreateUsers < ActiveRecord::Migration
+ class CreateUsers < ActiveRecord::Migration::Current
end
RUBY
@plugin.write "db/migrate/2_add_last_name_to_users.rb", <<-RUBY
- class AddLastNameToUsers < ActiveRecord::Migration
+ class AddLastNameToUsers < ActiveRecord::Migration::Current
end
RUBY
@plugin.write "db/migrate/3_create_sessions.rb", <<-RUBY
- class CreateSessions < ActiveRecord::Migration
+ class CreateSessions < ActiveRecord::Migration::Current
end
RUBY
app_file "db/migrate/1_create_sessions.rb", <<-RUBY
- class CreateSessions < ActiveRecord::Migration
+ class CreateSessions < ActiveRecord::Migration::Current
def up
end
end
@@ -123,12 +123,12 @@ module RailtiesTest
end
@plugin.write "db/migrate/1_create_users.rb", <<-RUBY
- class CreateUsers < ActiveRecord::Migration
+ class CreateUsers < ActiveRecord::Migration::Current
end
RUBY
@blog.write "db/migrate/2_create_blogs.rb", <<-RUBY
- class CreateBlogs < ActiveRecord::Migration
+ class CreateBlogs < ActiveRecord::Migration::Current
end
RUBY
@@ -163,11 +163,11 @@ module RailtiesTest
end
@core.write "db/migrate/1_create_users.rb", <<-RUBY
- class CreateUsers < ActiveRecord::Migration; end
+ class CreateUsers < ActiveRecord::Migration::Current; end
RUBY
@api.write "db/migrate/2_create_keys.rb", <<-RUBY
- class CreateKeys < ActiveRecord::Migration; end
+ class CreateKeys < ActiveRecord::Migration::Current; end
RUBY
boot_rails
@@ -190,7 +190,7 @@ module RailtiesTest
RUBY
@plugin.write "db/migrate/0_add_first_name_to_users.rb", <<-RUBY
- class AddFirstNameToUsers < ActiveRecord::Migration
+ class AddFirstNameToUsers < ActiveRecord::Migration::Current
end
RUBY
diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb
index e517d8dd0b..2d08d4ec30 100644
--- a/railties/test/test_unit/reporter_test.rb
+++ b/railties/test/test_unit/reporter_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'rails/test_unit/reporter'
+require 'minitest/mock'
class TestUnitReporterTest < ActiveSupport::TestCase
class ExampleTest < Minitest::Test
@@ -61,14 +62,16 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- assert_match %r{\A\n\nboo\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}, @output.string
+ expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot:\nboo\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
+ assert_match expect, @output.string
end
test "outputs errors inline" do
@reporter.record(errored_test)
@reporter.report
- assert_match %r{\A\n\nArgumentError: wups\n No backtrace\n\nbin/rails test .*test/test_unit/reporter_test.rb:6\n\n\z}, @output.string
+ expect = %r{\AE\n\nError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}
+ assert_match expect, @output.string
end
test "outputs skipped tests inline if verbose" do
@@ -76,7 +79,8 @@ class TestUnitReporterTest < ActiveSupport::TestCase
verbose.record(skipped_test)
verbose.report
- assert_match %r{\A\n\nskipchurches, misstemples\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}, @output.string
+ expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
+ assert_match expect, @output.string
end
test "does not output rerun snippets after run" do
@@ -110,6 +114,36 @@ class TestUnitReporterTest < ActiveSupport::TestCase
assert_no_match 'Failed tests:', @output.string
end
+ test "outputs colored passing results" do
+ @output.stub(:tty?, true) do
+ colored = Rails::TestUnitReporter.new @output, color: true, output_inline: true
+ colored.record(passing_test)
+
+ expect = %r{\e\[32m\.\e\[0m}
+ assert_match expect, @output.string
+ end
+ end
+
+ test "outputs colored skipped results" do
+ @output.stub(:tty?, true) do
+ colored = Rails::TestUnitReporter.new @output, color: true, output_inline: true
+ colored.record(skipped_test)
+
+ expect = %r{\e\[33mS\e\[0m}
+ assert_match expect, @output.string
+ end
+ end
+
+ test "outputs colored failed results" do
+ @output.stub(:tty?, true) do
+ colored = Rails::TestUnitReporter.new @output, color: true, output_inline: true
+ colored.record(errored_test)
+
+ expected = %r{\e\[31mE\e\[0m\n\n\e\[31mError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\e\[0m}
+ assert_match expected, @output.string
+ end
+ end
+
private
def assert_rerun_snippet_count(snippet_count)
assert_equal snippet_count, @output.string.scan(%r{^bin/rails test }).size
@@ -142,6 +176,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
rescue Minitest::Assertion => e
e
end
+ st.time = 10
st
end
end
diff --git a/tasks/release.rb b/tasks/release.rb
index 7c8b884d44..ea7f66a171 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,4 +1,4 @@
-FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer railties )
+FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer actioncable railties )
root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip
@@ -57,8 +57,6 @@ directory "pkg"
sh "gem install #{gem}"
end
- task :prep_release => [:ensure_clean_state, :build]
-
task :push => :build do
sh "gem push #{gem}"
end
@@ -139,5 +137,7 @@ namespace :all do
sh "git push --tags"
end
+ task :prep_release => %w(ensure_clean_state build)
+
task :release => %w(ensure_clean_state build bundle commit tag push)
end
diff --git a/version.rb b/version.rb
index 7d74b1bfe5..92230a74e4 100644
--- a/version.rb
+++ b/version.rb
@@ -8,7 +8,7 @@ module Rails
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end