aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/stale.yml28
-rw-r--r--.travis.yml32
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock92
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md2
-rw-r--r--RELEASING_RAILS.md2
-rw-r--r--actioncable/CHANGELOG.md46
-rw-r--r--actioncable/README.md4
-rw-r--r--actioncable/Rakefile36
-rw-r--r--actioncable/lib/action_cable/connection/authorization.rb10
-rw-r--r--actioncable/lib/action_cable/gem_version.rb4
-rw-r--r--actioncable/package.json2
-rw-r--r--actioncable/test/channel/stream_test.rb4
-rw-r--r--actioncable/test/client_test.rb31
-rw-r--r--actionmailer/CHANGELOG.md34
-rw-r--r--actionmailer/lib/action_mailer/base.rb3
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb4
-rw-r--r--actionmailer/test/message_delivery_test.rb6
-rw-r--r--actionmailer/test/parameterized_test.rb1
-rw-r--r--actionpack/CHANGELOG.md386
-rw-r--r--actionpack/lib/action_controller/api.rb8
-rw-r--r--actionpack/lib/action_controller/base.rb15
-rw-r--r--actionpack/lib/action_controller/metal.rb6
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_flash.rb4
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb14
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb6
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb8
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb4
-rw-r--r--actionpack/lib/action_controller/metal/parameter_encoding.rb2
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb14
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb7
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb16
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb2
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb106
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb2
-rw-r--r--actionpack/lib/action_controller/railtie.rb2
-rw-r--r--actionpack/lib/action_controller/renderer.rb7
-rw-r--r--actionpack/lib/action_controller/test_case.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb18
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb14
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb9
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb8
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb57
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb12
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb31
-rw-r--r--actionpack/lib/action_dispatch/system_testing/browser.rb27
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb28
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb47
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb19
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb4
-rw-r--r--actionpack/test/abstract_unit.rb8
-rw-r--r--actionpack/test/controller/api/with_helpers_test.rb42
-rw-r--r--actionpack/test/controller/base_test.rb27
-rw-r--r--actionpack/test/controller/metal_test.rb30
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb63
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb41
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb15
-rw-r--r--actionpack/test/controller/renderer_test.rb24
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb101
-rw-r--r--actionpack/test/controller/test_case_test.rb19
-rw-r--r--actionpack/test/dispatch/routing/custom_url_helpers_test.rb36
-rw-r--r--actionpack/test/dispatch/routing_test.rb128
-rw-r--r--actionpack/test/dispatch/system_testing/browser_test.rb10
-rw-r--r--actionpack/test/dispatch/system_testing/driver_test.rb12
-rw-r--r--actionpack/test/dispatch/system_testing/screenshot_helper_test.rb19
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb14
-rw-r--r--actionview/CHANGELOG.md191
-rw-r--r--actionview/Rakefile43
-rw-r--r--actionview/app/assets/javascripts/config.coffee34
-rw-r--r--actionview/app/assets/javascripts/rails-ujs.coffee94
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/BANNER.js5
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee (renamed from actionview/app/assets/javascripts/features/confirm.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/disable.coffee (renamed from actionview/app/assets/javascripts/features/disable.coffee)4
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/method.coffee (renamed from actionview/app/assets/javascripts/features/method.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/remote.coffee (renamed from actionview/app/assets/javascripts/features/remote.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/start.coffee70
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee (renamed from actionview/app/assets/javascripts/utils/ajax.coffee)7
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee (renamed from actionview/app/assets/javascripts/utils/csrf.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee (renamed from actionview/app/assets/javascripts/utils/dom.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/event.coffee (renamed from actionview/app/assets/javascripts/utils/event.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/form.coffee (renamed from actionview/app/assets/javascripts/utils/form.coffee)0
-rw-r--r--actionview/lib/action_view/digestor.rb6
-rw-r--r--actionview/lib/action_view/gem_version.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb42
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb14
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb2
-rw-r--r--actionview/lib/action_view/railtie.rb2
-rw-r--r--actionview/package.json4
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb46
-rw-r--r--actionview/test/template/date_helper_test.rb2
-rw-r--r--actionview/test/template/digestor_test.rb4
-rw-r--r--actionview/test/template/sanitize_helper_test.rb4
-rw-r--r--actionview/test/ujs/public/test/call-remote.js28
-rw-r--r--actionview/test/ujs/public/test/data-confirm.js28
-rw-r--r--actionview/test/ujs/public/test/data-remote.js67
-rw-r--r--activejob/CHANGELOG.md54
-rw-r--r--activejob/lib/active_job/gem_version.rb4
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb11
-rw-r--r--activejob/test/cases/test_helper_test.rb11
-rw-r--r--activemodel/CHANGELOG.md48
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb4
-rw-r--r--activemodel/lib/active_model/dirty.rb8
-rw-r--r--activemodel/lib/active_model/errors.rb17
-rw-r--r--activemodel/lib/active_model/gem_version.rb4
-rw-r--r--activemodel/lib/active_model/type.rb10
-rw-r--r--activemodel/lib/active_model/type/date.rb2
-rw-r--r--activemodel/lib/active_model/type/helpers/time_value.rb2
-rw-r--r--activemodel/lib/active_model/type/string.rb7
-rw-r--r--activemodel/lib/active_model/validations/format.rb1
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb1
-rw-r--r--activemodel/lib/active_model/validations/presence.rb1
-rw-r--r--activemodel/test/cases/errors_test.rb16
-rw-r--r--activemodel/test/cases/type/string_test.rb13
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb9
-rw-r--r--activemodel/test/validators/email_validator.rb1
-rw-r--r--activerecord/CHANGELOG.md693
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb16
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb9
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb13
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb27
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb120
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb88
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb63
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb166
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb112
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb260
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb91
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb92
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb122
-rw-r--r--activerecord/lib/active_record/core.rb1
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb1
-rw-r--r--activerecord/lib/active_record/fixtures.rb29
-rw-r--r--activerecord/lib/active_record/gem_version.rb4
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb18
-rw-r--r--activerecord/lib/active_record/migration.rb49
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb5
-rw-r--r--activerecord/lib/active_record/null_relation.rb2
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb8
-rw-r--r--activerecord/lib/active_record/railties/databases.rake29
-rw-r--r--activerecord/lib/active_record/reflection.rb83
-rw-r--r--activerecord/lib/active_record/relation.rb8
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb17
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb229
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb55
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb19
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb88
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb41
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb59
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb49
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb7
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb4
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb15
-rw-r--r--activerecord/lib/active_record/schema_migration.rb6
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb4
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb20
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rw-r--r--activerecord/lib/active_record/type/decimal_without_scale.rb4
-rw-r--r--activerecord/lib/active_record/type/serialized.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb2
-rw-r--r--activerecord/test/cases/adapter_test.rb19
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb175
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/statement_pool_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb10
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb22
-rw-r--r--activerecord/test/cases/ar_schema_test.rb217
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb257
-rw-r--r--activerecord/test/cases/associations/extension_test.rb5
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb2
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb2
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb2
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/required_test.rb42
-rw-r--r--activerecord/test/cases/associations_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb121
-rw-r--r--activerecord/test/cases/cache_key_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb17
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb1
-rw-r--r--activerecord/test/cases/column_definition_test.rb2
-rw-r--r--activerecord/test/cases/comment_test.rb14
-rw-r--r--activerecord/test/cases/connection_pool_test.rb5
-rw-r--r--activerecord/test/cases/date_time_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb66
-rw-r--r--activerecord/test/cases/dup_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb34
-rw-r--r--activerecord/test/cases/fixtures_test.rb112
-rw-r--r--activerecord/test/cases/integration_test.rb1
-rw-r--r--activerecord/test/cases/json_serialization_test.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb34
-rw-r--r--activerecord/test/cases/migration/columns_test.rb10
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb13
-rw-r--r--activerecord/test/cases/migration/pending_migrations_test.rb12
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb7
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb18
-rw-r--r--activerecord/test/cases/migrator_test.rb61
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/readonly_test.rb2
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb2
-rw-r--r--activerecord/test/cases/relation/merging_test.rb4
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_test.rb7
-rw-r--r--activerecord/test/cases/relation_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb55
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb21
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb72
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb18
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb4
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb8
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb19
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb40
-rw-r--r--activerecord/test/cases/transactions_test.rb2
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/cases/view_test.rb4
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb2
-rw-r--r--activerecord/test/models/essay.rb1
-rw-r--r--activerecord/test/models/post.rb4
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb8
-rw-r--r--activerecord/test/support/connection.rb5
-rw-r--r--activesupport/CHANGELOG.md426
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb68
-rw-r--r--activesupport/lib/active_support/core_ext/hash/reverse_merge.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb32
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb59
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb3
-rw-r--r--activesupport/lib/active_support/deprecation.rb3
-rw-r--r--activesupport/lib/active_support/deprecation/constant_accessor.rb50
-rw-r--r--activesupport/lib/active_support/duration.rb111
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb5
-rw-r--r--activesupport/lib/active_support/gem_version.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb15
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb40
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb7
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb1
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb50
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb85
-rw-r--r--activesupport/test/caching_test.rb11
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb166
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb32
-rw-r--r--activesupport/test/core_ext/duration_test.rb157
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb717
-rw-r--r--activesupport/test/core_ext/module_test.rb32
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb5
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb43
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb45
-rw-r--r--activesupport/test/deprecation_test.rb23
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb745
-rw-r--r--activesupport/test/inflector_test.rb13
-rw-r--r--activesupport/test/inflector_test_cases.rb16
-rw-r--r--activesupport/test/time_zone_test.rb186
-rwxr-xr-xci/travis.rb1
-rw-r--r--guides/CHANGELOG.md7
-rw-r--r--guides/Rakefile19
-rw-r--r--guides/assets/stylesheets/main.css4
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/bug_report_templates/active_job_gem.rb2
-rw-r--r--guides/bug_report_templates/active_job_master.rb1
-rw-r--r--guides/bug_report_templates/active_record_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_master.rb1
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb1
-rw-r--r--guides/bug_report_templates/generic_gem.rb2
-rw-r--r--guides/bug_report_templates/generic_master.rb1
-rw-r--r--guides/rails_guides/markdown.rb4
-rw-r--r--guides/source/5_1_release_notes.md382
-rw-r--r--guides/source/_welcome.html.erb3
-rw-r--r--guides/source/action_cable_overview.md6
-rw-r--r--guides/source/action_controller_overview.md4
-rw-r--r--guides/source/action_mailer_basics.md5
-rw-r--r--guides/source/active_job_basics.md2
-rw-r--r--guides/source/active_model_basics.md53
-rw-r--r--guides/source/active_record_callbacks.md10
-rw-r--r--guides/source/active_record_querying.md23
-rw-r--r--guides/source/active_record_validations.md3
-rw-r--r--guides/source/api_documentation_guidelines.md4
-rw-r--r--guides/source/asset_pipeline.md2
-rw-r--r--guides/source/association_basics.md22
-rw-r--r--guides/source/caching_with_rails.md24
-rw-r--r--guides/source/command_line.md10
-rw-r--r--guides/source/configuring.md12
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md32
-rw-r--r--guides/source/debugging_rails_applications.md13
-rw-r--r--guides/source/documents.yaml5
-rw-r--r--guides/source/engines.md112
-rw-r--r--guides/source/form_helpers.md2
-rw-r--r--guides/source/generators.md2
-rw-r--r--guides/source/getting_started.md4
-rw-r--r--guides/source/i18n.md10
-rw-r--r--guides/source/maintenance_policy.md6
-rw-r--r--guides/source/rails_on_rack.md11
-rw-r--r--guides/source/routing.md15
-rw-r--r--guides/source/security.md21
-rw-r--r--guides/source/testing.md33
-rw-r--r--guides/source/upgrading_ruby_on_rails.md2
-rw-r--r--guides/source/working_with_javascript_in_rails.md8
-rw-r--r--railties/CHANGELOG.md159
-rw-r--r--railties/RDOC_MAIN.rdoc2
-rw-r--r--railties/lib/rails.rb4
-rw-r--r--railties/lib/rails/application.rb4
-rw-r--r--railties/lib/rails/application/bootstrap.rb1
-rw-r--r--railties/lib/rails/application/configuration.rb40
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb2
-rw-r--r--railties/lib/rails/code_statistics.rb3
-rw-r--r--railties/lib/rails/command/base.rb8
-rw-r--r--railties/lib/rails/commands/dbconsole/dbconsole_command.rb2
-rw-r--r--railties/lib/rails/commands/destroy/destroy_command.rb13
-rw-r--r--railties/lib/rails/commands/generate/generate_command.rb10
-rw-r--r--railties/lib/rails/commands/new/new_command.rb6
-rw-r--r--railties/lib/rails/commands/runner/runner_command.rb13
-rw-r--r--railties/lib/rails/commands/secrets/USAGE8
-rw-r--r--railties/lib/rails/commands/secrets/secrets_command.rb27
-rw-r--r--railties/lib/rails/commands/server/server_command.rb14
-rw-r--r--railties/lib/rails/commands/test/test_command.rb6
-rw-r--r--railties/lib/rails/engine/updater.rb19
-rw-r--r--railties/lib/rails/gem_version.rb4
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb21
-rw-r--r--railties/lib/rails/generators/erb.rb4
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb16
-rw-r--r--railties/lib/rails/generators/named_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb51
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt37
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/secrets.yml4
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb3
-rw-r--r--railties/lib/rails/paths.rb9
-rw-r--r--railties/lib/rails/secrets.rb31
-rw-r--r--railties/lib/rails/tasks/engine.rake11
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/test_help.rb13
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb12
-rw-r--r--railties/lib/rails/test_unit/test_requirer.rb7
-rw-r--r--railties/lib/rails/test_unit/testing.rake10
-rw-r--r--railties/test/application/generators_test.rb3
-rw-r--r--railties/test/application/initializers/frameworks_test.rb8
-rw-r--r--railties/test/application/middleware/cache_test.rb16
-rw-r--r--railties/test/application/rake/migrations_test.rb22
-rw-r--r--railties/test/application/runner_test.rb8
-rw-r--r--railties/test/application/test_runner_test.rb76
-rw-r--r--railties/test/command/base_test.rb11
-rw-r--r--railties/test/commands/secrets_test.rb37
-rw-r--r--railties/test/commands/server_test.rb26
-rw-r--r--railties/test/generators/api_app_generator_test.rb23
-rw-r--r--railties/test/generators/app_generator_test.rb88
-rw-r--r--railties/test/generators/encrypted_secrets_generator_test.rb6
-rw-r--r--railties/test/generators/plugin_generator_test.rb16
-rw-r--r--railties/test/generators/scaffold_generator_test.rb63
-rw-r--r--railties/test/generators_test.rb2
-rw-r--r--railties/test/paths_test.rb20
-rw-r--r--railties/test/railties/mounted_engine_test.rb2
-rw-r--r--railties/test/secrets_test.rb36
-rw-r--r--tasks/release.rb2
-rw-r--r--version.rb4
435 files changed, 7212 insertions, 5544 deletions
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 0000000000..71704b3cd7
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,28 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 90
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - pinned
+ - security
+ - With reproduction steps
+ - attached PR
+ - regression
+ - release blocker
+# Label to use when marking an issue as stale
+staleLabel: stale
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not been commented on for at least three months.
+
+ The resources of the Rails team are limited, and so we are asking for your help.
+
+ If you can still reproduce this error on the `5-1-stable` branch or on `master`,
+ please reply with all of the information you have about it in order to keep the issue open.
+
+ Thank you for all your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
+# Limit to only `issues` or `pulls`
+only: issues
diff --git a/.travis.yml b/.travis.yml
index 2006291052..5b8cfa9dbf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,6 +15,9 @@ services:
addons:
postgresql: "9.4"
+ apt:
+ packages:
+ - postgresql-9.4
bundler_args: --without test --jobs 3 --retry 3
before_install:
@@ -52,28 +55,28 @@ env:
- "GEM=ac:integration"
rvm:
- - 2.2.6
- - 2.3.3
- - 2.4.0
+ - 2.2.7
+ - 2.3.4
+ - 2.4.1
- ruby-head
matrix:
include:
- - rvm: 2.4.0
+ - rvm: 2.4.1
env: "GEM=av:ujs"
- - rvm: 2.2.6
+ - rvm: 2.2.7
env: "GEM=aj:integration"
services:
- memcached
- redis
- rabbitmq
- - rvm: 2.3.3
+ - rvm: 2.3.4
env: "GEM=aj:integration"
services:
- memcached
- redis
- rabbitmq
- - rvm: 2.4.0
+ - rvm: 2.4.1
env: "GEM=aj:integration"
services:
- memcached
@@ -85,25 +88,30 @@ matrix:
- memcached
- redis
- rabbitmq
- - rvm: 2.3.3
+ - rvm: 2.3.4
env:
- "GEM=ar:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.0
- - rvm: 2.4.0
+ - rvm: 2.3.4
env:
- "GEM=ar:sqlite3_mem"
- - rvm: jruby-9.1.7.0
+ - rvm: 2.3.4
+ env:
+ - "GEM=ar:postgresql POSTGRES=9.2"
+ addons:
+ postgresql: "9.2"
+ - rvm: jruby-9.1.8.0
jdk: oraclejdk8
env:
- "GEM=ap"
- - rvm: jruby-9.1.7.0
+ - rvm: jruby-9.1.8.0
jdk: oraclejdk8
env:
- "GEM=am,amo,aj"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.1.7.0
+ - rvm: jruby-9.1.8.0
- env: "GEM=ac:integration"
fast_finish: true
diff --git a/Gemfile b/Gemfile
index a0da9d391f..5077c257d9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,8 @@ end
gemspec
+gem "arel", github: "rails/arel"
+
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem "rake", ">= 11.1"
@@ -14,7 +16,7 @@ gem "rake", ">= 11.1"
# be loaded after loading the test library.
gem "mocha", "~> 0.14", require: false
-gem "capybara", "~> 2.7.0"
+gem "capybara", "~> 2.13.0"
gem "rack-cache", "~> 1.2"
gem "jquery-rails"
@@ -84,6 +86,7 @@ group :cable do
gem "blade", require: false, platforms: [:ruby]
gem "blade-sauce_labs_plugin", require: false, platforms: [:ruby]
+ gem "sprockets-export", require: false
end
# Add your own local bundler stuff.
diff --git a/Gemfile.lock b/Gemfile.lock
index 74f76f9e7c..84ab3a01ca 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -23,61 +23,67 @@ GIT
event_emitter
websocket
+GIT
+ remote: https://github.com/rails/arel.git
+ revision: 437aa3a4bb8ad4f3f4eba299dbb1112852f9c7ac
+ specs:
+ arel (8.0.0)
+
PATH
remote: .
specs:
- actioncable (5.1.0.beta1)
- actionpack (= 5.1.0.beta1)
+ actioncable (5.2.0.alpha)
+ actionpack (= 5.2.0.alpha)
nio4r (~> 2.0)
websocket-driver (~> 0.6.1)
- actionmailer (5.1.0.beta1)
- actionpack (= 5.1.0.beta1)
- actionview (= 5.1.0.beta1)
- activejob (= 5.1.0.beta1)
+ actionmailer (5.2.0.alpha)
+ actionpack (= 5.2.0.alpha)
+ actionview (= 5.2.0.alpha)
+ activejob (= 5.2.0.alpha)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.1.0.beta1)
- actionview (= 5.1.0.beta1)
- activesupport (= 5.1.0.beta1)
+ actionpack (5.2.0.alpha)
+ actionview (= 5.2.0.alpha)
+ activesupport (= 5.2.0.alpha)
rack (~> 2.0)
rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.1.0.beta1)
- activesupport (= 5.1.0.beta1)
+ actionview (5.2.0.alpha)
+ activesupport (= 5.2.0.alpha)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.1.0.beta1)
- activesupport (= 5.1.0.beta1)
+ activejob (5.2.0.alpha)
+ activesupport (= 5.2.0.alpha)
globalid (>= 0.3.6)
- activemodel (5.1.0.beta1)
- activesupport (= 5.1.0.beta1)
- activerecord (5.1.0.beta1)
- activemodel (= 5.1.0.beta1)
- activesupport (= 5.1.0.beta1)
+ activemodel (5.2.0.alpha)
+ activesupport (= 5.2.0.alpha)
+ activerecord (5.2.0.alpha)
+ activemodel (= 5.2.0.alpha)
+ activesupport (= 5.2.0.alpha)
arel (~> 8.0)
- activesupport (5.1.0.beta1)
+ activesupport (5.2.0.alpha)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
- rails (5.1.0.beta1)
- actioncable (= 5.1.0.beta1)
- actionmailer (= 5.1.0.beta1)
- actionpack (= 5.1.0.beta1)
- actionview (= 5.1.0.beta1)
- activejob (= 5.1.0.beta1)
- activemodel (= 5.1.0.beta1)
- activerecord (= 5.1.0.beta1)
- activesupport (= 5.1.0.beta1)
+ rails (5.2.0.alpha)
+ actioncable (= 5.2.0.alpha)
+ actionmailer (= 5.2.0.alpha)
+ actionpack (= 5.2.0.alpha)
+ actionview (= 5.2.0.alpha)
+ activejob (= 5.2.0.alpha)
+ activemodel (= 5.2.0.alpha)
+ activerecord (= 5.2.0.alpha)
+ activesupport (= 5.2.0.alpha)
bundler (>= 1.3.0, < 2.0)
- railties (= 5.1.0.beta1)
+ railties (= 5.2.0.alpha)
sprockets-rails (>= 2.0.0)
- railties (5.1.0.beta1)
- actionpack (= 5.1.0.beta1)
- activesupport (= 5.1.0.beta1)
+ railties (5.2.0.alpha)
+ actionpack (= 5.2.0.alpha)
+ activesupport (= 5.2.0.alpha)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -88,7 +94,6 @@ GEM
addressable (2.5.0)
public_suffix (~> 2.0, >= 2.0.2)
amq-protocol (2.1.0)
- arel (8.0.0)
ast (2.3.0)
backburner (1.3.1)
beaneater (~> 1.0)
@@ -120,7 +125,7 @@ GEM
bunny (2.6.2)
amq-protocol (>= 2.0.1)
byebug (9.0.6)
- capybara (2.7.1)
+ capybara (2.13.0)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@@ -136,7 +141,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.12.2)
- concurrent-ruby (1.0.4)
+ concurrent-ruby (1.0.5)
connection_pool (2.2.1)
cookiejar (0.3.3)
curses (1.0.2)
@@ -159,7 +164,7 @@ GEM
http_parser.rb (>= 0.6.0)
em-socksify (0.3.1)
eventmachine (>= 1.0.0.beta.4)
- erubi (1.4.0)
+ erubi (1.6.0)
erubis (2.7.0)
event_emitter (0.2.5)
eventmachine (1.2.1)
@@ -182,8 +187,8 @@ GEM
ffi (1.9.17)
ffi (1.9.17-x64-mingw32)
ffi (1.9.17-x86-mingw32)
- globalid (0.3.7)
- activesupport (>= 4.1.0)
+ globalid (0.4.0)
+ activesupport (>= 4.2.0)
hiredis (0.6.1)
http_parser.rb (0.6.0)
i18n (0.8.1)
@@ -319,6 +324,7 @@ GEM
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
+ sprockets-export (0.9.1)
sprockets-rails (3.2.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
@@ -335,12 +341,12 @@ GEM
rack (>= 1, < 3)
thor (0.19.4)
thread (0.1.7)
- thread_safe (0.3.5)
+ thread_safe (0.3.6)
tilt (2.0.5)
turbolinks (5.0.1)
turbolinks-source (~> 5)
turbolinks-source (5.0.0)
- tzinfo (1.2.2)
+ tzinfo (1.2.3)
thread_safe (~> 0.1)
tzinfo-data (1.2016.10)
tzinfo (>= 1.0.0)
@@ -370,13 +376,14 @@ DEPENDENCIES
activerecord-jdbcmysql-adapter (>= 1.3.0)
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
+ arel!
backburner
bcrypt (~> 3.1.11)
benchmark-ips
blade
blade-sauce_labs_plugin
byebug
- capybara (~> 2.7.0)
+ capybara (~> 2.13.0)
coffee-rails
dalli (>= 2.2.1)
delayed_job
@@ -414,6 +421,7 @@ DEPENDENCIES
sequel
sidekiq
sneakers
+ sprockets-export
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
@@ -425,4 +433,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
- 1.14.4
+ 1.14.6
diff --git a/RAILS_VERSION b/RAILS_VERSION
index d5d15fa148..d5ebf861d3 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-5.1.0.beta1
+5.2.0.alpha
diff --git a/README.md b/README.md
index c4fa983c52..7e25a0c8f1 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ and may also be used independently outside Rails.
* [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html)
* [Ruby on Rails Guides](http://guides.rubyonrails.org)
* [The API Documentation](http://api.rubyonrails.org)
- * [Ruby on Rails Tutorial](http://www.railstutorial.org/book)
+ * [Ruby on Rails Tutorial](https://www.railstutorial.org/book)
## Contributing
diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md
index 10a8bca3b3..6e27d8ee5a 100644
--- a/RELEASING_RAILS.md
+++ b/RELEASING_RAILS.md
@@ -58,7 +58,7 @@ lists:
Implementors will love you and help you.
-### 3 Days before release
+## 3 Days before release
This is when you should release the release candidate. Here are your tasks
for today:
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index a0254fe323..d5bd58cfdb 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,45 +1 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Redis subscription adapters now support `channel_prefix` option in `cable.yml`
-
- Avoids channel name collisions when multiple apps use the same Redis server.
-
- *Chad Ingram*
-
-* Permit same-origin connections by default.
-
- Added new option `config.action_cable.allow_same_origin_as_host = false`
- to disable this behaviour.
-
- *Dávid Halász*, *Matthew Draper*
-
-* Prevent race where the client could receive and act upon a
- subscription confirmation before the channel's `subscribed` method
- completed.
-
- Fixes #25381.
-
- *Vladimir Dementyev*
-
-* Buffer now writes to WebSocket connections, to avoid blocking threads
- that could be doing more useful things.
-
- *Matthew Draper*, *Tinco Andringa*
-
-* Protect against concurrent writes to a WebSocket connection from
- multiple threads; the underlying OS write is not always threadsafe.
-
- *Tinco Andringa*
-
-* Add `ActiveSupport::Notifications` hook to `Broadcaster#broadcast`.
-
- *Matthew Wear*
-
-* Close hijacked socket when connection is shut down.
-
- Fixes #25613.
-
- *Tinco Andringa*
-
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actioncable/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actioncable/CHANGELOG.md) for previous changes.
diff --git a/actioncable/README.md b/actioncable/README.md
index c55b7dc57b..e044f54b45 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -53,8 +53,8 @@ module ApplicationCable
private
def find_verified_user
- if current_user = User.find_by(id: cookies.signed[:user_id])
- current_user
+ if verified_user = User.find_by(id: cookies.signed[:user_id])
+ verified_user
else
reject_unauthorized_connection
end
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
index 87d443919c..bda8c7b6c8 100644
--- a/actioncable/Rakefile
+++ b/actioncable/Rakefile
@@ -1,12 +1,13 @@
require "rake/testtask"
require "pathname"
+require "open3"
require "action_cable"
dir = File.dirname(__FILE__)
task default: :test
-task package: "assets:compile"
+task package: %w( assets:compile assets:verify )
Rake::TestTask.new do |t|
t.libs << "test"
@@ -37,6 +38,39 @@ namespace :assets do
desc "Compile Action Cable assets"
task :compile do
require "blade"
+ require "sprockets"
+ require "sprockets/export"
Blade.build
end
+
+ desc "Verify compiled Action Cable assets"
+ task :verify do
+ file = "lib/assets/compiled/action_cable.js"
+ pathname = Pathname.new("#{dir}/#{file}")
+
+ print "[verify] #{file} exists "
+ if pathname.exist?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{file} is a UMD module "
+ if pathname.read =~ /module\.exports.*define\.amd/m
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{dir} can be required as a module "
+ stdout, stderr, status = Open3.capture3("node", "--print", "window = {}; require('#{dir}');")
+ if status.success?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]\n#{stderr}"
+ fail
+ end
+ end
end
diff --git a/actioncable/lib/action_cable/connection/authorization.rb b/actioncable/lib/action_cable/connection/authorization.rb
index 85df206445..989a67d6df 100644
--- a/actioncable/lib/action_cable/connection/authorization.rb
+++ b/actioncable/lib/action_cable/connection/authorization.rb
@@ -3,11 +3,11 @@ module ActionCable
module Authorization
class UnauthorizedError < StandardError; end
- private
- def reject_unauthorized_connection
- logger.error "An unauthorized connection attempt was rejected"
- raise UnauthorizedError
- end
+ # Closes the \WebSocket connection if it is open and returns a 404 "File not Found" response.
+ def reject_unauthorized_connection
+ logger.error "An unauthorized connection attempt was rejected"
+ raise UnauthorizedError
+ end
end
end
end
diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb
index c09613a747..5d6f9af0bb 100644
--- a/actioncable/lib/action_cable/gem_version.rb
+++ b/actioncable/lib/action_cable/gem_version.rb
@@ -6,9 +6,9 @@ module ActionCable
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actioncable/package.json b/actioncable/package.json
index 69ae3519d9..acec1e2e9c 100644
--- a/actioncable/package.json
+++ b/actioncable/package.json
@@ -1,6 +1,6 @@
{
"name": "actioncable",
- "version": "5.1.0-beta1",
+ "version": "5.2.0-alpha",
"description": "WebSocket framework for Ruby on Rails.",
"main": "lib/assets/compiled/action_cable.js",
"files": [
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
index 31dcde2e29..50fc7be30b 100644
--- a/actioncable/test/channel/stream_test.rb
+++ b/actioncable/test/channel/stream_test.rb
@@ -55,6 +55,8 @@ module ActionCable::StreamTests
channel = ChatChannel.new connection, "{id: 1}", id: 1
channel.subscribe_to_channel
+ wait_for_async
+
connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
channel.unsubscribe_from_channel
end
@@ -67,6 +69,8 @@ module ActionCable::StreamTests
channel = SymbolChannel.new connection, ""
channel.subscribe_to_channel
+ wait_for_async
+
connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
channel.unsubscribe_from_channel
end
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 98a114a5f4..30ac1e9c38 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -68,12 +68,33 @@ class ClientTest < ActionCable::TestCase
server.min_threads = 1
server.max_threads = 4
- t = Thread.new { server.run.join }
- yield port
+ thread = server.run
- ensure
- server.stop(true) if server
- t.join if t
+ begin
+ yield port
+
+ ensure
+ server.stop
+
+ begin
+ thread.join
+
+ rescue IOError
+ # Work around https://bugs.ruby-lang.org/issues/13405
+ #
+ # Puma's sometimes raising while shutting down, when it closes
+ # its internal pipe. We can safely ignore that, but we do need
+ # to do the step skipped by the exception:
+ server.binder.close
+
+ rescue RuntimeError => ex
+ # Work around https://bugs.ruby-lang.org/issues/13239
+ raise unless ex.message =~ /can't modify frozen IOError/
+
+ # Handle this as if it were the IOError: do the same as above.
+ server.binder.close
+ end
+ end
end
class SyncClient
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index ee33450b45..e488d867de 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,33 +1 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Add `:args` to `process.action_mailer` event.
-
- *Yuji Yaginuma*
-
-* Add parameterized invocation of mailers as a way to share before filters and defaults between actions.
- See `ActionMailer::Parameterized` for a full example of the benefit.
-
- *DHH*
-
-* Allow lambdas to be used as lazy defaults in addition to procs.
-
- *DHH*
-
-* Mime type: allow to custom content type when setting body in headers
- and attachments.
-
- Example:
-
- def test_emails
- attachments["invoice.pdf"] = "This is test File content"
- mail(body: "Hello there", content_type: "text/html")
- end
-
- *Minh Quy*
-
-* Exception handling: use `rescue_from` to handle exceptions raised by
- mailer actions, by message delivery, and by deferred delivery jobs.
-
- *Jeremy Daer*
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 9b5d39faea..6849f5c0f9 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -133,6 +133,9 @@ module ActionMailer
#
# config.action_mailer.default_url_options = { host: "example.com" }
#
+ # You can also define a <tt>default_url_options</tt> method on individual mailers to override these
+ # default settings per-mailer.
+ #
# By default when <tt>config.force_ssl</tt> is true, URLs generated for hosts will use the HTTPS protocol.
#
# = Sending mail
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index de2d71bd3e..f5594ef928 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -6,9 +6,9 @@ module ActionMailer
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index a79d77e1e5..c0683be94d 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -76,14 +76,14 @@ class MessageDeliveryTest < ActiveSupport::TestCase
test "should enqueue a delivery with a delay" do
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f + 600.seconds, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
- @mail.deliver_later wait: 600.seconds
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current + 10.minutes, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
+ @mail.deliver_later wait: 10.minutes
end
end
end
test "should enqueue a delivery at a specific time" do
- later_time = Time.now.to_f + 3600
+ later_time = Time.current + 1.hour
assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
@mail.deliver_later wait_until: later_time
end
diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb
index 914ed12312..e988fffcb9 100644
--- a/actionmailer/test/parameterized_test.rb
+++ b/actionmailer/test/parameterized_test.rb
@@ -14,7 +14,6 @@ class ParameterizedTest < ActiveSupport::TestCase
@previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name
ActionMailer::Base.deliver_later_queue_name = :test_queue
- ActionMailer::Base.delivery_method = :test
@mail = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com").invitation
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 641af029aa..51cec82801 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,383 +1,13 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
+* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load`
-* Prefer `remove_method` over `undef_method` when reloading routes
+ `ActionController::Base` and `ActionController::API` have differing implementations. This means that
+ the one umbrella hook `action_controller` is not able to address certain situations where a method
+ may not exist in a certain implementation.
- When `undef_method` is used it prevents access to other implementations of that
- url helper in the ancestor chain so use `remove_method` instead to restore access.
+ This is fixed by adding two new hooks so you can target `ActionController::Base` vs `ActionController::API`
- *Andrew White*
+ Fixes #27013.
-* Add the `resolve` method to the routing DSL
+ *Julian Nadeau*
- This new method allows customization of the polymorphic mapping of models:
-
- ``` ruby
- resource :basket
- resolve(class: "Basket") { [:basket] }
- ```
-
- ``` erb
- <%= form_for @basket do |form| %>
- <!-- basket form -->
- <% end %>
- ```
-
- This generates the correct singular URL for the form instead of the default
- resources member url, e.g. `/basket` vs. `/basket/:id`.
-
- Fixes #1769.
-
- *Andrew White*
-
-* Add the `direct` method to the routing DSL
-
- This new method allows creation of custom url helpers, e.g:
-
- ``` ruby
- direct(:apple) { "http://www.apple.com" }
-
- >> apple_url
- => "http://www.apple.com"
- ```
-
- This has the advantage of being available everywhere url helpers are available
- unlike custom url helpers defined in helper modules, etc.
-
- *Andrew White*
-
-* Add `ActionDispatch::SystemTestCase` to Action Pack
-
- Adds Capybara integration directly into Rails through Action Pack!
-
- See PR [#26703](https://github.com/rails/rails/pull/26703)
-
- *Eileen M. Uchitelle*
-
-* Remove deprecated `.to_prepare`, `.to_cleanup`, `.prepare!` and `.cleanup!` from `ActionDispatch::Reloader`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionDispatch::Callbacks.to_prepare` and `ActionDispatch::Callbacks.to_cleanup`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionController::Metal.call`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionController::Metal#env`.
-
- *Rafael Mendonça França*
-
-* Make `with_routing` test helper work when testing controllers inheriting from `ActionController::API`
-
- *Julia López*
-
-* Use accept header in integration tests with `as: :json`
-
- Instead of appending the `format` to the request path, Rails will figure
- out the format from the header instead.
-
- This allows devs to use `:as` on routes that don't have a format.
-
- Fixes #27144.
-
- *Kasper Timm Hansen*
-
-* Reset a new session directly after its creation in `ActionDispatch::IntegrationTest#open_session`.
-
- Fixes #22742.
-
- *Tawan Sierek*
-
-* Fixes incorrect output from `rails routes` when using singular resources.
-
- Fixes #26606.
-
- *Erick Reyna*
-
-* Fixes multiple calls to `logger.fatal` instead of a single call,
- for every line in an exception backtrace, when printing trace
- from `DebugExceptions` middleware.
-
- Fixes #26134.
-
- *Vipul A M*
-
-* Add support for arbitrary hashes in strong parameters:
-
- ```ruby
- params.permit(preferences: {})
- ```
-
- *Xavier Noria*
-
-* Add `ActionController::Parameters#merge!`, which behaves the same as `Hash#merge!`.
-
- *Yuji Yaginuma*
-
-* Allow keys not found in `RACK_KEY_TRANSLATION` for setting the environment when rendering
- arbitrary templates.
-
- *Sammy Larbi*
-
-* Remove deprecated support to non-keyword arguments in `ActionDispatch::IntegrationTest#process`,
- `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionDispatch::IntegrationTest#*_via_redirect`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionDispatch::IntegrationTest#xml_http_request`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support for passing `:path` and route path as strings in `ActionDispatch::Routing::Mapper#match`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support for passing path as `nil` in `ActionDispatch::Routing::Mapper#match`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `cache_control` argument from `ActionDispatch::Static#initialize`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to passing strings or symbols to the middleware stack.
-
- *Rafael Mendonça França*
-
-* Change HSTS subdomain to true.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `host` and `port` ssl options.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `const_error` argument in
- `ActionDispatch::Session::SessionRestoreError#initialize`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `#original_exception` in `ActionDispatch::Session::SessionRestoreError`.
-
- *Rafael Mendonça França*
-
-* Deprecate `ActionDispatch::ParamsParser::ParseError` in favor of
- `ActionDispatch::Http::Parameters::ParseError`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionDispatch::ParamsParser`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `original_exception` and `message` arguments in
- `ActionDispatch::ParamsParser::ParseError#initialize`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `#original_exception` in `ActionDispatch::ParamsParser::ParseError`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated access to mime types through constants.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to non-keyword arguments in `ActionController::TestCase#process`,
- `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `xml_http_request` and `xhr` methods in `ActionController::TestCase`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods in `ActionController::Parameters`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to comparing a `ActionController::Parameters`
- with a `Hash`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to `:text` in `render`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to `:nothing` in `render`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to `:back` in `redirect_to`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to passing status as option `head`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to passing original exception to `ActionController::BadRequest`
- and the `ActionController::BadRequest#original_exception` method.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods `skip_action_callback`, `skip_filter`, `before_filter`,
- `prepend_before_filter`, `skip_before_filter`, `append_before_filter`, `around_filter`
- `prepend_around_filter`, `skip_around_filter`, `append_around_filter`, `after_filter`,
- `prepend_after_filter`, `skip_after_filter` and `append_after_filter`.
-
- *Rafael Mendonça França*
-
-* Show an "unmatched constraints" error when params fail to match constraints
- on a matched route, rather than a "missing keys" error.
-
- Fixes #26470.
-
- *Chris Carter*
-
-* Fix adding implicitly rendered template digests to ETags.
-
- Fixes a case when modifying an implicitly rendered template for a
- controller action using `fresh_when` or `stale?` would not result in a new
- `ETag` value.
-
- *Javan Makhmali*
-
-* Make `fixture_file_upload` work in integration tests.
-
- *Yuji Yaginuma*
-
-* Add `to_param` to `ActionController::Parameters` deprecations.
-
- In the future `ActionController::Parameters` are discouraged from being used
- in URLs without explicit whitelisting. Go through `to_h` to use `to_param`.
-
- *Kir Shatrov*
-
-* Fix nested multiple roots
-
- The PR #20940 enabled the use of multiple roots with different constraints
- at the top level but unfortunately didn't work when those roots were inside
- a namespace and also broke the use of root inside a namespace after a top
- level root was defined because the check for the existence of the named route
- used the global :root name and not the namespaced name.
-
- This is fixed by using the name_for_action method to expand the :root name to
- the full namespaced name. We can pass nil for the second argument as we're not
- dealing with resource definitions so don't need to handle the cases for edit
- and new routes.
-
- Fixes #26148.
-
- *Ryo Hashimoto*, *Andrew White*
-
-* Include the content of the flash in the auto-generated etag. This solves the following problem:
-
- 1. POST /messages
- 2. redirect_to messages_url, notice: 'Message was created'
- 3. GET /messages/1
- 4. GET /messages
-
- Step 4 would before still include the flash message, even though it's no longer relevant,
- because the etag cache was recorded with the flash in place and didn't change when it was gone.
-
- *DHH*
-
-* SSL: Changes redirect behavior for all non-GET and non-HEAD requests
- (like POST/PUT/PATCH etc) to `http://` resources to redirect to `https://`
- with a [307 status code](http://tools.ietf.org/html/rfc7231#section-6.4.7) instead of [301 status code](http://tools.ietf.org/html/rfc7231#section-6.4.2).
-
- 307 status code instructs the HTTP clients to preserve the original
- request method while redirecting. It has been part of HTTP RFC since
- 1999 and is implemented/recognized by most (if not all) user agents.
-
- # Before
- POST http://example.com/articles (i.e. ArticlesContoller#create)
- redirects to
- GET https://example.com/articles (i.e. ArticlesContoller#index)
-
- # After
- POST http://example.com/articles (i.e. ArticlesContoller#create)
- redirects to
- POST https://example.com/articles (i.e. ArticlesContoller#create)
-
- *Chirag Singhal*
-
-* Add `:as` option to `ActionController:TestCase#process` and related methods.
-
- Specifying `as: mime_type` allows the `CONTENT_TYPE` header to be specified
- in controller tests without manually doing this through `@request.headers['CONTENT_TYPE']`.
-
- *Everest Stefan Munro-Zeisberger*
-
-* Show cache hits and misses when rendering partials.
-
- Partials using the `cache` helper will show whether a render hit or missed
- the cache:
-
- ```
- Rendered messages/_message.html.erb in 1.2 ms [cache hit]
- Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss]
- ```
-
- This removes the need for the old fragment cache logging:
-
- ```
- Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms)
- Rendered messages/_message.html.erb in 1.2 ms [cache hit]
- Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms)
- Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss]
- ```
-
- Though that full output can be reenabled with
- `config.action_controller.enable_fragment_cache_logging = true`.
-
- *Stan Lo*
-
-* Don't override the `Accept` header in integration tests when called with `xhr: true`.
-
- Fixes #25859.
-
- *David Chen*
-
-* Fix `defaults` option for root route.
-
- A regression from some refactoring for the 5.0 release, this change
- fixes the use of `defaults` (default parameters) in the `root` routing method.
-
- *Chris Arcand*
-
-* Check `request.path_parameters` encoding at the point they're set.
-
- Check for any non-UTF8 characters in path parameters at the point they're
- set in `env`. Previously they were checked for when used to get a controller
- class, but this meant routes that went directly to a Rack app, or skipped
- controller instantiation for some other reason, had to defend against
- non-UTF8 characters themselves.
-
- *Grey Baker*
-
-* Don't raise `ActionController::UnknownHttpMethod` from `ActionDispatch::Static`.
-
- Pass `Rack::Request` objects to `ActionDispatch::FileHandler` to avoid it
- raising `ActionController::UnknownHttpMethod`. If an unknown method is
- passed, it should pass exception higher in the stack instead, once we've had a
- chance to define exception handling behaviour.
-
- *Grey Baker*
-
-* Handle `Rack::QueryParser` errors in `ActionDispatch::ExceptionWrapper`.
-
- Updated `ActionDispatch::ExceptionWrapper` to handle the Rack 2.0 namespace
- for `ParameterTypeError` and `InvalidParameterError` errors.
-
- *Grey Baker*
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index 5cd8d77ddb..94698df730 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -81,10 +81,9 @@ module ActionController
# end
# end
#
- # Quite straightforward. Make sure to check the modules included in
- # <tt>ActionController::Base</tt> if you want to use any other
- # functionality that is not provided by <tt>ActionController::API</tt>
- # out of the box.
+ # Make sure to check the modules included in <tt>ActionController::Base</tt>
+ # if you want to use any other functionality that is not provided
+ # by <tt>ActionController::API</tt> out of the box.
class API < Metal
abstract!
@@ -142,6 +141,7 @@ module ActionController
include mod
end
+ ActiveSupport.run_load_hooks(:action_controller_api, self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index ca8066cd82..8c2b111f89 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -8,7 +8,7 @@ module ActionController
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
#
# By default, only the ApplicationController in a \Rails application inherits from <tt>ActionController::Base</tt>. All other
- # controllers in turn inherit from ApplicationController. This gives you one class to configure things such as
+ # controllers inherit from ApplicationController. This gives you one class to configure things such as
# request forgery protection and filtering of sensitive request parameters.
#
# A sample controller could look like this:
@@ -30,7 +30,7 @@ module ActionController
#
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
# new post), it initiates a redirect instead. This redirect works by returning an external
- # "302 Moved" HTTP response that takes the user to the index action.
+ # <tt>302 Moved</tt> HTTP response that takes the user to the index action.
#
# These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect.
# Most actions are variations on these themes.
@@ -59,7 +59,7 @@ module ActionController
# <input type="text" name="post[name]" value="david">
# <input type="text" name="post[address]" value="hyacintvej">
#
- # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
+ # A request coming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
# If the address input had been named <tt>post[address][street]</tt>, the <tt>params</tt> would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
@@ -74,7 +74,7 @@ module ActionController
#
# session[:person] = Person.authenticate(user_name, password)
#
- # And retrieved again through the same hash:
+ # You can retrieve it again through the same hash:
#
# Hello #{session[:person]}
#
@@ -261,6 +261,13 @@ module ActionController
PROTECTED_IVARS
end
+ def self.make_response!(request)
+ ActionDispatch::Response.create.tap do |res|
+ res.request = request
+ end
+ end
+
+ ActiveSupport.run_load_hooks(:action_controller_base, self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 337718afc0..246644dcbd 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -129,7 +129,7 @@ module ActionController
end
def self.make_response!(request)
- ActionDispatch::Response.create.tap do |res|
+ ActionDispatch::Response.new.tap do |res|
res.request = request
end
end
@@ -138,7 +138,7 @@ module ActionController
false
end
- # Delegates to the class' <tt>controller_name</tt>
+ # Delegates to the class' <tt>controller_name</tt>.
def controller_name
self.class.controller_name
end
@@ -244,7 +244,7 @@ module ActionController
end
end
- # Direct dispatch to the controller. Instantiates the controller, then
+ # Direct dispatch to the controller. Instantiates the controller, then
# executes the action named +name+.
def self.dispatch(name, req, res)
if middleware_stack.any?
diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb
index 474d75f02e..7bd338bd7c 100644
--- a/actionpack/lib/action_controller/metal/etag_with_flash.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb
@@ -1,9 +1,9 @@
module ActionController
# When you're using the flash, it's generally used as a conditional on the view.
# This means the content of the view depends on the flash. Which in turn means
- # that the etag for a response should be computed with the content of the flash
+ # that the ETag for a response should be computed with the content of the flash
# in mind. This does that by including the content of the flash as a component
- # in the etag that's generated for a response.
+ # in the ETag that's generated for a response.
module EtagWithFlash
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 9d43e752ac..73e67573ca 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -2,17 +2,17 @@ require "active_support/core_ext/hash/except"
require "active_support/core_ext/hash/slice"
module ActionController
- # This module provides a method which will redirect the browser to use HTTPS
- # protocol. This will ensure that user's sensitive information will be
+ # This module provides a method which will redirect the browser to use the secured HTTPS
+ # protocol. This will ensure that users' sensitive information will be
# transferred safely over the internet. You _should_ always force the browser
# to use HTTPS when you're transferring sensitive information such as
# user authentication, account information, or credit card information.
#
# Note that if you are really concerned about your application security,
# you might consider using +config.force_ssl+ in your config file instead.
- # That will ensure all the data transferred via HTTPS protocol and prevent
- # the user from getting their session hijacked when accessing the site over
- # unsecured HTTP protocol.
+ # That will ensure all the data is transferred via HTTPS, and will
+ # prevent the user from getting their session hijacked when accessing the
+ # site over unsecured HTTP protocol.
module ForceSSL
extend ActiveSupport::Concern
include AbstractController::Callbacks
@@ -23,7 +23,7 @@ module ActionController
module ClassMethods
# Force the request to this particular controller or specified actions to be
- # under HTTPS protocol.
+ # through the HTTPS protocol.
#
# If you need to disable this for any reason (e.g. development) then you can use
# an +:if+ or +:unless+ condition.
@@ -71,7 +71,7 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host_or_options</tt> - Either a host name or any of the url &
+ # * <tt>host_or_options</tt> - Either a host name or any of the url and
# redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 0575360068..d8bc895265 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -445,7 +445,7 @@ module ActionController
end
end
- # Parses the token and options out of the token authorization header.
+ # Parses the token and options out of the token Authorization header.
# The value for the Authorization header is expected to have the prefix
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
# Authorization: Token token="abc", nonce="def"
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index dde924e682..eeb27f99f4 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -2,11 +2,11 @@ module ActionController
# Handles implicit rendering for a controller action that does not
# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
#
- # For API controllers, the implicit response is always 204 No Content.
+ # For API controllers, the implicit response is always <tt>204 No Content</tt>.
#
# For all other controllers, we use these heuristics to decide whether to
# render a template, raise an error for a missing template, or respond with
- # 204 No Content:
+ # <tt>204 No Content</tt>:
#
# First, if we DO find a template, it's rendered. Template lookup accounts
# for the action name, locales, format, variant, template handlers, and more
@@ -23,7 +23,7 @@ module ActionController
# <tt>ActionView::UnknownFormat</tt> with an explanation.
#
# Finally, if we DON'T find a template AND the request isn't a browser page
- # load, then we implicitly respond with 204 No Content.
+ # load, then we implicitly respond with <tt>204 No Content</tt>.
module ImplicitRender
# :stopdoc:
include BasicImplicitRender
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 924686218f..2485d27cec 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -3,7 +3,7 @@ require "abstract_controller/logger"
module ActionController
# Adds instrumentation to several ends in ActionController::Base. It also provides
- # some hooks related with process_action, this allows an ORM like Active Record
+ # some hooks related with process_action. This allows an ORM like Active Record
# and/or DataMapper to plug in ActionController and show related information.
#
# Check ActiveRecord::Railties::ControllerRuntime for an example.
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index fed99e6c82..a607ee2309 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -239,8 +239,8 @@ module ActionController
error = nil
# This processes the action in a child thread. It lets us return the
- # response code and headers back up the rack stack, and still process
- # the body in parallel with sending data to the client
+ # response code and headers back up the Rack stack, and still process
+ # the body in parallel with sending data to the client.
new_controller_thread {
ActiveSupport::Dependencies.interlock.running do
t2 = Thread.current
@@ -278,9 +278,9 @@ module ActionController
raise error if error
end
- # Spawn a new thread to serve up the controller in. This is to get
+ # Spawn a new thread to serve up the controller in. This is to get
# around the fact that Rack isn't based around IOs and we need to use
- # a thread to stream data from the response bodies. Nobody should call
+ # a thread to stream data from the response bodies. Nobody should call
# this method except in Rails internals. Seriously!
def new_controller_thread # :nodoc:
Thread.new {
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index f6aabcb102..96bd548268 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -181,8 +181,8 @@ module ActionController #:nodoc:
#
# request.variant = [:tablet, :phone]
#
- # which will work similarly to formats and MIME types negotiation. If there will be no
- # +:tablet+ variant declared, +:phone+ variant will be picked:
+ # This will work similarly to formats and MIME types negotiation. If there
+ # is no +:tablet+ variant declared, the +:phone+ variant will be used:
#
# respond_to do |format|
# format.html.none
diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb
index 962532ff09..ecc691619e 100644
--- a/actionpack/lib/action_controller/metal/parameter_encoding.rb
+++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb
@@ -39,7 +39,7 @@ module ActionController
# end
#
# The show action in the above controller would have all parameter values
- # encoded as ASCII-8BIT. This is useful in the case where an application
+ # encoded as ASCII-8BIT. This is useful in the case where an application
# must handle data but encoding of the data is unknown, like file system data.
def skip_parameter_encoding(action)
@_parameter_encodings[action.to_s] = true
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 7fc898f034..a89fc1678b 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -105,7 +105,11 @@ module ActionController
unless super || exclude
if m.respond_to?(:attribute_names) && m.attribute_names.any?
- self.include = m.attribute_names
+ if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty?
+ self.include = m.attribute_names + m.stored_attributes.values.flatten.map(&:to_s)
+ else
+ self.include = m.attribute_names
+ end
end
end
end
@@ -213,7 +217,7 @@ module ActionController
end
# Sets the default wrapper key or model which will be used to determine
- # wrapper key and attribute names. Will be called automatically when the
+ # wrapper key and attribute names. Called automatically when the
# module is inherited.
def inherited(klass)
if klass._wrapper_options.format.any?
@@ -225,7 +229,7 @@ module ActionController
end
end
- # Performs parameters wrapping upon the request. Will be called automatically
+ # Performs parameters wrapping upon the request. Called automatically
# by the metal call stack.
def process_action(*args)
if _wrapper_enabled?
@@ -238,11 +242,11 @@ module ActionController
wrapped_keys = request.request_parameters.keys
wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
- # This will make the wrapped hash accessible from controller and view
+ # This will make the wrapped hash accessible from controller and view.
request.parameters.merge! wrapped_hash
request.request_parameters.merge! wrapped_hash
- # This will display the wrapped hash in the log file
+ # This will display the wrapped hash in the log file.
request.filtered_parameters.merge! wrapped_filtered_hash
end
super
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index a349841082..fdfe82f96b 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -22,7 +22,7 @@ module ActionController
# redirect_to posts_url
# redirect_to proc { edit_post_url(@post) }
#
- # The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
+ # The redirection happens as a <tt>302 Found</tt> header unless otherwise specified using the <tt>:status</tt> option:
#
# redirect_to post_url(@post), status: :found
# redirect_to action: 'atom', status: :moved_permanently
@@ -36,7 +36,7 @@ module ActionController
# If you are using XHR requests other than GET or POST and redirecting after the
# request then some browsers will follow the redirect using the original request
# method. This may lead to undesirable behavior such as a double DELETE. To work
- # around this you can return a <tt>303 See Other</tt> status code which will be
+ # around this you can return a <tt>303 See Other</tt> status code which will be
# followed using a GET request.
#
# redirect_to posts_url, status: :see_other
@@ -50,6 +50,9 @@ module ActionController
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
#
+ # Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
+ # To terminate the execution of the function immediately after the +redirect_to+, use return.
+ # redirect_to post_url(@post) and return
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 6b17719381..67f207afc2 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -36,7 +36,7 @@ module ActionController
super
end
- # Overwrite render_to_string because body can now be set to a rack body.
+ # Overwrite render_to_string because body can now be set to a Rack body.
def render_to_string(*)
result = super
if result.respond_to?(:each)
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index e8965a6561..5051c02a62 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -213,7 +213,11 @@ module ActionController #:nodoc:
if !verified_request?
if logger && log_warning_on_csrf_failure
- logger.warn "Can't verify CSRF token authenticity."
+ if valid_request_origin?
+ logger.warn "Can't verify CSRF token authenticity."
+ else
+ logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
+ end
end
handle_unverified_request
end
@@ -262,9 +266,9 @@ module ActionController #:nodoc:
# Returns true or false if a request is verified. Checks:
#
- # * Is it a GET or HEAD request? Gets should be safe and idempotent
+ # * Is it a GET or HEAD request? GETs should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
- # * Does the X-CSRF-Token header match the form_authenticity_token
+ # * Does the X-CSRF-Token header match the form_authenticity_token?
def verified_request? # :doc:
!protect_against_forgery? || request.get? || request.head? ||
(valid_request_origin? && any_authenticity_token_valid?)
@@ -327,7 +331,7 @@ module ActionController #:nodoc:
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
# This is actually an unmasked token. This is expected if
# you have just upgraded to masked tokens, but should stop
- # happening shortly after installing this gem
+ # happening shortly after installing this gem.
compare_with_real_token masked_token, session
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
@@ -336,13 +340,13 @@ module ActionController #:nodoc:
compare_with_real_token(csrf_token, session) ||
valid_per_form_csrf_token?(csrf_token, session)
else
- false # Token is malformed
+ false # Token is malformed.
end
end
def unmask_token(masked_token) # :doc:
# Split the token into the one-time pad and the encrypted
- # value and decrypt it
+ # 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)
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index 2d99e4045b..25757938f5 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -10,7 +10,7 @@ module ActionController #:nodoc:
# exceptions must be shown. This method is only called when
# consider_all_requests_local is false. By default, it returns
# false, but someone may set it to `request.local?` so local
- # requests in production still shows the detailed exception pages.
+ # requests in production still show the detailed exception pages.
def show_detailed_exceptions?
false
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 877a08b222..58cf60ad2a 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -3,7 +3,7 @@ require "rack/chunked"
module ActionController #:nodoc:
# Allows views to be streamed back to the client as they are rendered.
#
- # The default way Rails renders views is by first rendering the template
+ # By default, Rails renders views by first rendering the template
# and then the layout. The response is sent to the client after the whole
# template is rendered, all queries are made, and the layout is processed.
#
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index d304dcf468..baf15570d5 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -112,6 +112,77 @@ module ActionController
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
+ ##
+ # :method: as_json
+ #
+ # :call-seq:
+ # as_json(options=nil)
+ #
+ # Returns a hash that can be used as the JSON representation for the parameters.
+
+ ##
+ # :method: empty?
+ #
+ # :call-seq:
+ # empty?()
+ #
+ # Returns true if the parameters have no key/value pairs.
+
+ ##
+ # :method: has_key?
+ #
+ # :call-seq:
+ # has_key?(key)
+ #
+ # Returns true if the given key is present in the parameters.
+
+ ##
+ # :method: has_value?
+ #
+ # :call-seq:
+ # has_value?(value)
+ #
+ # Returns true if the given value is present for some key in the parameters.
+
+ ##
+ # :method: include?
+ #
+ # :call-seq:
+ # include?(key)
+ #
+ # Returns true if the given key is present in the parameters.
+
+ ##
+ # :method: key?
+ #
+ # :call-seq:
+ # key?(key)
+ #
+ # Returns true if the given key is present in the parameters.
+
+ ##
+ # :method: keys
+ #
+ # :call-seq:
+ # keys()
+ #
+ # Returns a new array of the keys of the parameters.
+
+ ##
+ # :method: value?
+ #
+ # :call-seq:
+ # value?(value)
+ #
+ # Returns true if the given value is present for some key in the parameters.
+
+ ##
+ # :method: values
+ #
+ # :call-seq:
+ # values()
+ #
+ # Returns a new array of the values of the parameters.
delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?,
:as_json, to: :@parameters
@@ -191,7 +262,7 @@ module ActionController
alias_method :to_unsafe_hash, :to_unsafe_h
# Convert all hashes in values into parameters, then yield each pair in
- # the same way as <tt>Hash#each_pair</tt>
+ # the same way as <tt>Hash#each_pair</tt>.
def each_pair(&block)
@parameters.each_pair do |key, value|
yield key, convert_hashes_to_parameters(key, value)
@@ -339,7 +410,7 @@ module ActionController
#
# params.permit(preferences: {})
#
- # but be careful because this opens the door to arbitrary input. In this
+ # Be careful because this opens the door to arbitrary input. In this
# case, +permit+ ensures values in the returned structure are permitted
# scalars and filters out anything else.
#
@@ -575,20 +646,37 @@ module ActionController
end
# Returns a new <tt>ActionController::Parameters</tt> with all keys from
- # +other_hash+ merges into current hash.
+ # +other_hash+ merged into current hash.
def merge(other_hash)
new_instance_with_inherited_permitted_status(
@parameters.merge(other_hash.to_h)
)
end
- # Returns current <tt>ActionController::Parameters</tt> instance which
- # +other_hash+ merges into current hash.
+ # Returns current <tt>ActionController::Parameters</tt> instance with
+ # +other_hash+ merged into current hash.
def merge!(other_hash)
@parameters.merge!(other_hash.to_h)
self
end
+ # Returns a new <tt>ActionController::Parameters</tt> with all keys from
+ # current hash merged into +other_hash+.
+ def reverse_merge(other_hash)
+ new_instance_with_inherited_permitted_status(
+ other_hash.to_h.merge(@parameters)
+ )
+ end
+ alias_method :with_defaults, :reverse_merge
+
+ # Returns current <tt>ActionController::Parameters</tt> instance with
+ # current hash merged into +other_hash+.
+ def reverse_merge!(other_hash)
+ @parameters.merge!(other_hash.to_h) { |key, left, right| left }
+ self
+ end
+ alias_method :with_defaults!, :reverse_merge!
+
# This is required by ActiveModel attribute assignment, so that user can
# pass +Parameters+ to a mass assignment methods in a model. It should not
# matter as we are using +HashWithIndifferentAccess+ internally.
@@ -629,7 +717,7 @@ module ActionController
undef_method :to_param
- # Returns duplicate of object including all parameters
+ # Returns duplicate of object including all parameters.
def deep_dup
self.class.new(@parameters.deep_dup).tap do |duplicate|
duplicate.permitted = @permitted
@@ -849,7 +937,7 @@ module ActionController
# whitelisted.
#
# In addition, parameters can be marked as required and flow through a
- # predefined raise/rescue flow to end up as a 400 Bad Request with no
+ # predefined raise/rescue flow to end up as a <tt>400 Bad Request</tt> with no
# effort.
#
# class PeopleController < ActionController::Base
@@ -862,7 +950,7 @@ module ActionController
# end
#
# # This will pass with flying colors as long as there's a person key in the
- # # parameters, otherwise it'll raise an ActionController::MissingParameter
+ # # parameters, otherwise it'll raise an ActionController::ParameterMissing
# # exception, which will get caught by ActionController::Base and turned
# # into a 400 Bad Request reply.
# def update
@@ -873,7 +961,7 @@ module ActionController
#
# private
# # Using a private method to encapsulate the permissible parameters is
- # # just a good pattern since you'll be able to reuse the same permit
+ # # a good pattern since you'll be able to reuse the same permit
# # list between create and update. Also, you can specialize this method
# # with per-user checking of permissible attributes.
# def person_params
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 9f3cc099d6..21ed5b4ec8 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -3,7 +3,7 @@ module ActionController
# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
#
# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
- # url options like the +host+. In order to do so, this module requires the host class
+ # URL options like the +host+. In order to do so, this module requires the host class
# to implement +env+ which needs to be Rack-compatible and +request+
# which is either an instance of +ActionDispatch::Request+ or an object
# that responds to the +host+, +optional_port+, +protocol+ and
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index a7cdfe6a98..fadfc8de60 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -42,7 +42,7 @@ module ActionController
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
- # Ensure readers methods get compiled
+ # Ensure readers methods get compiled.
options.asset_host ||= app.config.asset_host
options.relative_url_root ||= app.config.relative_url_root
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index acb400cd15..cbb719d8b2 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -5,7 +5,7 @@ module ActionController
# without requirement of being in controller actions.
#
# You get a concrete renderer class by invoking ActionController::Base#renderer.
- # For example,
+ # For example:
#
# ApplicationController.renderer
#
@@ -18,7 +18,7 @@ module ActionController
# ApplicationController.render template: '...'
#
# #render allows you to use the same options that you can use when rendering in a controller.
- # For example,
+ # For example:
#
# FooController.render :action, locals: { ... }, assigns: { ... }
#
@@ -56,7 +56,7 @@ module ActionController
# Create a new renderer for the same controller but with new defaults.
def with_defaults(defaults)
- self.class.new controller, env, self.defaults.merge(defaults)
+ self.class.new controller, @env, self.defaults.merge(defaults)
end
# Accepts a custom Rack environment to render templates in.
@@ -85,6 +85,7 @@ module ActionController
def normalize_keys(env)
new_env = {}
env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
+ new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
new_env
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 7229c67f30..bc42d50205 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -13,10 +13,10 @@ module ActionController
end
module Live
- # Disable controller / rendering threads in tests. User tests can access
+ # Disable controller / rendering threads in tests. User tests can access
# the database on the main thread, so they could open a txn, then the
# controller thread will open a new connection and try to access data
- # that's only visible to the main thread's txn. This is the problem in #23483
+ # that's only visible to the main thread's txn. This is the problem in #23483.
remove_method :new_controller_thread
def new_controller_thread # :nodoc:
yield
@@ -35,7 +35,7 @@ module ActionController
attr_reader :controller_class
- # Create a new test request with default `env` values
+ # Create a new test request with default `env` values.
def self.create(controller_class)
env = {}
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
@@ -131,7 +131,7 @@ module ActionController
include Rack::Test::Utils
def should_multipart?(params)
- # FIXME: lifted from Rack-Test. We should push this separation upstream
+ # FIXME: lifted from Rack-Test. We should push this separation upstream.
multipart = false
query = lambda { |value|
case value
@@ -300,7 +300,7 @@ module ActionController
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
- # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
+ # On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
@@ -534,7 +534,6 @@ module ActionController
@request.delete_header "HTTP_ACCEPT"
end
@request.query_string = ""
- @request.env.delete "PATH_INFO"
@response.sent!
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index c4fe3a5c09..19f89edbc1 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -135,9 +135,7 @@ module ActionDispatch
}
end
- # Receives an array of mimes and return the first user sent mime that
- # matches the order array.
- #
+ # Returns the first MIME type that matches the provided array of MIME types.
def negotiate_mime(order)
formats.each do |priority|
if priority == Mime::ALL
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 1583a8f87f..0cf010f59e 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -46,7 +46,7 @@ module Mime
end
end
- # Encapsulates the notion of a mime type. Can be used at render time, for example, with:
+ # Encapsulates the notion of a MIME type. Can be used at render time, for example, with:
#
# class PostsController < ActionController::Base
# def show
@@ -64,7 +64,7 @@ module Mime
@register_callbacks = []
- # A simple helper class used in parsing the accept header
+ # A simple helper class used in parsing the accept header.
class AcceptItem #:nodoc:
attr_accessor :index, :name, :q
alias :to_s :name
@@ -72,7 +72,7 @@ module Mime
def initialize(index, name, q = nil)
@index = index
@name = name
- q ||= 0.0 if @name == "*/*".freeze # default wildcard match to end of list
+ q ||= 0.0 if @name == "*/*".freeze # Default wildcard match to end of list.
@q = ((q || 1.0).to_f * 100).to_i
end
@@ -90,22 +90,22 @@ module Mime
text_xml_idx = find_item_by_name list, "text/xml"
app_xml_idx = find_item_by_name list, Mime[:xml].to_s
- # Take care of the broken text/xml entry by renaming or deleting it
+ # Take care of the broken text/xml entry by renaming or deleting it.
if text_xml_idx && app_xml_idx
app_xml = list[app_xml_idx]
text_xml = list[text_xml_idx]
- app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two
- if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
+ app_xml.q = [text_xml.q, app_xml.q].max # Set the q value to the max of the two.
+ if app_xml_idx > text_xml_idx # Make sure app_xml is ahead of text_xml in the list.
list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml
app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx
end
- list.delete_at(text_xml_idx) # delete text_xml from the list
+ list.delete_at(text_xml_idx) # Delete text_xml from the list.
elsif text_xml_idx
list[text_xml_idx].name = Mime[:xml].to_s
end
- # Look for more specific XML-based types and sort them ahead of app/xml
+ # Look for more specific XML-based types and sort them ahead of app/xml.
if app_xml_idx
app_xml = list[app_xml_idx]
idx = app_xml_idx
@@ -147,7 +147,7 @@ module Mime
EXTENSION_LOOKUP[extension.to_s]
end
- # Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for
+ # Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for
# rendering different HTML versions depending on the user agent, like an iPhone.
def register_alias(string, symbol, extension_synonyms = [])
register(string, symbol, [], extension_synonyms, true)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 8f21eca440..79a2ef965b 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -13,7 +13,7 @@ module ActionDispatch
}
# Raised when raw data from the request cannot be parsed by the parser
- # defined for request's content mime type.
+ # defined for request's content MIME type.
class ParseError < StandardError
def initialize
super($!.message)
@@ -30,9 +30,9 @@ module ActionDispatch
end
module ClassMethods
- # Configure the parameter parser for a given mime type.
+ # Configure the parameter parser for a given MIME type.
#
- # It accepts a hash where the key is the symbol of the mime type
+ # It accepts a hash where the key is the symbol of the MIME type
# and the value is a proc.
#
# original_parsers = ActionDispatch::Request.parameter_parsers
@@ -100,7 +100,7 @@ module ActionDispatch
begin
strategy.call(raw_post)
- rescue # JSON or Ruby code block errors
+ rescue # JSON or Ruby code block errors.
my_logger = logger || ActiveSupport::Logger.new($stderr)
my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}"
@@ -115,6 +115,7 @@ module ActionDispatch
end
module ParamsParser
- ParseError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("ActionDispatch::ParamsParser::ParseError", "ActionDispatch::Http::Parameters::ParseError")
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
+ deprecate_constant "ParseError", "ActionDispatch::Http::Parameters::ParseError"
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 19fa42ce12..6d42404a98 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -114,7 +114,7 @@ module ActionDispatch
HTTP_METHOD_LOOKUP = {}
- # Populate the HTTP method lookup cache
+ # Populate the HTTP method lookup cache.
HTTP_METHODS.each { |method|
HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
}
@@ -165,12 +165,12 @@ module ActionDispatch
def show_exceptions? # :nodoc:
# We're treating `nil` as "unset", and we want the default setting to be
- # `true`. This logic should be extracted to `env_config` and calculated
+ # `true`. This logic should be extracted to `env_config` and calculated
# once.
!(get_header("action_dispatch.show_exceptions".freeze) == false)
end
- # Returns a symbol form of the #request_method
+ # Returns a symbol form of the #request_method.
def request_method_symbol
HTTP_METHOD_LOOKUP[request_method]
end
@@ -182,7 +182,7 @@ module ActionDispatch
@method ||= check_method(get_header("rack.methodoverride.original_method") || get_header("REQUEST_METHOD"))
end
- # Returns a symbol form of the #method
+ # Returns a symbol form of the #method.
def method_symbol
HTTP_METHOD_LOOKUP[method]
end
@@ -267,7 +267,7 @@ module ActionDispatch
# (which sets the action_dispatch.request_id environment variable).
#
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
- # This relies on the rack variable set by the ActionDispatch::RequestId middleware.
+ # This relies on the Rack variable set by the ActionDispatch::RequestId middleware.
def request_id
get_header ACTION_DISPATCH_REQUEST_ID
end
@@ -339,7 +339,7 @@ module ActionDispatch
Session::Options.set self, options
end
- # Override Rack's GET method to support indifferent access
+ # Override Rack's GET method to support indifferent access.
def GET
fetch_header("action_dispatch.request.query_parameters") do |k|
rack_query_params = super || {}
@@ -352,7 +352,7 @@ module ActionDispatch
end
alias :query_parameters :GET
- # Override Rack's POST method to support indifferent access
+ # Override Rack's POST method to support indifferent access.
def POST
fetch_header("action_dispatch.request.request_parameters") do
pr = parse_formatted_parameters(params_parsers) do |params|
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index dc159596c4..2ee52eeb48 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -85,7 +85,7 @@ module ActionDispatch # :nodoc:
cattr_accessor(:default_headers)
include Rack::Response::Helpers
- # Aliasing these off because AD::Http::Cache::Response defines them
+ # Aliasing these off because AD::Http::Cache::Response defines them.
alias :_cache_control :cache_control
alias :_cache_control= :cache_control=
@@ -142,7 +142,7 @@ module ActionDispatch # :nodoc:
private
def each_chunk(&block)
- @buf.each(&block) # extract into own method
+ @buf.each(&block)
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index a6937d54ff..b6be48a48b 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -101,10 +101,8 @@ module ActionDispatch
end
def add_trailing_slash(path)
- # includes querysting
if path.include?("?")
path.sub!(/\?/, '/\&')
- # does not have a .format
elsif !path.include?(".")
path.sub!(/[^\/]\z|\A\z/, '\&/')
end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index f3b8e82d32..326f4e52f9 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -15,7 +15,7 @@ module ActionDispatch
def generate(name, options, path_parameters, parameterize = nil)
constraints = path_parameters.merge(options)
- missing_keys = nil # need for variable scope
+ missing_keys = nil
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index beb9f1ef3b..e1ac2c873e 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -109,7 +109,6 @@ module ActionDispatch
svg = to_svg
javascripts = [states, fsm_js]
- # Annoying hack warnings
fun_routes = fun_routes
stylesheets = stylesheets
svg = svg
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 927fd369c4..7bc15aa6b3 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -89,8 +89,15 @@ module ActionDispatch
end
end
+ # Needed for `rails routes`. Picks up succinctly defined requirements
+ # for a route, for example route
+ #
+ # get 'photo/:id', :controller => 'photos', :action => 'show',
+ # :id => /[A-Z]\d{5}/
+ #
+ # will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/}
+ # as requirements.
def requirements
- # needed for rails `rails routes`
@defaults.merge(path.requirements).delete_if { |_, v|
/.+?/ == v
}
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index d641642338..ffcd875b4d 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -5,7 +5,7 @@ module ActionDispatch
# Normalizes URI path.
#
# Strips off trailing slash and ensures there is a leading slash.
- # Also converts downcase url encoded string to uppercase.
+ # Also converts downcase URL encoded string to uppercase.
#
# normalize_path("/foo") # => "/foo"
# normalize_path("/foo/") # => "/foo"
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 1c50192867..335797f4b9 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -154,7 +154,7 @@ module ActionDispatch
end
end
- # Loop through the requirements AST
+ # Loop through the requirements AST.
class Each < FunctionalVisitor # :nodoc:
def visit(node, block)
block.call(node)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index c61cb3fd68..e565c03a8a 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -160,7 +160,7 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
- # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
+ # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed.
module ChainedCookieJars
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
#
@@ -345,16 +345,16 @@ module ActionDispatch
options[:path] ||= "/"
if options[:domain] == :all || options[:domain] == "all"
- # if there is a provided tld length then we use it otherwise default domain regexp
+ # If there is a provided tld length then we use it otherwise default domain regexp.
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
- # if host is not ip and matches domain regexp
+ # If host is not ip and matches domain regexp.
# (ip confirms to domain regexp so we explicitly check for ip)
options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
".#{$&}"
end
elsif options[:domain].is_a? Array
- # if host matches one of the supplied domains without a dot in front of it
+ # If host matches one of the supplied domains without a dot in front of it.
options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
end
end
@@ -404,7 +404,7 @@ module ActionDispatch
@delete_cookies[name.to_s] == options
end
- # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
+ # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie.
def clear(options = {})
@cookies.each_key { |k| delete(k, options) }
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index cbe2f4be4d..6b29ce63ba 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -65,7 +65,7 @@ module ActionDispatch
self.flash = flash_hash.dup
end
- if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
session.key?("flash") && session["flash"].nil?
session.delete("flash")
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 8bae5bfeff..53d5a4918c 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -157,13 +157,13 @@ module ActionDispatch
def ips_from(header) # :doc:
return [] unless header
- # Split the comma-separated list into an array of strings
+ # Split the comma-separated list into an array of strings.
ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
begin
- # Only return IPs that are valid according to the IPAddr#new method
+ # Only return IPs that are valid according to the IPAddr#new method.
range = IPAddr.new(ip).to_range
- # we want to make sure nobody is sneaking a netmask in
+ # We want to make sure nobody is sneaking a netmask in.
range.begin == range.end
rescue ArgumentError
nil
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index d9f018c8ac..21ccf5a097 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -53,7 +53,7 @@ module ActionDispatch
rescue ArgumentError => argument_error
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
begin
- # Note that the regexp does not allow $1 to end with a ':'
+ # Note that the regexp does not allow $1 to end with a ':'.
$1.constantize
rescue LoadError, NameError
raise ActionDispatch::Session::SessionRestoreError
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 90f26a1c33..5a99714ec2 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -8,7 +8,7 @@ module ActionDispatch
# The exceptions app should be passed as parameter on initialization
# of ShowExceptions. Every time there is an exception, ShowExceptions will
# store the exception in env["action_dispatch.exception"], rewrite the
- # PATH_INFO to the exception status code and call the rack app.
+ # PATH_INFO to the exception status code and call the Rack app.
#
# If the application returns a "X-Cascade" pass response, this middleware
# will send an empty response as result with the correct status code.
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 429ea7057c..2d21ae63f5 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -60,7 +60,7 @@
<%= link_to "Path", "#", 'data-route-helper' => '_path',
title: "Returns a relative path (without the http or domain)" %> /
<%= link_to "Url", "#", 'data-route-helper' => '_url',
- title: "Returns an absolute url (with the http and domain)" %>
+ title: "Returns an absolute URL (with the http and domain)" %>
</th>
<th><%# HTTP Verb %>
</th>
@@ -93,7 +93,7 @@
}
}
- // get JSON from url and invoke callback with result
+ // get JSON from URL and invoke callback with result
function getJSON(url, success) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index a2a80f39fc..74ba6466cf 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -7,10 +7,10 @@ module ActionDispatch
ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc:
ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
- # Singleton object used to determine if an optional param wasn't specified
+ # Singleton object used to determine if an optional param wasn't specified.
Unspecified = Object.new
- # Creates a session hash, merging the properties of the previous session if any
+ # Creates a session hash, merging the properties of the previous session if any.
def self.create(store, req, default_options)
session_was = find req
session = Request::Session.new(store, req)
@@ -63,7 +63,7 @@ module ActionDispatch
@req = req
@delegate = {}
@loaded = false
- @exists = nil # we haven't checked yet
+ @exists = nil # We haven't checked yet.
end
def id
@@ -79,7 +79,7 @@ module ActionDispatch
options = self.options || {}
@by.send(:delete_session, @req, options.id(@req), options)
- # Load the new sid to be written with the response
+ # Load the new sid to be written with the response.
@loaded = false
load_for_write!
end
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 01bc871e5f..3615e4b1d8 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -40,7 +40,6 @@ module ActionDispatch
class ParamEncoder # :nodoc:
# Convert nested Hash to HashWithIndifferentAccess.
- #
def self.normalize_encode_params(params)
case params
when Array
@@ -63,7 +62,7 @@ module ActionDispatch
end
end
- # Remove nils from the params hash
+ # Remove nils from the params hash.
class NoNilParamEncoder < ParamEncoder # :nodoc:
def self.handle_array(params)
list = super
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index c554ce98bc..60d4789a63 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -120,7 +120,7 @@ module ActionDispatch
# controller :blog do
# get 'blog/show' => :list
# get 'blog/delete' => :delete
- # get 'blog/edit' => :edit
+ # get 'blog/edit' => :edit
# end
#
# # provides named routes for show, delete, and edit
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index b91ffb8419..9aa4b92df2 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -196,7 +196,7 @@ module ActionDispatch
@buffer << @view.render(partial: "routes/route", collection: routes)
end
- # the header is part of the HTML page, so we don't construct it here.
+ # The header is part of the HTML page, so we don't construct it here.
def header(routes)
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 10d733e477..74904e3d45 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -17,9 +17,9 @@ module ActionDispatch
CALL = ->(app, req) { app.call req.env }
def initialize(app, constraints, strategy)
- # Unwrap Constraints objects. I don't actually think it's possible
+ # Unwrap Constraints objects. I don't actually think it's possible
# to pass a Constraints object to this constructor, but there were
- # multiple places that kept testing children of this object. I
+ # multiple places that kept testing children of this object. I
# *think* they were just being defensive, but I have no idea.
if app.is_a?(self.class)
constraints += app.constraints
@@ -54,6 +54,7 @@ module ActionDispatch
class Mapping #:nodoc:
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
attr_reader :requirements, :defaults
attr_reader :to, :default_controller, :default_action
@@ -93,7 +94,7 @@ module ActionDispatch
end
def self.optional_format?(path, format)
- format != false && !path.include?(":format") && !path.end_with?("/")
+ format != false && path !~ OPTIONAL_FORMAT_REGEX
end
def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
@@ -218,7 +219,7 @@ module ActionDispatch
private
def add_wildcard_options(options, formatted, path_ast)
# Add a constraint for wildcard route to make it non-greedy and match the
- # optional format part of the route by default
+ # optional format part of the route by default.
if formatted != false
path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
hash[node.name.to_sym] ||= /.+?/
@@ -396,7 +397,7 @@ module ActionDispatch
end
module Base
- # Matches a url pattern to one or more routes.
+ # Matches a URL pattern to one or more routes.
#
# You should not use the +match+ method in your router
# without specifying an HTTP method.
@@ -406,7 +407,7 @@ module ActionDispatch
# # sets :controller, :action and :id in params
# match ':controller/:action/:id', via: [:get, :post]
#
- # Note that +:controller+, +:action+ and +:id+ are interpreted as url
+ # Note that +:controller+, +:action+ and +:id+ are interpreted as URL
# query parameters and thus available through +params+ in an action.
#
# If you want to expose your action to GET, use +get+ in the router:
@@ -455,7 +456,7 @@ module ActionDispatch
#
# === Options
#
- # Any options not seen here are passed on as params with the url.
+ # Any options not seen here are passed on as params with the URL.
#
# [:controller]
# The route's controller.
@@ -660,7 +661,7 @@ module ActionDispatch
else
prefix_options = options.slice(*_route.segment_keys)
prefix_options[:relative_url_root] = "".freeze
- # we must actually delete prefix segment keys to avoid passing them to next url_for
+ # We must actually delete prefix segment keys to avoid passing them to next url_for.
_route.segment_keys.each { |k| options.delete(k) }
_routes.url_helpers.send("#{name}_path", prefix_options)
end
@@ -1238,7 +1239,7 @@ module ActionDispatch
#
# resource :profile
#
- # creates six different routes in your application, all mapping to
+ # This creates six different routes in your application, all mapping to
# the +Profiles+ controller (note that the controller is named after
# the plural):
#
@@ -1323,14 +1324,14 @@ module ActionDispatch
#
# resources :posts, path_names: { new: "brand_new" }
#
- # The above example will now change /posts/new to /posts/brand_new
+ # The above example will now change /posts/new to /posts/brand_new.
#
# [:path]
# Allows you to change the path prefix for the resource.
#
# resources :posts, path: 'postings'
#
- # The resource and all segments will now route to /postings instead of /posts
+ # The resource and all segments will now route to /postings instead of /posts.
#
# [:only]
# Only generate routes for the given actions.
@@ -1525,7 +1526,7 @@ module ActionDispatch
end
end
- # See ActionDispatch::Routing::Mapper::Scoping#namespace
+ # See ActionDispatch::Routing::Mapper::Scoping#namespace.
def namespace(path, options = {})
if resource_scope?
nested { super }
@@ -1545,7 +1546,7 @@ module ActionDispatch
!parent_resource.singleton? && @scope[:shallow]
end
- # Matches a url pattern to one or more routes.
+ # Matches a URL pattern to one or more routes.
# For more information, see match[rdoc-ref:Base#match].
#
# match 'path' => 'controller#action', via: patch
@@ -1904,7 +1905,7 @@ module ActionDispatch
ast = Journey::Parser.parse path
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
- @set.add_route(mapping, ast, as, anchor)
+ @set.add_route(mapping, as)
end
def match_root_route(options)
@@ -2003,7 +2004,7 @@ module ActionDispatch
# concerns :commentable
# end
#
- # concerns also work in any routes helper that you want to use:
+ # Concerns also work in any routes helper that you want to use:
#
# namespace :posts do
# concerns :commentable
@@ -2038,33 +2039,33 @@ module ActionDispatch
# end
#
# The return value from the block passed to `direct` must be a valid set of
- # arguments for `url_for` which will actually build the url string. This can
+ # arguments for `url_for` which will actually build the URL string. This can
# be one of the following:
#
- # * A string, which is treated as a generated url
+ # * A string, which is treated as a generated URL
# * A hash, e.g. { controller: "pages", action: "index" }
# * An array, which is passed to `polymorphic_url`
# * An Active Model instance
# * An Active Model class
#
- # NOTE: Other url helpers can be called in the block but be careful not to invoke
- # your custom url helper again otherwise it will result in a stack overflow error
+ # NOTE: Other URL helpers can be called in the block but be careful not to invoke
+ # your custom URL helper again otherwise it will result in a stack overflow error.
#
# You can also specify default options that will be passed through to
- # your url helper definition, e.g:
+ # your URL helper definition, e.g:
#
# direct :browse, page: 1, size: 10 do |options|
- # [ :products, options.merge(params.permit(:page, :size)) ]
+ # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
# end
#
# In this instance the `params` object comes from the context in which the the
- # block is executed, e.g. generating a url inside a controller action or a view.
+ # block is executed, e.g. generating a URL inside a controller action or a view.
# If the block is executed where there isn't a params object such as this:
#
# Rails.application.routes.url_helpers.browse_path
#
# then it will raise a `NameError`. Because of this you need to be aware of the
- # context in which you will use your custom url helper when defining it.
+ # context in which you will use your custom URL helper when defining it.
#
# NOTE: The `direct` method can't be used inside of a scope block such as
# `namespace` or `scope` and will raise an error if it detects that it is.
@@ -2076,7 +2077,7 @@ module ActionDispatch
@set.add_url_helper(name, options, &block)
end
- # Define custom polymorphic mappings of models to urls. This alters the
+ # Define custom polymorphic mappings of models to URLs. This alters the
# behavior of `polymorphic_url` and consequently the behavior of
# `link_to` and `form_for` when passed a model instance, e.g:
#
@@ -2089,7 +2090,7 @@ module ActionDispatch
# This will now generate "/basket" when a `Basket` instance is passed to
# `link_to` or `form_for` instead of the standard "/baskets/:id".
#
- # NOTE: This custom behavior only applies to simple polymorphic urls where
+ # NOTE: This custom behavior only applies to simple polymorphic URLs where
# a single model instance is passed and not more complicated forms, e.g:
#
# # config/routes.rb
@@ -2105,7 +2106,7 @@ module ActionDispatch
# link_to "Profile", [:admin, @current_user]
#
# The first `link_to` will generate "/profile" but the second will generate
- # the standard polymorphic url of "/admin/users/1".
+ # the standard polymorphic URL of "/admin/users/1".
#
# You can pass options to a polymorphic mapping - the arity for the block
# needs to be two as the instance is passed as the first argument, e.g:
@@ -2114,9 +2115,9 @@ module ActionDispatch
# [:basket, options]
# end
#
- # This generates the url "/basket#items" because when the last item in an
+ # This generates the URL "/basket#items" because when the last item in an
# array passed to `polymorphic_url` is a hash then it's treated as options
- # to the url helper that gets called.
+ # to the URL helper that gets called.
#
# NOTE: The `resolve` method can't be used inside of a scope block such as
# `namespace` or `scope` and will raise an error if it detects that it is.
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 984ded1ff5..e89ea8b21d 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -40,7 +40,7 @@ module ActionDispatch
#
# Example usage:
#
- # edit_polymorphic_path(@post) # => "/posts/1/edit"
+ # edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf"
#
# == Usage with mounted engines
@@ -104,7 +104,7 @@ module ActionDispatch
end
if mapping = polymorphic_mapping(record_or_hash_or_array)
- return mapping.call(self, [record_or_hash_or_array, options])
+ return mapping.call(self, [record_or_hash_or_array, options], false)
end
opts = options.dup
@@ -128,7 +128,7 @@ module ActionDispatch
end
if mapping = polymorphic_mapping(record_or_hash_or_array)
- return mapping.call(self, [record_or_hash_or_array, options], only_path: true)
+ return mapping.call(self, [record_or_hash_or_array, options], true)
end
opts = options.dup
@@ -273,7 +273,7 @@ module ActionDispatch
def handle_model_call(target, record)
if mapping = polymorphic_mapping(target, record)
- mapping.call(target, [record], only_path: suffix == "path")
+ mapping.call(target, [record], suffix == "path")
else
method, args = handle_model(record)
target.send(method, *args)
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index dabc045007..3bcb341758 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -36,6 +36,8 @@ module ActionDispatch
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
+ req.commit_flash
+
body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>)
headers = {
@@ -144,7 +146,7 @@ module ActionDispatch
#
# get 'docs/:article', to: redirect('/wiki/%{article}')
#
- # Note that if you return a path without a leading slash then the url is prefixed with the
+ # Note that if you return a path without a leading slash then the URL is prefixed with the
# current SCRIPT_NAME environment variable. This is typically '/' but may be different in
# a mounted engine or where the application is deployed to a subdirectory of a website.
#
@@ -163,7 +165,7 @@ module ActionDispatch
# Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
# the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
- # The options version of redirect allows you to supply only the parts of the url which need
+ # The options version of redirect allows you to supply only the parts of the URL which need
# to change, it also supports interpolation of the path similar to the first example.
#
# get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2672cd24ed..129e90037e 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -164,13 +164,13 @@ module ActionDispatch
@path_helpers_module.module_eval do
define_method(:"#{name}_path") do |*args|
- helper.call(self, args, only_path: true)
+ helper.call(self, args, true)
end
end
@url_helpers_module.module_eval do
define_method(:"#{name}_url") do |*args|
- helper.call(self, args)
+ helper.call(self, args, false)
end
end
end
@@ -295,7 +295,7 @@ module ActionDispatch
end
private
- # Create a url helper allowing ordered parameters to be associated
+ # Create a URL helper allowing ordered parameters to be associated
# with corresponding dynamic segments, so you can do:
#
# foo_url(bar, baz, bang)
@@ -509,6 +509,14 @@ module ActionDispatch
@_proxy.url_for(options)
end
+ def full_url_for(options)
+ @_proxy.full_url_for(options)
+ end
+
+ def route_for(name, *args)
+ @_proxy.route_for(name, *args)
+ end
+
def optimize_routes_generation?
@_proxy.optimize_routes_generation?
end
@@ -565,7 +573,7 @@ module ActionDispatch
routes.empty?
end
- def add_route(mapping, path_ast, name, anchor)
+ def add_route(mapping, name)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
if name && named_routes[name]
@@ -613,26 +621,14 @@ module ActionDispatch
@block = block
end
- def call(t, args, outer_options = {})
+ def call(t, args, only_path = false)
options = args.extract_options!
- url_options = eval_block(t, args, options)
-
- case url_options
- when String
- t.url_for(url_options)
- when Hash
- t.url_for(url_options.merge(outer_options))
- when ActionController::Parameters
- if url_options.permitted?
- t.url_for(url_options.to_h.merge(outer_options))
- else
- raise ArgumentError, "Generating a URL from non sanitized request parameters is insecure!"
- end
- when Array
- opts = url_options.extract_options!
- t.url_for(url_options.push(opts.merge(outer_options)))
+ url = t.full_url_for(eval_block(t, args, options))
+
+ if only_path
+ "/" + url.partition(%r{(?<!/)/(?!/)}).last
else
- t.url_for([url_options, outer_options])
+ url
end
end
@@ -860,8 +856,7 @@ module ActionDispatch
params[key] = URI.parser.unescape(value)
end
end
- old_params = req.path_parameters
- req.path_parameters = old_params.merge params
+ req.path_parameters = params
app = route.app
if app.matches?(req) && app.dispatcher?
begin
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 3e564f13d8..008216cc80 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -113,10 +113,10 @@ module ActionDispatch
default_url_options
end
- # Generate a url based on the options provided, default_url_options and the
+ # Generate a URL based on the options provided, default_url_options and the
# routes defined in routes.rb. The following options are supported:
#
- # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
+ # * <tt>:only_path</tt> - If true, the relative URL is returned. Defaults to +false+.
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
# If <tt>:only_path</tt> is false, this option must be
@@ -164,6 +164,10 @@ module ActionDispatch
# implicitly used by +url_for+ can always be overwritten like shown on the
# last +url_for+ calls.
def url_for(options = nil)
+ full_url_for(options)
+ end
+
+ def full_url_for(options = nil) # :nodoc:
case options
when nil
_routes.url_for(url_options.symbolize_keys)
@@ -192,6 +196,10 @@ module ActionDispatch
end
end
+ def route_for(name, *args) # :nodoc:
+ public_send(:"#{name}_url", *args)
+ end
+
protected
def optimize_routes_generation?
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index d6388d8fb7..98fdb36c91 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,8 +1,8 @@
require "capybara/dsl"
+require "capybara/minitest"
require "action_controller"
require "action_dispatch/system_testing/driver"
require "action_dispatch/system_testing/server"
-require "action_dispatch/system_testing/browser"
require "action_dispatch/system_testing/test_helpers/screenshot_helper"
require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
@@ -49,7 +49,7 @@ module ActionDispatch
# By default, <tt>ActionDispatch::SystemTestCase</tt> is driven by the
# Selenium driver, with the Chrome browser, and a browser size of 1400x1400.
#
- # Changing the driver configuration options are easy. Let's say you want to use
+ # Changing the driver configuration options is easy. Let's say you want to use
# the Firefox browser instead of Chrome. In your +application_system_test_case.rb+
# file add the following:
#
@@ -81,17 +81,27 @@ module ActionDispatch
# tests as long as you include the required gems and files.
class SystemTestCase < IntegrationTest
include Capybara::DSL
+ include Capybara::Minitest::Assertions
include SystemTesting::TestHelpers::SetupAndTeardown
include SystemTesting::TestHelpers::ScreenshotHelper
+ def initialize(*) # :nodoc:
+ super
+ self.class.driver.use
+ end
+
def self.start_application # :nodoc:
Capybara.app = Rack::Builder.new do
map "/" do
run Rails.application
end
end
+
+ SystemTesting::Server.new.run
end
+ class_attribute :driver, instance_accessor: false
+
# System Test configuration options
#
# The default settings are Selenium, using Chrome, with a screen size
@@ -104,22 +114,11 @@ module ActionDispatch
# driven_by :selenium, using: :firefox
#
# driven_by :selenium, screen_size: [800, 800]
- def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400])
- driver = if selenium?(driver)
- SystemTesting::Browser.new(using, screen_size)
- else
- SystemTesting::Driver.new(driver)
- end
-
- setup { driver.use }
- teardown { driver.reset }
-
- SystemTesting::Server.new.run
+ def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {})
+ self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
end
- def self.selenium?(driver) # :nodoc:
- driver == :selenium
- end
+ driven_by :selenium
end
SystemTestCase.start_application
diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb
deleted file mode 100644
index 14ea06459d..0000000000
--- a/actionpack/lib/action_dispatch/system_testing/browser.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require "action_dispatch/system_testing/driver"
-
-module ActionDispatch
- module SystemTesting
- class Browser < Driver # :nodoc:
- def initialize(name, screen_size)
- super(name)
- @name = name
- @screen_size = screen_size
- end
-
- def use
- register
- super
- end
-
- private
- def register
- Capybara.register_driver @name do |app|
- Capybara::Selenium::Driver.new(app, browser: @name).tap do |driver|
- driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
index 8decb54419..5cf17883f7 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -1,18 +1,34 @@
module ActionDispatch
module SystemTesting
class Driver # :nodoc:
- def initialize(name)
+ def initialize(name, **options)
@name = name
+ @browser = options[:using]
+ @screen_size = options[:screen_size]
+ @options = options[:options]
end
def use
- @current = Capybara.current_driver
- Capybara.current_driver = @name
+ register if selenium?
+ setup
end
- def reset
- Capybara.current_driver = @current
- end
+ private
+ def selenium?
+ @name == :selenium
+ end
+
+ def register
+ Capybara.register_driver @name do |app|
+ Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver|
+ driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
+ end
+ end
+ end
+
+ def setup
+ Capybara.current_driver = @name
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
index ddc961cf84..859d68e475 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
@@ -1,16 +1,27 @@
module ActionDispatch
module SystemTesting
module TestHelpers
- # Screenshot helper for system testing
+ # Screenshot helper for system testing.
module ScreenshotHelper
# Takes a screenshot of the current page in the browser.
#
# +take_screenshot+ can be used at any point in your system tests to take
# a screenshot of the current state. This can be useful for debugging or
# automating visual testing.
+ #
+ # The screenshot will be displayed in your console, if supported.
+ #
+ # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
+ # control the output. Possible values are:
+ # * [+inline+ (default)] display the screenshot in the terminal using the
+ # iTerm image protocol (http://iterm2.com/documentation-images.html).
+ # * [+simple+] only display the screenshot path.
+ # This is the default value if the +CI+ environment variables
+ # is defined.
+ # * [+artifact+] display the screenshot in the terminal, using the terminal
+ # artifact format (http://buildkite.github.io/terminal/inline-images/).
def take_screenshot
save_image
- puts "[Screenshot]: #{image_path}"
puts display_image
end
@@ -22,7 +33,7 @@ module ActionDispatch
# fails add +take_failed_screenshot+ to the teardown block before clearing
# sessions.
def take_failed_screenshot
- take_screenshot if failed?
+ take_screenshot if failed? && supports_screenshot?
end
private
@@ -38,14 +49,32 @@ module ActionDispatch
page.save_screenshot(Rails.root.join(image_path))
end
+ def output_type
+ # Environment variables have priority
+ output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] || ENV["CAPYBARA_INLINE_SCREENSHOT"]
+
+ # If running in a CI environment, default to simple
+ output_type ||= "simple" if ENV["CI"]
+
+ # Default
+ output_type ||= "inline"
+
+ output_type
+ end
+
def display_image
- if ENV["CAPYBARA_INLINE_SCREENSHOT"] == "artifact"
- "\e]1338;url=artifact://#{image_path}\a"
- else
+ message = "[Screenshot]: #{image_path}\n"
+
+ case output_type
+ when "artifact"
+ message << "\e]1338;url=artifact://#{image_path}\a\n"
+ when "inline"
name = inline_base64(File.basename(image_path))
image = inline_base64(File.read(image_path))
- "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a"
+ message << "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n"
end
+
+ message
end
def inline_base64(path)
@@ -55,6 +84,10 @@ module ActionDispatch
def failed?
!passed? && !skipped?
end
+
+ def supports_screenshot?
+ Capybara.current_driver != :rack_test
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
index 491559eedf..187ba2cc5f 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
@@ -10,9 +10,9 @@ module ActionDispatch
end
def after_teardown
- super
take_failed_screenshot
Capybara.reset_sessions!
+ super
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 817737341c..1baf979ac9 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -45,7 +45,7 @@ module ActionDispatch
# # Asserts that the redirection was to the named route login_url
# assert_redirected_to login_url
#
- # # Asserts that the redirection was to the url for @customer
+ # # Asserts that the redirection was to the URL for @customer
# assert_redirected_to @customer
#
# # Asserts that the redirection matches the regular expression
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 37c1ca02b6..8645df4370 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -18,8 +18,8 @@ module ActionDispatch
# assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
#
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
- # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the
- # extras argument, appending the query string on the path directly will not work. For example:
+ # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the extras
+ # argument because appending the query string on the path directly will not work. For example:
#
# # Asserts that a path of '/items/list/1?view=print' returns the correct options
# assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
@@ -132,8 +132,7 @@ module ActionDispatch
end
# A helper to make it easier to test different route configurations.
- # This method temporarily replaces @routes
- # with a new RouteSet instance.
+ # This method temporarily replaces @routes with a new RouteSet instance.
#
# The new instance is yielded to the passed block. Typically the block
# will create some routes using <tt>set.draw { match ... }</tt>:
@@ -186,7 +185,6 @@ module ActionDispatch
method = :get
end
- # Assume given controller
request = ActionController::TestRequest.create @controller.class
if path =~ %r{://}
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 5fa0b727ab..2e2db98ad6 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -192,11 +192,10 @@ module ActionDispatch
# HTTP methods in integration tests. +#process+ is only required when using a
# request method that doesn't have a method defined in the integration tests.
#
- # This method returns a Response object, which one can use to
- # inspect the details of the response. Furthermore, if this method was
- # called from an ActionDispatch::IntegrationTest object, then that
- # object's <tt>@response</tt> instance variable will point to the same
- # response object.
+ # This method returns the response status, after performing the request.
+ # Furthermore, if this method was called from an ActionDispatch::IntegrationTest object,
+ # then that object's <tt>@response</tt> instance variable will point to a Response object
+ # which one can use to inspect the details of the response.
#
# Example:
# process :get, '/author', params: { since: 201501011400 }
@@ -247,7 +246,7 @@ module ActionDispatch
wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
end
- # this modifies the passed request_env directly
+ # This modifies the passed request_env directly.
if wrapped_headers.present?
Http::Headers.from_hash(request_env).merge!(wrapped_headers)
end
@@ -258,7 +257,7 @@ module ActionDispatch
session = Rack::Test::Session.new(_mock_session)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
- # Make sure requested path is always a full uri
+ # Make sure requested path is always a full URI.
session.request(build_full_uri(path, request_env), request_env)
@request_count += 1
@@ -325,8 +324,8 @@ module ActionDispatch
def create_session(app)
klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
- # If the app is a Rails app, make url_helpers available on the session
- # This makes app.url_for and app.foo_path available in the console
+ # If the app is a Rails app, make url_helpers available on the session.
+ # This makes app.url_for and app.foo_path available in the console.
if app.respond_to?(:routes)
include app.routes.url_helpers
include app.routes.mounted_helpers
@@ -572,7 +571,7 @@ module ActionDispatch
# end
#
# assert_response :success
- # assert_equal({ id: Arcticle.last.id, title: "Ahoy!" }, response.parsed_body)
+ # assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body)
# end
# end
#
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 91b25ec155..ec949c869b 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -9,7 +9,7 @@ module ActionDispatch
"HTTP_USER_AGENT" => "Rails Testing",
)
- # Create a new test request with default `env` values
+ # Create a new test request with default `env` values.
def self.create(env = {})
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
env["rack.request.cookie_hash"] ||= {}.with_indifferent_access
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index d6a91a0569..fddc3033d5 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -6,9 +6,9 @@ module ActionPack
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 459b0d6c54..4185ce1a1f 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -439,3 +439,11 @@ class ActiveSupport::TestCase
skip message if defined?(JRUBY_VERSION)
end
end
+
+class DrivenByRackTest < ActionDispatch::SystemTestCase
+ driven_by :rack_test
+end
+
+class DrivenBySeleniumWithChrome < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :chrome
+end
diff --git a/actionpack/test/controller/api/with_helpers_test.rb b/actionpack/test/controller/api/with_helpers_test.rb
new file mode 100644
index 0000000000..06db949153
--- /dev/null
+++ b/actionpack/test/controller/api/with_helpers_test.rb
@@ -0,0 +1,42 @@
+require "abstract_unit"
+
+module ApiWithHelper
+ def my_helper
+ "helper"
+ end
+end
+
+class WithHelpersController < ActionController::API
+ include ActionController::Helpers
+ helper ApiWithHelper
+
+ def with_helpers
+ render plain: self.class.helpers.my_helper
+ end
+end
+
+class SubclassWithHelpersController < WithHelpersController
+ def with_helpers
+ render plain: self.class.helpers.my_helper
+ end
+end
+
+class WithHelpersTest < ActionController::TestCase
+ tests WithHelpersController
+
+ def test_with_helpers
+ get :with_helpers
+
+ assert_equal "helper", response.body
+ end
+end
+
+class SubclassWithHelpersTest < ActionController::TestCase
+ tests WithHelpersController
+
+ def test_with_helpers
+ get :with_helpers
+
+ assert_equal "helper", response.body
+ end
+end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 42a5157010..4e969fac07 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -11,6 +11,12 @@ end
class EmptyController < ActionController::Base
end
+class SimpleController < ActionController::Base
+ def hello
+ self.response_body = "hello"
+ end
+end
+
class NonEmptyController < ActionController::Base
def public_action
head :ok
@@ -118,6 +124,27 @@ class ControllerInstanceTests < ActiveSupport::TestCase
controller = klass.new
assert_equal "examples", controller.controller_path
end
+
+ def test_response_has_default_headers
+ original_default_headers = ActionDispatch::Response.default_headers
+
+ ActionDispatch::Response.default_headers = {
+ "X-Frame-Options" => "DENY",
+ "X-Content-Type-Options" => "nosniff",
+ "X-XSS-Protection" => "1;"
+ }
+
+ response_headers = SimpleController.action("hello").call(
+ "REQUEST_METHOD" => "GET",
+ "rack.input" => -> {}
+ )[1]
+
+ assert response_headers.key?("X-Frame-Options")
+ assert response_headers.key?("X-Content-Type-Options")
+ assert response_headers.key?("X-XSS-Protection")
+ ensure
+ ActionDispatch::Response.default_headers = original_default_headers
+ end
end
class PerformActionTest < ActionController::TestCase
diff --git a/actionpack/test/controller/metal_test.rb b/actionpack/test/controller/metal_test.rb
new file mode 100644
index 0000000000..e16452ed6f
--- /dev/null
+++ b/actionpack/test/controller/metal_test.rb
@@ -0,0 +1,30 @@
+require "abstract_unit"
+
+class MetalControllerInstanceTests < ActiveSupport::TestCase
+ class SimpleController < ActionController::Metal
+ def hello
+ self.response_body = "hello"
+ end
+ end
+
+ def test_response_has_default_headers
+ original_default_headers = ActionDispatch::Response.default_headers
+
+ ActionDispatch::Response.default_headers = {
+ "X-Frame-Options" => "DENY",
+ "X-Content-Type-Options" => "nosniff",
+ "X-XSS-Protection" => "1;"
+ }
+
+ response_headers = SimpleController.action("hello").call(
+ "REQUEST_METHOD" => "GET",
+ "rack.input" => -> {}
+ )[1]
+
+ refute response_headers.key?("X-Frame-Options")
+ refute response_headers.key?("X-Content-Type-Options")
+ refute response_headers.key?("X-XSS-Protection")
+ ensure
+ ActionDispatch::Response.default_headers = original_default_headers
+ end
+end
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 2893eb7b91..7725c25e22 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -53,6 +53,15 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
@params.each_pair { |key, value| assert_not(value.permitted?) if key == "person" }
end
+ test "empty? returns true when params contains no key/value pairs" do
+ params = ActionController::Parameters.new
+ assert params.empty?
+ end
+
+ test "empty? returns false when any params are present" do
+ refute @params.empty?
+ end
+
test "except retains permitted status" do
@params.permit!
assert @params.except(:person).permitted?
@@ -75,6 +84,45 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params[:person].fetch(:name).permitted?
end
+ test "has_key? returns true if the given key is present in the params" do
+ assert @params.has_key?(:person)
+ end
+
+ test "has_key? returns false if the given key is not present in the params" do
+ refute @params.has_key?(:address)
+ end
+
+ test "has_value? returns true if the given value is present in the params" do
+ params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
+ assert params.has_value?("Chicago")
+ end
+
+ test "has_value? returns false if the given value is not present in the params" do
+ params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
+ refute params.has_value?("New York")
+ end
+
+ test "include? returns true if the given key is present in the params" do
+ assert @params.include?(:person)
+ end
+
+ test "include? returns false if the given key is not present in the params" do
+ refute @params.include?(:address)
+ end
+
+ test "key? returns true if the given key is present in the params" do
+ assert @params.key?(:person)
+ end
+
+ test "key? returns false if the given key is not present in the params" do
+ refute @params.key?(:address)
+ end
+
+ test "keys returns an array of the keys of the params" do
+ assert_equal ["person"], @params.keys
+ assert_equal ["age", "name", "addresses"], @params[:person].keys
+ end
+
test "reject retains permitted status" do
assert_not @params.reject { |k| k == "person" }.permitted?
end
@@ -120,6 +168,21 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params.transform_values { |v| v }.permitted?
end
+ test "value? returns true if the given value is present in the params" do
+ params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
+ assert params.value?("Chicago")
+ end
+
+ test "value? returns false if the given value is not present in the params" do
+ params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
+ refute params.value?("New York")
+ end
+
+ test "values returns an array of the values of the params" do
+ params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
+ assert_equal ["Chicago", "Illinois"], params.values
+ end
+
test "values_at retains permitted status" do
@params.permit!
assert @params.values_at(:person).first.permitted?
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 8920914af1..3e067314d6 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -302,6 +302,47 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal "32", @params[:person][:age]
end
+ test "#reverse_merge with parameters" do
+ default_params = ActionController::Parameters.new(id: "1234", person: {}).permit!
+ merged_params = @params.reverse_merge(default_params)
+
+ assert_equal "1234", merged_params[:id]
+ refute_predicate merged_params[:person], :empty?
+ end
+
+ test "#with_defaults is an alias of reverse_merge" do
+ default_params = ActionController::Parameters.new(id: "1234", person: {}).permit!
+ merged_params = @params.with_defaults(default_params)
+
+ assert_equal "1234", merged_params[:id]
+ refute_predicate merged_params[:person], :empty?
+ end
+
+ test "not permitted is sticky beyond reverse_merge" do
+ refute_predicate @params.reverse_merge(a: "b"), :permitted?
+ end
+
+ test "permitted is sticky beyond reverse_merge" do
+ @params.permit!
+ assert_predicate @params.reverse_merge(a: "b"), :permitted?
+ end
+
+ test "#reverse_merge! with parameters" do
+ default_params = ActionController::Parameters.new(id: "1234", person: {}).permit!
+ @params.reverse_merge!(default_params)
+
+ assert_equal "1234", @params[:id]
+ refute_predicate @params[:person], :empty?
+ end
+
+ test "#with_defaults! is an alias of reverse_merge!" do
+ default_params = ActionController::Parameters.new(id: "1234", person: {}).permit!
+ @params.with_defaults!(default_params)
+
+ assert_equal "1234", @params[:id]
+ refute_predicate @params[:person], :empty?
+ end
+
test "modifying the parameters" do
@params[:person][:hometown] = "Chicago"
@params[:person][:family] = { brother: "Jonas" }
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index faa57c4559..2a41d57b26 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -32,6 +32,10 @@ class ParamsWrapperTest < ActionController::TestCase
def self.attribute_names
[]
end
+
+ def self.stored_attributes
+ { settings: [:color, :size] }
+ end
end
class Person
@@ -62,6 +66,17 @@ class ParamsWrapperTest < ActionController::TestCase
end
end
+ def test_store_accessors_wrapped
+ assert_called(User, :attribute_names, times: 2, returns: ["username"]) do
+ with_default_wrapper_options do
+ @request.env["CONTENT_TYPE"] = "application/json"
+ post :parse, params: { "username" => "sikachu", "color" => "blue", "size" => "large" }
+ assert_parameters("username" => "sikachu", "color" => "blue", "size" => "large",
+ "user" => { "username" => "sikachu", "color" => "blue", "size" => "large" })
+ end
+ end
+ end
+
def test_specify_wrapper_name
with_default_wrapper_options do
UsersController.wrap_parameters :person
diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
index 866600b935..052c974d68 100644
--- a/actionpack/test/controller/renderer_test.rb
+++ b/actionpack/test/controller/renderer_test.rb
@@ -19,6 +19,16 @@ class RendererTest < ActiveSupport::TestCase
assert_equal controller, renderer.controller
end
+ test "creating with new defaults" do
+ renderer = ApplicationController.renderer
+
+ new_defaults = { https: true }
+ new_renderer = renderer.with_defaults(new_defaults).new
+ content = new_renderer.render(inline: "<%= request.ssl? %>")
+
+ assert_equal "true", content
+ end
+
test "rendering with a class renderer" do
renderer = ApplicationController.renderer
content = renderer.render template: "ruby_template"
@@ -103,6 +113,20 @@ class RendererTest < ActiveSupport::TestCase
assert_equal "true", content
end
+ test "return valid asset url with defaults" do
+ renderer = ApplicationController.renderer
+ content = renderer.render inline: "<%= asset_url 'asset.jpg' %>"
+
+ assert_equal "http://example.org/asset.jpg", content
+ end
+
+ test "return valid asset url when https is true" do
+ renderer = ApplicationController.renderer.new https: true
+ content = renderer.render inline: "<%= asset_url 'asset.jpg' %>"
+
+ assert_equal "https://example.org/asset.jpg", content
+ end
+
private
def render
@render ||= ApplicationController.renderer.method(:render)
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index d645ddfdbe..521d93f02e 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -35,6 +35,22 @@ module RequestForgeryProtectionActions
render inline: "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>"
end
+ def form_with_remote
+ render inline: "<%= form_with(scope: :some_resource) {} %>"
+ end
+
+ def form_with_remote_with_token
+ render inline: "<%= form_with(scope: :some_resource, authenticity_token: true) {} %>"
+ end
+
+ def form_with_local_with_token
+ render inline: "<%= form_with(scope: :some_resource, local: true, authenticity_token: true) {} %>"
+ end
+
+ def form_with_remote_with_external_token
+ render inline: "<%= form_with(scope: :some_resource, authenticity_token: 'external_token') {} %>"
+ end
+
def same_origin_js
render js: "foo();"
end
@@ -235,6 +251,80 @@ module RequestForgeryProtectionTests
end
end
+ def test_should_render_form_with_with_token_tag_if_remote
+ assert_not_blocked do
+ get :form_with_remote
+ end
+ assert_match(/authenticity_token/, response.body)
+ end
+
+ def test_should_render_form_with_without_token_tag_if_remote_and_embedding_token_is_off
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = false
+ assert_not_blocked do
+ get :form_with_remote
+ end
+ assert_no_match(/authenticity_token/, response.body)
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+ assert_not_blocked do
+ get :form_with_remote_with_external_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token"
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_external_authenticity_token_requested
+ assert_not_blocked do
+ get :form_with_remote_with_external_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token"
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_authenticity_token_requested
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ get :form_with_remote_with_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_with_authenticity_token_requested
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ get :form_with_local_with_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_embedding_token_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ get :form_with_remote
+ end
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
def test_should_allow_get
assert_not_blocked { get :index }
end
@@ -347,6 +437,10 @@ module RequestForgeryProtectionTests
end
def test_should_block_post_with_origin_checking_and_wrong_origin
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+
forgery_protection_origin_check do
session[:_csrf_token] = @token
@controller.stub :form_authenticity_token, @token do
@@ -356,6 +450,13 @@ module RequestForgeryProtectionTests
end
end
end
+
+ assert_match(
+ "HTTP Origin header (http://bad.host) didn't match request.base_url (http://test.host)",
+ logger.logged(:warn).last
+ )
+ ensure
+ ActionController::Base.logger = old_logger
end
def test_should_warn_on_missing_csrf_token
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 891ce0e905..3a4307b64b 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -679,6 +679,11 @@ XML
assert_equal "baz", @request.filtered_parameters[:foo]
end
+ def test_path_is_kept_after_the_request
+ get :test_params, params: { id: "foo" }
+ assert_equal "/test_case_test/test/test_params/foo", @request.path
+ end
+
def test_path_params_reset_between_request
get :test_params, params: { id: "foo" }
assert_equal "foo", @request.path_parameters[:id]
@@ -728,20 +733,6 @@ XML
assert_equal "text/html", @response.body
end
- def test_request_path_info_and_format_reset
- get :test_format, format: "json"
- assert_equal "application/json", @response.body
-
- get :test_uri, format: "json"
- assert_equal "/test_case_test/test/test_uri.json", @response.body
-
- get :test_format
- assert_equal "text/html", @response.body
-
- get :test_uri
- assert_equal "/test_case_test/test/test_uri", @response.body
- end
-
def test_request_format_kwarg_overrides_params
get :test_format, format: "json", params: { format: "html" }
assert_equal "application/json", @response.body
diff --git a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
index f85b989892..cb5ca5888b 100644
--- a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
+++ b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
@@ -4,18 +4,23 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
class Linkable
attr_reader :id
+ def self.name
+ super.demodulize
+ end
+
def initialize(id)
@id = id
end
def linkable_type
- self.class.name.demodulize.underscore
+ self.class.name.underscore
end
end
class Category < Linkable; end
class Collection < Linkable; end
class Product < Linkable; end
+ class Manufacturer < Linkable; end
class Model
extend ActiveModel::Naming
@@ -79,7 +84,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
get "/media/:id", to: "media#show", as: :media
get "/pages/:id", to: "pages#show", as: :page
- resources :categories, :collections, :products
+ resources :categories, :collections, :products, :manufacturers
namespace :admin do
get "/dashboard", to: "dashboard#index"
@@ -89,6 +94,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
direct("string") { "http://www.rubyonrails.org" }
direct(:helper) { basket_url }
direct(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] }
+ direct(:nested) { |linkable| route_for(:linkable, linkable) }
direct(:params) { |params| params }
direct(:symbol) { :basket }
direct(:hash) { { controller: "basket", action: "show" } }
@@ -102,6 +108,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
resolve("Article") { |article| [:post, { id: article.id }] }
resolve("Basket") { |basket| [:basket] }
+ resolve("Manufacturer") { |manufacturer| route_for(:linkable, manufacturer) }
resolve("User", anchor: "details") { |user, options| [:profile, options] }
resolve("Video") { |video| [:media, { id: video.id }] }
resolve(%w[Page CategoryPage ProductPage]) { |page| [:page, { id: page.id }] }
@@ -119,6 +126,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
@category = Category.new("1")
@collection = Collection.new("2")
@product = Product.new("3")
+ @manufacturer = Manufacturer.new("apple")
@basket = Basket.new
@user = User.new
@video = Video.new("4")
@@ -136,14 +144,14 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
end
def test_direct_paths
- assert_equal "http://www.rubyonrails.org", website_path
- assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_path
+ assert_equal "/", website_path
+ assert_equal "/", Routes.url_helpers.website_path
- assert_equal "http://www.rubyonrails.org", string_path
- assert_equal "http://www.rubyonrails.org", Routes.url_helpers.string_path
+ assert_equal "/", string_path
+ assert_equal "/", Routes.url_helpers.string_path
- assert_equal "http://www.example.com/basket", helper_url
- assert_equal "http://www.example.com/basket", Routes.url_helpers.helper_url
+ assert_equal "/basket", helper_path
+ assert_equal "/basket", Routes.url_helpers.helper_path
assert_equal "/categories/1", linkable_path(@category)
assert_equal "/categories/1", Routes.url_helpers.linkable_path(@category)
@@ -152,6 +160,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
assert_equal "/products/3", linkable_path(@product)
assert_equal "/products/3", Routes.url_helpers.linkable_path(@product)
+ assert_equal "/categories/1", nested_path(@category)
+ assert_equal "/categories/1", Routes.url_helpers.nested_path(@category)
+
assert_equal "/", params_path(@safe_params)
assert_equal "/", Routes.url_helpers.params_path(@safe_params)
assert_raises(ArgumentError) { params_path(@unsafe_params) }
@@ -192,6 +203,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
assert_equal "http://www.example.com/products/3", linkable_url(@product)
assert_equal "http://www.example.com/products/3", Routes.url_helpers.linkable_url(@product)
+ assert_equal "http://www.example.com/categories/1", nested_url(@category)
+ assert_equal "http://www.example.com/categories/1", Routes.url_helpers.nested_url(@category)
+
assert_equal "http://www.example.com/", params_url(@safe_params)
assert_equal "http://www.example.com/", Routes.url_helpers.params_url(@safe_params)
assert_raises(ArgumentError) { params_url(@unsafe_params) }
@@ -244,6 +258,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
assert_equal "/pages/8", polymorphic_path(@product_page)
assert_equal "/pages/8", Routes.url_helpers.polymorphic_path(@product_page)
assert_equal "/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @product_page)
+
+ assert_equal "/manufacturers/apple", polymorphic_path(@manufacturer)
+ assert_equal "/manufacturers/apple", Routes.url_helpers.polymorphic_path(@manufacturer)
end
def test_resolve_urls
@@ -277,6 +294,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
assert_equal "http://www.example.com/pages/8", polymorphic_url(@product_page)
assert_equal "http://www.example.com/pages/8", Routes.url_helpers.polymorphic_url(@product_page)
assert_equal "http://www.example.com/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @product_page)
+
+ assert_equal "http://www.example.com/manufacturers/apple", polymorphic_url(@manufacturer)
+ assert_equal "http://www.example.com/manufacturers/apple", Routes.url_helpers.polymorphic_url(@manufacturer)
end
def test_defining_direct_inside_a_scope_raises_runtime_error
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 53758a4fbc..fdc47743fa 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3706,6 +3706,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal "/bar", bar_root_path
end
+ def test_nested_routes_under_format_resource
+ draw do
+ resources :formats do
+ resources :items
+ end
+ end
+
+ get "/formats/1/items.json"
+ assert_equal 200, @response.status
+ assert_equal "items#index", @response.body
+ assert_equal "/formats/1/items.json", format_items_path(1, :json)
+
+ get "/formats/1/items/2.json"
+ assert_equal 200, @response.status
+ assert_equal "items#show", @response.body
+ assert_equal "/formats/1/items/2.json", format_item_path(1, 2, :json)
+ end
+
private
def draw(&block)
@@ -4913,3 +4931,113 @@ class TestInternalRoutingParams < ActionDispatch::IntegrationTest
)
end
end
+
+class FlashRedirectTest < ActionDispatch::IntegrationTest
+ SessionKey = "_myapp_session"
+ Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
+
+ class KeyGeneratorMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["action_dispatch.key_generator"] ||= Generator
+ @app.call(env)
+ end
+ end
+
+ class FooController < ActionController::Base
+ def bar
+ render plain: (flash[:foo] || "foo")
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ get "/foo", to: redirect { |params, req| req.flash[:foo] = "bar"; "/bar" }
+ get "/bar", to: "flash_redirect_test/foo#bar"
+ end
+
+ APP = build_app Routes do |middleware|
+ middleware.use KeyGeneratorMiddleware
+ middleware.use ActionDispatch::Session::CookieStore, key: SessionKey
+ middleware.use ActionDispatch::Flash
+ middleware.delete ActionDispatch::ShowExceptions
+ end
+
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def test_block_redirect_commits_flash
+ get "/foo", env: { "action_dispatch.key_generator" => Generator }
+ assert_response :redirect
+
+ follow_redirect!
+ assert_equal "bar", response.body
+ end
+end
+
+class TestRecognizePath < ActionDispatch::IntegrationTest
+ class PageConstraint
+ attr_reader :key, :pattern
+
+ def initialize(key, pattern)
+ @key = key
+ @pattern = pattern
+ end
+
+ def matches?(request)
+ request.path_parameters[key] =~ pattern
+ end
+ end
+
+ stub_controllers do |routes|
+ Routes = routes
+ routes.draw do
+ get "/hash/:foo", to: "pages#show", constraints: { foo: /foo/ }
+ get "/hash/:bar", to: "pages#show", constraints: { bar: /bar/ }
+
+ get "/proc/:foo", to: "pages#show", constraints: proc { |r| r.path_parameters[:foo] =~ /foo/ }
+ get "/proc/:bar", to: "pages#show", constraints: proc { |r| r.path_parameters[:bar] =~ /bar/ }
+
+ get "/class/:foo", to: "pages#show", constraints: PageConstraint.new(:foo, /foo/)
+ get "/class/:bar", to: "pages#show", constraints: PageConstraint.new(:bar, /bar/)
+ end
+ end
+
+ APP = build_app Routes
+ def app
+ APP
+ end
+
+ def test_hash_constraints_dont_leak_between_routes
+ expected_params = { controller: "pages", action: "show", bar: "bar" }
+ actual_params = recognize_path("/hash/bar")
+
+ assert_equal expected_params, actual_params
+ end
+
+ def test_proc_constraints_dont_leak_between_routes
+ expected_params = { controller: "pages", action: "show", bar: "bar" }
+ actual_params = recognize_path("/proc/bar")
+
+ assert_equal expected_params, actual_params
+ end
+
+ def test_class_constraints_dont_leak_between_routes
+ expected_params = { controller: "pages", action: "show", bar: "bar" }
+ actual_params = recognize_path("/class/bar")
+
+ assert_equal expected_params, actual_params
+ end
+
+ private
+
+ def recognize_path(*args)
+ Routes.recognize_path(*args)
+ end
+end
diff --git a/actionpack/test/dispatch/system_testing/browser_test.rb b/actionpack/test/dispatch/system_testing/browser_test.rb
deleted file mode 100644
index b0ad309492..0000000000
--- a/actionpack/test/dispatch/system_testing/browser_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require "abstract_unit"
-require "action_dispatch/system_testing/browser"
-
-class BrowserTest < ActiveSupport::TestCase
- test "initializing the browser" do
- browser = ActionDispatch::SystemTesting::Browser.new(:chrome, [ 1400, 1400 ])
- assert_equal :chrome, browser.instance_variable_get(:@name)
- assert_equal [ 1400, 1400 ], browser.instance_variable_get(:@screen_size)
- end
-end
diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb
index f0ebdb38db..814e1d707b 100644
--- a/actionpack/test/dispatch/system_testing/driver_test.rb
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -6,4 +6,16 @@ class DriverTest < ActiveSupport::TestCase
driver = ActionDispatch::SystemTesting::Driver.new(:selenium)
assert_equal :selenium, driver.instance_variable_get(:@name)
end
+
+ test "initializing the driver with a browser" do
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :chrome, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" })
+ assert_equal :selenium, driver.instance_variable_get(:@name)
+ assert_equal :chrome, driver.instance_variable_get(:@browser)
+ assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
+ assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
+ end
+
+ test "selenium? returns false if driver is poltergeist" do
+ assert_not ActionDispatch::SystemTesting::Driver.new(:poltergeist).send(:selenium?)
+ end
end
diff --git a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
index 3b4ea96c4f..a83818fd80 100644
--- a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
+++ b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
@@ -1,15 +1,16 @@
require "abstract_unit"
require "action_dispatch/system_testing/test_helpers/screenshot_helper"
+require "capybara/dsl"
class ScreenshotHelperTest < ActiveSupport::TestCase
test "image path is saved in tmp directory" do
- new_test = ActionDispatch::SystemTestCase.new("x")
+ new_test = DrivenBySeleniumWithChrome.new("x")
assert_equal "tmp/screenshots/x.png", new_test.send(:image_path)
end
test "image path includes failures text if test did not pass" do
- new_test = ActionDispatch::SystemTestCase.new("x")
+ new_test = DrivenBySeleniumWithChrome.new("x")
new_test.stub :passed?, false do
assert_equal "tmp/screenshots/failures_x.png", new_test.send(:image_path)
@@ -17,7 +18,7 @@ class ScreenshotHelperTest < ActiveSupport::TestCase
end
test "image path does not include failures text if test skipped" do
- new_test = ActionDispatch::SystemTestCase.new("x")
+ new_test = DrivenBySeleniumWithChrome.new("x")
new_test.stub :passed?, false do
new_test.stub :skipped?, true do
@@ -26,3 +27,15 @@ class ScreenshotHelperTest < ActiveSupport::TestCase
end
end
end
+
+class RackTestScreenshotsTest < DrivenByRackTest
+ test "rack_test driver does not support screenshot" do
+ assert_not self.send(:supports_screenshot?)
+ end
+end
+
+class SeleniumScreenshotsTest < DrivenBySeleniumWithChrome
+ test "selenium driver supports screenshot" do
+ assert self.send(:supports_screenshot?)
+ end
+end
diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
index ff01d6739a..33d98f924f 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -1,12 +1,12 @@
require "abstract_unit"
-class DrivenByCaseTestTest < ActiveSupport::TestCase
- test "selenium? returns false if driver is poltergeist" do
- assert_not ActionDispatch::SystemTestCase.selenium?(:poltergeist)
+class SetDriverToRackTestTest < DrivenByRackTest
+ test "uses rack_test" do
+ assert_equal :rack_test, Capybara.current_driver
end
end
-class DrivenByRackTestTest < ActionDispatch::SystemTestCase
+class OverrideSeleniumSubclassToRackTestTest < DrivenBySeleniumWithChrome
driven_by :rack_test
test "uses rack_test" do
@@ -14,10 +14,8 @@ class DrivenByRackTestTest < ActionDispatch::SystemTestCase
end
end
-class DrivenBySeleniumWithChromeTest < ActionDispatch::SystemTestCase
- driven_by :selenium, using: :chrome
-
+class SetDriverToSeleniumTest < DrivenBySeleniumWithChrome
test "uses selenium" do
- assert_equal :chrome, Capybara.current_driver
+ assert_equal :selenium, Capybara.current_driver
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index f5d2c9f23b..c514e757c8 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,190 +1 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Change the ERB handler from Erubis to Erubi.
-
- Erubi is an Erubis fork that's svelte, simple, and currently maintained.
- Plus it supports `--enable-frozen-string-literal` in Ruby 2.3+.
-
- Compatibility: Drops support for `<%===` tags for debug output.
- These were an unused, undocumented side effect of the Erubis
- implementation.
-
- Deprecation: The Erubis handler will be removed in Rails 5.2, for the
- handful of folks using it directly.
-
- *Jeremy Evans*
-
-* Allow render locals to be assigned to instance variables in a view.
-
- Fixes #27480.
-
- *Andrew White*
-
-* Add `check_parameters` option to `current_page?` which makes it more strict.
-
- *Maksym Pugach*
-
-* Return correct object name in form helper method after `fields_for`.
-
- Fixes #26931.
-
- *Yuji Yaginuma*
-
-* Use `ActionView::Resolver.caching?` (`config.action_view.cache_template_loading`)
- to enable template recompilation.
-
- Before it was enabled by `consider_all_requests_local`, which caused
- recompilation in tests.
-
- *Max Melentiev*
-
-* Add `form_with` to unify `form_tag` and `form_for` usage.
-
- Used like `form_tag` (where just the open tag is output):
-
- ```erb
- <%= form_with scope: :post, url: super_special_posts_path %>
- ```
-
- Used like `form_for`:
-
- ```erb
- <%= form_with model: @post do |form| %>
- <%= form.text_field :title %>
- <% end %>
- ```
-
- *Kasper Timm Hansen*, *Marek Kirejczyk*
-
-* Add `fields` form helper method.
-
- ```erb
- <%= fields :comment, model: @comment do |fields| %>
- <%= fields.text_field :title %>
- <% end %>
- ```
-
- Can also be used within form helpers such as `form_with`.
-
- *Kasper Timm Hansen*
-
-* Removed deprecated `#original_exception` in `ActionView::Template::Error`.
-
- *Rafael Mendonça França*
-
-* Render now accepts any keys for locals, including reserved keywords.
-
- Only locals with valid variable names get set directly. Others
- will still be available in `local_assigns`.
-
- Example of render with reserved keywords:
-
- ```erb
- <%= render "example", class: "text-center", message: "Hello world!" %>
-
- <!-- _example.html.erb: -->
- <%= tag.div class: local_assigns[:class] do %>
- <p><%= message %></p>
- <% end %>
- ```
-
- *Peter Schilling*, *Matthew Draper*
-
-* Show cache hits and misses when rendering partials.
-
- Partials using the `cache` helper will show whether a render hit or missed
- the cache:
-
- ```
- Rendered messages/_message.html.erb in 1.2 ms [cache hit]
- Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss]
- ```
-
- This removes the need for the old fragment cache logging:
-
- ```
- Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms)
- Rendered messages/_message.html.erb in 1.2 ms [cache hit]
- Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms)
- Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss]
- ```
-
- Though that full output can be reenabled with
- `config.action_controller.enable_fragment_cache_logging = true`.
-
- *Stan Lo*
-
-* Changed partial rendering with a collection to allow collections which
- implement `to_a`.
-
- Extracting the collection option had an optimization to avoid unnecessary
- queries of ActiveRecord Relations by calling `#to_ary` on the given
- collection. Instances of `Enumerator` or `Enumerable` are valid
- collections, but they do not implement `#to_ary`. By changing this to
- `#to_a`, they will now be extracted and rendered as expected.
-
- *Steven Harman*
-
-* New syntax for tag helpers. Avoid positional parameters and support HTML5 by default.
- Example usage of tag helpers before:
-
- ```ruby
- tag(:br, nil, true)
- content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
-
- <%= content_tag :div, class: "strong" do -%>
- Hello world!
- <% end -%>
- ```
-
- Example usage of tag helpers after:
-
- ```ruby
- tag.br
- tag.div tag.p("Hello world!"), class: "strong"
-
- <%= tag.div class: "strong" do %>
- Hello world!
- <% end %>
- ```
-
- *Marek Kirejczyk*, *Kasper Timm Hansen*
-
-* Change `datetime_field` and `datetime_field_tag` to generate `datetime-local` fields.
-
- As a new specification of the HTML 5 the text field type `datetime` will no longer exist
- and it is recommended to use `datetime-local`.
- Ref: https://html.spec.whatwg.org/multipage/forms.html#local-date-and-time-state-(type=datetime-local)
-
- *Herminio Torres*
-
-* Raw template handler (which is also the default template handler in Rails 5) now outputs
- HTML-safe strings.
-
- In Rails 5 the default template handler was changed to the raw template handler. Because
- the ERB template handler escaped strings by default this broke some applications that
- expected plain JS or HTML files to be rendered unescaped. This fixes the issue caused
- by changing the default handler by changing the Raw template handler to output HTML-safe
- strings.
-
- *Eileen M. Uchitelle*
-
-* `select_tag`'s `include_blank` option for generation for blank option tag, now adds an empty space label,
- when the value as well as content for option tag are empty, so that we conform with html specification.
- Ref: https://www.w3.org/TR/html5/forms.html#the-option-element.
-
- Generation of option before:
-
- ```html
- <option value=""></option>
- ```
-
- Generation of option after:
-
- ```html
- <option value="" label=" "></option>
- ```
-
- *Vipul A M*
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 00ab92129d..de588ad25c 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -1,10 +1,13 @@
require "rake/testtask"
require "fileutils"
+require "open3"
+
+dir = File.dirname(__FILE__)
desc "Default Task"
task default: :test
-task package: "assets:compile"
+task package: %w( assets:compile assets:verify )
# Run the unit tests
@@ -84,8 +87,46 @@ namespace :assets do
desc "Compile Action View assets"
task :compile do
require "blade"
+ require "sprockets"
+ require "sprockets/export"
Blade.build
end
+
+ desc "Verify compiled Action View assets"
+ task :verify do
+ file = "lib/assets/compiled/rails-ujs.js"
+ pathname = Pathname.new("#{dir}/#{file}")
+
+ print "[verify] #{file} exists "
+ if pathname.exist?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{file} is a UMD module "
+ if pathname.read =~ /module\.exports.*define\.amd/m
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{dir} can be required as a module "
+ js = <<-JS
+ window = { Event: class {} }
+ class Element {}
+ require('#{dir}')
+ JS
+ stdout, stderr, status = Open3.capture3("node", "--print", js)
+ if status.success?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]\n#{stderr}"
+ fail
+ end
+ end
end
task :lines do
diff --git a/actionview/app/assets/javascripts/config.coffee b/actionview/app/assets/javascripts/config.coffee
deleted file mode 100644
index a93325e903..0000000000
--- a/actionview/app/assets/javascripts/config.coffee
+++ /dev/null
@@ -1,34 +0,0 @@
-#= export Rails
-
-@Rails =
- # Link elements bound by rails-ujs
- linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]'
-
- # Button elements bound by rails-ujs
- buttonClickSelector:
- selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])'
- exclude: 'form button'
-
- # Select elements bound by rails-ujs
- inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]'
-
- # Form elements bound by rails-ujs
- formSubmitSelector: 'form'
-
- # Form input elements bound by rails-ujs
- formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])'
-
- # Form input elements disabled during form submission
- formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled'
-
- # Form input elements re-enabled after form submission
- formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled'
-
- # Form file input elements
- fileInputSelector: 'input[name][type=file]:not([disabled])'
-
- # Link onClick disable selector with possible reenable after remote submission
- linkDisableSelector: 'a[data-disable-with], a[data-disable]'
-
- # Button onClick disable selector with possible reenable after remote submission
- buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/assets/javascripts/rails-ujs.coffee
index df889ce067..bd6e9bb881 100644
--- a/actionview/app/assets/javascripts/rails-ujs.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs.coffee
@@ -1,75 +1,39 @@
-#
-# Unobtrusive JavaScript
-# https://github.com/rails/rails-ujs
-#
-# Released under the MIT license
-#
-#= require ./config
-#= require_tree ./utils
-#= require_tree ./features
+#= require ./rails-ujs/BANNER
+#= export Rails
+#= require_self
+#= require_tree ./rails-ujs/utils
+#= require_tree ./rails-ujs/features
+#= require ./rails-ujs/start
-{
- fire, delegate
- getData, $
- refreshCSRFTokens, CSRFProtection
- enableElement, disableElement
- handleConfirm
- handleRemote, formSubmitButtonClick, handleMetaClick
- handleMethod
-} = Rails
+@Rails =
+ # Link elements bound by rails-ujs
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]'
-# For backward compatibility
-if jQuery? and not jQuery.rails
- jQuery.rails = Rails
- jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
- CSRFProtection(xhr) unless options.crossDomain
+ # Button elements bound by rails-ujs
+ buttonClickSelector:
+ selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])'
+ exclude: 'form button'
-Rails.start = ->
- # Cut down on the number of issues from people inadvertently including
- # rails-ujs twice by detecting and raising an error when it happens.
- throw new Error('rails-ujs has already been loaded!') if window._rails_loaded
+ # Select elements bound by rails-ujs
+ inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]'
- # This event works the same as the load event, except that it fires every
- # time the page is loaded.
- # See https://github.com/rails/jquery-ujs/issues/357
- # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
- window.addEventListener 'pageshow', ->
- $(Rails.formEnableSelector).forEach (el) ->
- enableElement(el) if getData(el, 'ujs:disabled')
- $(Rails.linkDisableSelector).forEach (el) ->
- enableElement(el) if getData(el, 'ujs:disabled')
+ # Form elements bound by rails-ujs
+ formSubmitSelector: 'form'
- delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement
- delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement
- delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
- delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
+ # Form input elements bound by rails-ujs
+ formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])'
- delegate document, Rails.linkClickSelector, 'click', handleConfirm
- delegate document, Rails.linkClickSelector, 'click', handleMetaClick
- delegate document, Rails.linkClickSelector, 'click', disableElement
- delegate document, Rails.linkClickSelector, 'click', handleRemote
- delegate document, Rails.linkClickSelector, 'click', handleMethod
+ # Form input elements disabled during form submission
+ formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled'
- delegate document, Rails.buttonClickSelector, 'click', handleConfirm
- delegate document, Rails.buttonClickSelector, 'click', disableElement
- delegate document, Rails.buttonClickSelector, 'click', handleRemote
+ # Form input elements re-enabled after form submission
+ formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled'
- delegate document, Rails.inputChangeSelector, 'change', handleConfirm
- delegate document, Rails.inputChangeSelector, 'change', handleRemote
+ # Form file input elements
+ fileInputSelector: 'input[name][type=file]:not([disabled])'
- delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
- delegate document, Rails.formSubmitSelector, 'submit', handleRemote
- # Normal mode submit
- # Slight timeout so that the submit button gets properly serialized
- delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13)
- delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
- delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
+ # Link onClick disable selector with possible reenable after remote submission
+ linkDisableSelector: 'a[data-disable-with], a[data-disable]'
- delegate document, Rails.formInputClickSelector, 'click', handleConfirm
- delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
-
- document.addEventListener('DOMContentLoaded', refreshCSRFTokens)
- window._rails_loaded = true
-
-if window.Rails is Rails and fire(document, 'rails:attachBindings')
- Rails.start()
+ # Button onClick disable selector with possible reenable after remote submission
+ buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
diff --git a/actionview/app/assets/javascripts/rails-ujs/BANNER.js b/actionview/app/assets/javascripts/rails-ujs/BANNER.js
new file mode 100644
index 0000000000..47ecd66003
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/BANNER.js
@@ -0,0 +1,5 @@
+/*
+Unobtrusive JavaScript
+https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts
+Released under the MIT license
+ */
diff --git a/actionview/app/assets/javascripts/features/confirm.coffee b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
index 72b5aaa218..72b5aaa218 100644
--- a/actionview/app/assets/javascripts/features/confirm.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
diff --git a/actionview/app/assets/javascripts/features/disable.coffee b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
index e8cce7da40..90aa3bdf0e 100644
--- a/actionview/app/assets/javascripts/features/disable.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
@@ -2,6 +2,10 @@
{ matches, getData, setData, stopEverything, formElements } = Rails
+Rails.handleDisabledElement = (e) ->
+ element = this
+ stopEverything(e) if element.disabled
+
# Unified function to enable an element (link, button and form)
Rails.enableElement = (e) ->
element = if e instanceof Event then e.target else e
diff --git a/actionview/app/assets/javascripts/features/method.coffee b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee
index d04d9414dd..d04d9414dd 100644
--- a/actionview/app/assets/javascripts/features/method.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee
diff --git a/actionview/app/assets/javascripts/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
index 852587042c..852587042c 100644
--- a/actionview/app/assets/javascripts/features/remote.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee
new file mode 100644
index 0000000000..5746a22287
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee
@@ -0,0 +1,70 @@
+{
+ fire, delegate
+ getData, $
+ refreshCSRFTokens, CSRFProtection
+ enableElement, disableElement, handleDisabledElement
+ handleConfirm
+ handleRemote, formSubmitButtonClick, handleMetaClick
+ handleMethod
+} = Rails
+
+# For backward compatibility
+if jQuery? and not jQuery.rails
+ jQuery.rails = Rails
+ jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
+ CSRFProtection(xhr) unless options.crossDomain
+
+Rails.start = ->
+ # Cut down on the number of issues from people inadvertently including
+ # rails-ujs twice by detecting and raising an error when it happens.
+ throw new Error('rails-ujs has already been loaded!') if window._rails_loaded
+
+ # This event works the same as the load event, except that it fires every
+ # time the page is loaded.
+ # See https://github.com/rails/jquery-ujs/issues/357
+ # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
+ window.addEventListener 'pageshow', ->
+ $(Rails.formEnableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+ $(Rails.linkDisableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+
+ delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
+
+ delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.linkClickSelector, 'click', handleConfirm
+ delegate document, Rails.linkClickSelector, 'click', handleMetaClick
+ delegate document, Rails.linkClickSelector, 'click', disableElement
+ delegate document, Rails.linkClickSelector, 'click', handleRemote
+ delegate document, Rails.linkClickSelector, 'click', handleMethod
+
+ delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.buttonClickSelector, 'click', handleConfirm
+ delegate document, Rails.buttonClickSelector, 'click', disableElement
+ delegate document, Rails.buttonClickSelector, 'click', handleRemote
+
+ delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement
+ delegate document, Rails.inputChangeSelector, 'change', handleConfirm
+ delegate document, Rails.inputChangeSelector, 'change', handleRemote
+
+ delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement
+ delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
+ delegate document, Rails.formSubmitSelector, 'submit', handleRemote
+ # Normal mode submit
+ # Slight timeout so that the submit button gets properly serialized
+ delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13)
+ delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
+ delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
+
+ delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.formInputClickSelector, 'click', handleConfirm
+ delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
+
+ document.addEventListener('DOMContentLoaded', refreshCSRFTokens)
+ window._rails_loaded = true
+
+if window.Rails is Rails and fire(document, 'rails:attachBindings')
+ Rails.start()
diff --git a/actionview/app/assets/javascripts/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
index 9af515beda..26df7b9a3f 100644
--- a/actionview/app/assets/javascripts/utils/ajax.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
@@ -29,6 +29,7 @@ Rails.ajax = (options) ->
fire(document, 'ajaxStop') # to be compatible with jQuery.ajax
prepareOptions = (options) ->
+ options.url = options.url or location.href
options.type = options.type.toUpperCase()
# append data to url if it's a GET request
if options.type is 'GET' and options.data
@@ -63,10 +64,10 @@ processResponse = (response, type) ->
if typeof response is 'string' and typeof type is 'string'
if type.match(/\bjson\b/)
try response = JSON.parse(response)
- else if type.match(/\bjavascript\b/)
+ else if type.match(/\b(?:java|ecma)script\b/)
script = document.createElement('script')
- script.innerHTML = response
- document.body.appendChild(script)
+ script.text = response
+ document.head.appendChild(script).parentNode.removeChild(script)
else if type.match(/\b(xml|html|svg)\b/)
parser = new DOMParser()
type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
diff --git a/actionview/app/assets/javascripts/utils/csrf.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee
index 4eb5ebb414..4eb5ebb414 100644
--- a/actionview/app/assets/javascripts/utils/csrf.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee
diff --git a/actionview/app/assets/javascripts/utils/dom.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
index 6bef618147..6bef618147 100644
--- a/actionview/app/assets/javascripts/utils/dom.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
diff --git a/actionview/app/assets/javascripts/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
index 8d3ff007ea..8d3ff007ea 100644
--- a/actionview/app/assets/javascripts/utils/event.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
diff --git a/actionview/app/assets/javascripts/utils/form.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee
index 5fa337b518..5fa337b518 100644
--- a/actionview/app/assets/javascripts/utils/form.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 0658d8601d..5ddf1ceb66 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -62,8 +62,10 @@ module ActionView
node
end
else
- logger.error " '#{name}' file doesn't exist, so no dependencies"
- logger.error " Couldn't find template for digesting: #{name}"
+ unless name.include?("#") # Dynamic template partial names can never be tracked
+ logger.error " Couldn't find template for digesting: #{name}"
+ end
+
seen[name] ||= Missing.new(name, logical_name, nil)
end
end
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 662a85f191..92e21d7a4f 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -6,9 +6,9 @@ module ActionView
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 26a625e4fe..3eafe0028e 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -201,9 +201,9 @@ module ActionView
# <%= f.submit %>
# <% end %>
#
- # This also works for the methods in FormOptionHelper and DateHelper that
+ # This also works for the methods in FormOptionsHelper and DateHelper that
# are designed to work with an object as base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === #form_for with a model object
#
@@ -416,13 +416,13 @@ module ActionView
#
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
#
- # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f|
+ # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
# ...
# <% end %>
#
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
#
- # <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
+ # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
# ...
# <% end %>
def form_for(record, options = {}, &block)
@@ -474,6 +474,8 @@ module ActionView
end
private :apply_form_for_options!
+ mattr_accessor(:form_with_generates_remote_forms) { true }
+
# Creates a form tag based on mixing URLs, scopes, or models.
#
# # Using just a URL:
@@ -632,9 +634,9 @@ module ActionView
# <%= form.submit %>
# <% end %>
#
- # Same goes for the methods in FormOptionHelper and DateHelper designed
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
# to work with an object as a base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === Setting the method
#
@@ -791,9 +793,9 @@ module ActionView
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
#
- # Note: This also works for the methods in FormOptionHelper and
+ # Note: This also works for the methods in FormOptionsHelper and
# DateHelper that are designed to work with an object as base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === Nested Attributes Examples
#
@@ -1033,9 +1035,9 @@ module ActionView
# <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
# <% end %>
#
- # Same goes for the methods in FormOptionHelper and DateHelper designed
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
# to work with an object as a base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
def fields(scope = nil, model: nil, **options, &block)
options[:allow_method_names_outside_object] = true
options[:skip_default_ids] = true
@@ -1503,7 +1505,7 @@ module ActionView
end
private
- def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false,
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
skip_enforcing_utf8: false, **options)
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
@@ -1517,12 +1519,14 @@ module ActionView
html_options[:"accept-charset"] = "UTF-8"
html_options[:"data-remote"] = true unless local
- if !local && !embed_authenticity_token_in_remote_forms &&
- html_options[:authenticity_token].blank?
- # The authenticity token is taken from the meta tag in this case
- html_options[:authenticity_token] = false
- elsif html_options[:authenticity_token] == true
- # Include the default authenticity_token, which is only generated when its set to nil,
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
+
+ if !local && html_options[:authenticity_token].blank?
+ html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
+ end
+
+ if html_options[:authenticity_token] == true
+ # Include the default authenticity_token, which is only generated when it's set to nil,
# but we needed the true value to override the default of no authenticity_token on data-remote.
html_options[:authenticity_token] = nil
end
@@ -1724,9 +1728,9 @@ module ActionView
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
#
- # Note: This also works for the methods in FormOptionHelper and
+ # Note: This also works for the methods in FormOptionsHelper and
# DateHelper that are designed to work with an object as base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === Nested Attributes Examples
#
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index ffc64e7118..9fc08b3837 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -18,7 +18,7 @@ module ActionView
include TextHelper
mattr_accessor :embed_authenticity_token_in_remote_forms
- self.embed_authenticity_token_in_remote_forms = false
+ self.embed_authenticity_token_in_remote_forms = nil
# Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
# ActionController::Base#url_for. The method for the form defaults to POST.
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 1e9b813d3d..0abd5bc5dc 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -13,6 +13,7 @@ module ActionView
# It also strips href/src attributes with unsafe protocols like
# <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
# ASCII, and hex character references to work around these protocol filters.
+ # All special characters will be escaped.
#
# The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
# Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
@@ -20,8 +21,7 @@ module ActionView
# Custom sanitization rules can also be provided.
#
# Please note that sanitizing user-provided text does not guarantee that the
- # resulting markup is valid or even well-formed. For example, the output may still
- # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>.
+ # resulting markup is valid or even well-formed.
#
# ==== Options
#
@@ -86,7 +86,7 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from +html+, including comments.
+ # Strips all HTML tags from +html+, including comments and special characters.
#
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
@@ -96,8 +96,11 @@ module ActionView
#
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
# # => Welcome to my website!
+ #
+ # strip_tags("> A quote from Smith & Wesson")
+ # # => &gt; A quote from Smith &amp; Wesson
def strip_tags(html)
- self.class.full_sanitizer.sanitize(html, encode_special_chars: false)
+ self.class.full_sanitizer.sanitize(html)
end
# Strips all link tags from +html+ leaving just the link text.
@@ -110,6 +113,9 @@ module ActionView
#
# strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
# # => Blog: Visit.
+ #
+ # strip_links('<<a href="https://example.org">malformed & link</a>')
+ # # => &lt;malformed &amp; link
def strip_links(html)
self.class.link_sanitizer.sanitize(html)
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index a306903c60..304db38060 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -622,7 +622,7 @@ module ActionView
def to_form_params(attribute, namespace = nil)
attribute = if attribute.respond_to?(:permitted?)
unless attribute.permitted?
- raise ArgumentError, "Attempting to generate a buttom from non-sanitized request parameters!" \
+ raise ArgumentError, "Attempting to generate a button from non-sanitized request parameters!" \
" Whitelist and sanitize passed parameters to be secure."
end
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index d344d98f4b..0939584786 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -5,7 +5,7 @@ module ActionView
# = Action View Railtie
class Railtie < Rails::Engine # :nodoc:
config.action_view = ActiveSupport::OrderedOptions.new
- config.action_view.embed_authenticity_token_in_remote_forms = false
+ config.action_view.embed_authenticity_token_in_remote_forms = nil
config.action_view.debug_missing_translation = true
config.eager_load_namespaces << ActionView
diff --git a/actionview/package.json b/actionview/package.json
index a1da13315e..85f4ddacbe 100644
--- a/actionview/package.json
+++ b/actionview/package.json
@@ -1,6 +1,6 @@
{
"name": "rails-ujs",
- "version": "5.1.0-beta1",
+ "version": "5.2.0-alpha",
"description": "Ruby on Rails unobtrusive scripting adapter",
"main": "lib/assets/compiled/rails-ujs.js",
"files": [
@@ -11,7 +11,7 @@
},
"scripts": {
"build": "bundle exec blade build",
- "test": "echo \"See the README: https://github.com/rails/rails-ujs#how-to-run-tests\" && exit 1",
+ "test": "echo \"See the README: https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts#how-to-run-tests\" && exit 1",
"lint": "coffeelint app/assets/javascripts && eslint test/public/test"
},
"repository": {
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index e99c769dc2..b2e0fb08c4 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -730,3 +730,49 @@ class PolymorphicPathRoutesTest < PolymorphicRoutesTest
assert_equal url.sub(/http:\/\/#{host}/, ""), url_for(args)
end
end
+
+class DirectRoutesTest < ActionView::TestCase
+ class Linkable
+ attr_reader :id
+
+ def self.name
+ super.demodulize
+ end
+
+ def initialize(id)
+ @id = id
+ end
+
+ def linkable_type
+ self.class.name.underscore
+ end
+ end
+
+ class Category < Linkable; end
+ class Collection < Linkable; end
+ class Product < Linkable; end
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ resources :categories, :collections, :products
+ direct(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] }
+ end
+
+ include Routes.url_helpers
+
+ def setup
+ @category = Category.new("1")
+ @collection = Collection.new("2")
+ @product = Product.new("3")
+ end
+
+ def test_direct_routes
+ assert_equal "/categories/1", linkable_path(@category)
+ assert_equal "/collections/2", linkable_path(@collection)
+ assert_equal "/products/3", linkable_path(@product)
+
+ assert_equal "http://test.host/categories/1", linkable_url(@category)
+ assert_equal "http://test.host/collections/2", linkable_url(@collection)
+ assert_equal "http://test.host/products/3", linkable_url(@product)
+ end
+end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index bfd3ecd6fd..d257147e1f 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -832,7 +832,7 @@ class DateHelperTest < ActionView::TestCase
def test_select_date_with_too_big_range_between_start_year_and_end_year
assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 2000, end_year: 20000, prefix: "date[first]", order: [:month, :day, :year]) }
- assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: Date.today.year - 100.years, end_year: 2000, prefix: "date[first]", order: [:month, :day, :year]) }
+ assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 100, end_year: 2000, prefix: "date[first]", order: [:month, :day, :year]) }
end
def test_select_date_can_have_more_then_1000_years_interval_if_forced_via_parameter
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index a814cab686..e225c3de09 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -122,13 +122,13 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_logging_of_missing_template_for_dependencies
- assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do
+ assert_logged "Couldn't find template for digesting: messages/something_missing" do
dependencies("messages/something_missing")
end
end
def test_logging_of_missing_template_for_nested_dependencies
- assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do
+ assert_logged "Couldn't find template for digesting: messages/something_missing" do
nested_dependencies("messages/something_missing")
end
end
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index c8963fee9c..4d4ed3c35c 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-# The exhaustive tests are in test/controller/html/sanitizer_test.rb.
+# The exhaustive tests are in the rails-html-sanitizer gem.
# This tests that the helpers hook up correctly to the sanitizer classes.
class SanitizeHelperTest < ActionView::TestCase
tests ActionView::Helpers::SanitizeHelper
@@ -10,6 +10,7 @@ class SanitizeHelperTest < ActionView::TestCase
assert_equal "on my mind\nall day long", strip_links("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>")
assert_equal "Magic", strip_links("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic")
assert_equal "My mind\nall <b>day</b> long", strip_links("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>")
+ assert_equal "&lt;malformed &amp; link", strip_links('<<a href="https://example.org">malformed & link</a>')
end
def test_sanitize_form
@@ -26,6 +27,7 @@ class SanitizeHelperTest < ActionView::TestCase
assert_equal("Dont touch me", strip_tags("Dont touch me"))
assert_equal("This is a test.", strip_tags("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>"))
assert_equal "This has a here.", strip_tags("This has a <!-- comment --> here.")
+ assert_equal("Jekyll &amp; Hyde", strip_tags("Jekyll & Hyde"))
assert_equal "", strip_tags("<script>")
end
diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js
index dbeb8ad832..5932195363 100644
--- a/actionview/test/ujs/public/test/call-remote.js
+++ b/actionview/test/ujs/public/test/call-remote.js
@@ -100,6 +100,34 @@ asyncTest('JS code should be executed', 1, function() {
submit()
})
+asyncTest('ecmascript code should be executed', 1, function() {
+ buildForm({ method: 'post', 'data-type': 'script' })
+
+ $('form').append('<input type="text" name="content_type" value="application/ecmascript">')
+ $('form').append('<input type="text" name="content" value="ok(true, \'remote code should be run\')">')
+
+ submit()
+})
+
+asyncTest('execution of JS code does not modify current DOM', 1, function() {
+ var docLength, newDocLength
+ function getDocLength() {
+ return document.documentElement.outerHTML.length
+ }
+
+ buildForm({ method: 'post', 'data-type': 'script' })
+
+ $('form').append('<input type="text" name="content_type" value="text/javascript">')
+ $('form').append('<input type="text" name="content" value="\'remote code should be run\'">')
+
+ docLength = getDocLength()
+
+ submit(function() {
+ newDocLength = getDocLength()
+ ok(docLength === newDocLength, 'executed JS should not present in the document')
+ })
+})
+
asyncTest('XML document should be parsed', 1, function() {
buildForm({ method: 'post', 'data-type': 'html' })
diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js
index 28190c2250..229b9e1466 100644
--- a/actionview/test/ujs/public/test/data-confirm.js
+++ b/actionview/test/ujs/public/test/data-confirm.js
@@ -26,6 +26,13 @@ module('data-confirm', {
'data-confirm': 'Are you absolutely sure?'
}))
+ $('#qunit-fixture').append($('<button />', {
+ type: 'submit',
+ form: 'confirm',
+ disabled: 'disabled',
+ 'data-confirm': 'Are you absolutely sure?'
+ }))
+
this.windowConfirm = window.confirm
},
teardown: function() {
@@ -286,3 +293,24 @@ asyncTest('clicking on the children of a link should also trigger a confirm', 6,
.find('strong')
.triggerNative('click')
})
+
+asyncTest('clicking on the children of a disabled button should not trigger a confirm.', 1, function() {
+ var message
+ // auto-decline:
+ window.confirm = function(msg) { message = msg; return false }
+
+ $('button[data-confirm][disabled]')
+ .html("<strong>Click me</strong>")
+ .bindNative('confirm', function() {
+ App.assertCallbackNotInvoked('confirm')
+ })
+ .find('strong')
+ .bindNative('click', function() {
+ App.assertCallbackInvoked('click')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ start()
+ }, 50)
+})
diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js
index b756add24e..161a92ac11 100644
--- a/actionview/test/ujs/public/test/data-remote.js
+++ b/actionview/test/ujs/public/test/data-remote.js
@@ -1,3 +1,17 @@
+(function() {
+
+function buildSelect(attrs) {
+ attrs = $.extend({
+ 'name': 'user_data', 'data-remote': 'true', 'data-url': '/echo', 'data-params': 'data1=value1'
+ }, attrs)
+
+ $('#qunit-fixture').append(
+ $('<select />', attrs)
+ .append($('<option />', {value: 'optionValue1', text: 'option1'}))
+ .append($('<option />', {value: 'optionValue2', text: 'option2'}))
+ )
+}
+
module('data-remote', {
setup: function() {
$('#qunit-fixture')
@@ -135,17 +149,7 @@ asyncTest('clicking on a button with data-remote attribute', 5, function() {
})
asyncTest('changing a select option with data-remote attribute', 5, function() {
- $('form')
- .append(
- $('<select />', {
- 'name': 'user_data',
- 'data-remote': 'true',
- 'data-params': 'data1=value1',
- 'data-url': '/echo'
- })
- .append($('<option />', {value: 'optionValue1', text: 'option1'}))
- .append($('<option />', {value: 'optionValue2', text: 'option2'}))
- )
+ buildSelect()
$('select[data-remote]')
.bindNative('ajax:success', function(e, data, status, xhr) {
@@ -350,17 +354,7 @@ asyncTest('submitting a form with falsy "data-remote" attribute', 0, function()
})
asyncTest('changing a select option with falsy "data-remote" attribute', 0, function() {
- $('form')
- .append(
- $('<select />', {
- 'name': 'user_data',
- 'data-remote': 'false',
- 'data-params': 'data1=value1',
- 'data-url': '/echo'
- })
- .append($('<option />', {value: 'optionValue1', text: 'option1'}))
- .append($('<option />', {value: 'optionValue2', text: 'option2'}))
- )
+ buildSelect({'data-remote': 'false'})
$('select[data-remote=false]:first')
.bindNative('ajax:beforeSend', function() {
@@ -413,3 +407,32 @@ asyncTest('form buttons should only be serialized when clicked', 4, function() {
})
.find('[name=submit2]').triggerNative('click')
})
+
+asyncTest('changing a select option without "data-url" attribute still fires ajax request to current location', 1, function() {
+ var currentLocation, ajaxLocation
+
+ buildSelect({'data-url': ''});
+
+ $('select[data-remote]')
+ .bindNative('ajax:beforeSend', function(e, xhr, settings) {
+ // Get current location (the same way jQuery does)
+ try {
+ currentLocation = location.href
+ } catch(err) {
+ currentLocation = document.createElement( 'a' )
+ currentLocation.href = ''
+ currentLocation = currentLocation.href
+ }
+
+ ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '')
+ equal(ajaxLocation, currentLocation, 'URL should be current page by default')
+
+ return false
+ })
+ .val('optionValue2')
+ .triggerNative('change')
+
+ setTimeout(function() { start() }, 20)
+})
+
+})()
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index d561745611..6b4f93df8b 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,53 +1 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Correctly set test adapter when configure the queue adapter on a per job.
-
- Fixes #26360.
-
- *Yuji Yaginuma*
-
-* Push skipped jobs to `enqueued_jobs` when using `perform_enqueued_jobs` with a `only` filter in tests
-
- *Alexander Pauly*
-
-* Removed deprecated support to passing the adapter class to `.queue_adapter`.
-
- *Rafael Mendonça França*
-
-* Removed deprecated `#original_exception` in `ActiveJob::DeserializationError`.
-
- *Rafael Mendonça França*
-
-* Added instance variable `@queue` to JobWrapper.
-
- This will fix issues in [resque-scheduler](https://github.com/resque/resque-scheduler) `#job_to_hash` method,
- so we can use `#enqueue_delayed_selection`, `#remove_delayed` method in resque-scheduler smoothly.
-
- *mu29*
-
-* Yield the job instance so you have access to things like `job.arguments` on the custom logic after retries fail.
-
- *DHH*
-
-* Added declarative exception handling via `ActiveJob::Base.retry_on` and `ActiveJob::Base.discard_on`.
-
- Examples:
-
- class RemoteServiceJob < ActiveJob::Base
- retry_on CustomAppException # defaults to 3s wait, 5 attempts
- retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
- retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
- retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
- discard_on ActiveJob::DeserializationError
-
- def perform(*args)
- # Might raise CustomAppException or AnotherCustomAppException for something domain specific
- # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
- # Might raise Net::OpenTimeout when the remote service is down
- end
- end
-
- *DHH*
-
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activejob/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 2b608b9a65..bf81f37e81 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveJob
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
index ec825f12cd..1b633b210e 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -24,27 +24,30 @@ module ActiveJob
end
def enqueue(job) #:nodoc:
+ return if filtered?(job)
+
job_data = job_to_hash(job)
enqueue_or_perform(perform_enqueued_jobs, job, job_data)
end
def enqueue_at(job, timestamp) #:nodoc:
+ return if filtered?(job)
+
job_data = job_to_hash(job, at: timestamp)
enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
end
private
-
def job_to_hash(job, extras = {})
{ job: job.class, args: job.serialize.fetch("arguments"), queue: job.queue_name }.merge!(extras)
end
def enqueue_or_perform(perform, job, job_data)
- if !perform || filtered?(job)
- enqueued_jobs << job_data
- else
+ if perform
performed_jobs << job_data
Base.execute job.serialize
+ else
+ enqueued_jobs << job_data
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 2e6357f824..81e75b4374 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -56,17 +56,6 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
- def test_assert_enqueued_jobs_when_performing_with_only_option
- assert_nothing_raised do
- assert_enqueued_jobs 1, only: HelloJob do
- perform_enqueued_jobs only: LoggingJob do
- HelloJob.perform_later("sean")
- LoggingJob.perform_later("yves")
- end
- end
- end
- end
-
def test_assert_no_enqueued_jobs_with_no_block
assert_nothing_raised do
assert_no_enqueued_jobs
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 1503b6a3e4..cdba0cee12 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,32 +1,32 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
+* Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
-* Remove deprecated behavior that halts callbacks when the return is false.
+ Change `#keys` to only return the keys that don't have empty messages.
- *Rafael Mendonça França*
+ Change `#values` to only return the not empty values.
-* Remove unused `ActiveModel::TestCase` class.
+ Example:
- *Yuji Yaginuma*
+ # Before
+ person = Person.new
+ person.errors.keys # => []
+ person.errors.values # => []
+ person.errors.messages # => {}
+ person.errors[:name] # => []
+ person.errors.messages # => {:name => []}
+ person.errors.keys # => [:name]
+ person.errors.values # => [[]]
-* Moved DecimalWithoutScale, Text, and UnsignedInteger from Active Model to Active Record
+ # After
+ person = Person.new
+ person.errors.keys # => []
+ person.errors.values # => []
+ person.errors.messages # => {}
+ person.errors[:name] # => []
+ person.errors.messages # => {:name => []}
+ person.errors.keys # => []
+ person.errors.values # => []
- *Iain Beeston*
+ *bogdanvlviv*
-* Allow indifferent access in `ActiveModel::Errors`.
- `#include?`, `#has_key?`, `#key?`, `#delete` and `#full_messages_for`.
-
- *Kenichi Kamiya*
-
-* Removed deprecated `:tokenizer` in the length validator.
-
- *Rafael Mendonça França*
-
-* Removed deprecated methods in `ActiveModel::Errors`.
-
- `#get`, `#set`, `[]=`, `add_on_empty` and `add_on_blank`.
-
- *Rafael Mendonça França*
-
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
index 7dad3b6dff..ee130df989 100644
--- a/activemodel/lib/active_model/attribute_assignment.rb
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -27,7 +27,7 @@ module ActiveModel
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
end
- return if new_attributes.nil? || new_attributes.empty?
+ return if new_attributes.empty?
attributes = new_attributes.stringify_keys
_assign_attributes(sanitize_for_mass_assignment(attributes))
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 166c6ac21f..b5c0b43b61 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -472,5 +472,9 @@ module ActiveModel
def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
end
+
+ def _read_attribute(attr)
+ __send__(attr)
+ end
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 6e0af99ad7..dc81f74779 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -179,13 +179,13 @@ module ActiveModel
# Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
!!changes_include?(attr) &&
- (to == OPTION_NOT_GIVEN || to == __send__(attr)) &&
+ (to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
(from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
end
# Handles <tt>*_was</tt> for +method_missing+.
def attribute_was(attr) # :nodoc:
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
end
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
@@ -226,7 +226,7 @@ module ActiveModel
# Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
+ [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
end
# Handles <tt>*_previous_change</tt> for +method_missing+.
@@ -239,7 +239,7 @@ module ActiveModel
return if attribute_changed?(attr)
begin
- value = __send__(attr)
+ value = _read_attribute(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 9df4ca51fe..942b4fa9bb 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -132,15 +132,6 @@ module ActiveModel
#
# person.errors[:name] # => ["cannot be nil"]
# person.errors['name'] # => ["cannot be nil"]
- #
- # Note that, if you try to get errors of an attribute which has
- # no errors associated with it, this method will instantiate
- # an empty error list for it and +keys+ will return an array
- # of error keys which includes this attribute.
- #
- # person.errors.keys # => []
- # person.errors[:name] # => []
- # person.errors.keys # => [:name]
def [](attribute)
messages[attribute.to_sym]
end
@@ -181,7 +172,9 @@ module ActiveModel
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.values # => [["cannot be nil", "must be specified"]]
def values
- messages.values
+ messages.select do |key, value|
+ !value.empty?
+ end.values
end
# Returns all message keys.
@@ -189,7 +182,9 @@ module ActiveModel
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.keys # => [:name]
def keys
- messages.keys
+ messages.select do |key, value|
+ !value.empty?
+ end.keys
end
# Returns +true+ if no errors are found, +false+ otherwise.
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index 6a2ab2a8e5..67bdfaa643 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveModel
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index b8e6d2376b..095801d8f0 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -21,16 +21,8 @@ module ActiveModel
class << self
attr_accessor :registry # :nodoc:
- delegate :add_modifier, to: :registry
- # Add a new type to the registry, allowing it to be referenced as a
- # symbol by ActiveRecord::Attributes::ClassMethods#attribute. If your
- # type is only meant to be used with a specific database adapter, you can
- # do so by passing +adapter: :postgresql+. If your type has the same
- # name as a native type for the current adapter, an exception will be
- # raised unless you specify an +:override+ option. +override: true+ will
- # cause your type to be used instead of the native type. +override:
- # false+ will cause the native type to be used over yours if one exists.
+ # Add a new type to the registry, allowing it to be get through ActiveModel::Type#lookup
def register(type_name, klass = nil, **options, &block)
registry.register(type_name, klass, **options, &block)
end
diff --git a/activemodel/lib/active_model/type/date.rb b/activemodel/lib/active_model/type/date.rb
index 6e313fbca8..eefd080351 100644
--- a/activemodel/lib/active_model/type/date.rb
+++ b/activemodel/lib/active_model/type/date.rb
@@ -12,7 +12,7 @@ module ActiveModel
end
def type_cast_for_schema(value)
- "'#{value.to_s(:db)}'"
+ value.to_s(:db).inspect
end
private
diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb
index e57a52104b..53cf7c6029 100644
--- a/activemodel/lib/active_model/type/helpers/time_value.rb
+++ b/activemodel/lib/active_model/type/helpers/time_value.rb
@@ -38,7 +38,7 @@ module ActiveModel
end
def type_cast_for_schema(value)
- "'#{value.to_s(:db)}'"
+ value.to_s(:db).inspect
end
def user_input_in_time_zone(value)
diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb
index c7e0208a5a..850cab962b 100644
--- a/activemodel/lib/active_model/type/string.rb
+++ b/activemodel/lib/active_model/type/string.rb
@@ -12,7 +12,12 @@ module ActiveModel
private
def cast_value(value)
- ::String.new(super)
+ case value
+ when ::String then ::String.new(value)
+ when true then "t".freeze
+ when false then "f".freeze
+ else value.to_s
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index b4b8d9f33c..98b3f6a8e5 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,4 +1,3 @@
-
module ActiveModel
module Validations
class FormatValidator < EachValidator # :nodoc:
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 30a9ef472d..995b331245 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -70,6 +70,7 @@ module ActiveModel
end
def parse_raw_value_as_a_number(raw_value)
+ return raw_value.to_i if is_integer?(raw_value)
Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
end
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 6e8a434bbe..ce4106a5e1 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -1,4 +1,3 @@
-
module ActiveModel
module Validations
class PresenceValidator < EachValidator # :nodoc:
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 0872084cf5..43aee5a814 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -99,6 +99,14 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["omg", "zomg"], errors.values
end
+ test "values returns an empty array after try to get a message only" do
+ errors = ActiveModel::Errors.new(self)
+ errors.messages[:foo]
+ errors.messages[:baz]
+
+ assert_equal [], errors.values
+ end
+
test "keys returns the error keys" do
errors = ActiveModel::Errors.new(self)
errors.messages[:foo] << "omg"
@@ -107,6 +115,14 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal [:foo, :baz], errors.keys
end
+ test "keys returns an empty array after try to get a message only" do
+ errors = ActiveModel::Errors.new(self)
+ errors.messages[:foo]
+ errors.messages[:baz]
+
+ assert_equal [], errors.keys
+ end
+
test "detecting whether there are errors with empty?, blank?, include?" do
person = Person.new
person.errors[:foo]
diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb
index 222083817e..47d412e27e 100644
--- a/activemodel/test/cases/type/string_test.rb
+++ b/activemodel/test/cases/type/string_test.rb
@@ -12,16 +12,25 @@ module ActiveModel
end
test "cast strings are mutable" do
- s = "foo"
type = Type::String.new
+
+ s = "foo"
assert_equal false, type.cast(s).frozen?
+ assert_equal false, s.frozen?
+
+ f = "foo".freeze
+ assert_equal false, type.cast(f).frozen?
+ assert_equal true, f.frozen?
end
test "values are duped coming out" do
- s = "foo"
type = Type::String.new
+
+ s = "foo"
assert_not_same s, type.cast(s)
+ assert_equal s, type.cast(s)
assert_not_same s, type.deserialize(s)
+ assert_equal s, type.deserialize(s)
end
end
end
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index a1be2de578..c0158e075f 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -260,6 +260,15 @@ class NumericalityValidationTest < ActiveModel::TestCase
Person.clear_validators!
end
+ def test_validates_numericality_with_exponent_number
+ base = 10_000_000_000_000_000
+ Topic.validates_numericality_of :approved, less_than_or_equal_to: base
+ topic = Topic.new
+ topic.approved = (base + 1).to_s
+
+ assert topic.invalid?
+ end
+
def test_validates_numericality_with_invalid_args
assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" }
assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" }
diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb
index 9b74d83b37..f733c45cf0 100644
--- a/activemodel/test/validators/email_validator.rb
+++ b/activemodel/test/validators/email_validator.rb
@@ -1,4 +1,3 @@
-
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "is not an email") unless
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a2566ae5fb..f74425b281 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,697 +1,18 @@
-* Deprecate using `#quoted_id` in quoting.
+* Support Descending Indexes for MySQL.
- *Ryuta Kamizono*
-
-* Fix `wait_timeout` to configurable for mysql2 adapter.
-
- Fixes #26556.
-
- *Ryuta Kamizono*
-
-
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Correctly dump native timestamp types for MySQL.
-
- The native timestamp type in MySQL is different from datetime type.
- Internal representation of the timestamp type is UNIX time, This means
- that timestamp columns are affected by time zone.
-
- > SET time_zone = '+00:00';
- Query OK, 0 rows affected (0.00 sec)
-
- > INSERT INTO time_with_zone(ts,dt) VALUES (NOW(),NOW());
- Query OK, 1 row affected (0.02 sec)
-
- > SELECT * FROM time_with_zone;
- +---------------------+---------------------+
- | ts | dt |
- +---------------------+---------------------+
- | 2016-02-07 22:11:44 | 2016-02-07 22:11:44 |
- +---------------------+---------------------+
- 1 row in set (0.00 sec)
-
- > SET time_zone = '-08:00';
- Query OK, 0 rows affected (0.00 sec)
-
- > SELECT * FROM time_with_zone;
- +---------------------+---------------------+
- | ts | dt |
- +---------------------+---------------------+
- | 2016-02-07 14:11:44 | 2016-02-07 22:11:44 |
- +---------------------+---------------------+
- 1 row in set (0.00 sec)
-
- *Ryuta Kamizono*
-
-* All integer-like PKs are autoincrement unless they have an explicit default.
-
- *Matthew Draper*
-
-* Omit redundant `using: :btree` for schema dumping.
-
- *Ryuta Kamizono*
-
-* Deprecate passing `default` to `index_name_exists?`.
-
- *Ryuta Kamizono*
-
-* PostgreSQL: schema dumping support for interval and OID columns.
-
- *Ryuta Kamizono*
-
-* Deprecate `supports_primary_key?` on connection adapters since it's
- been long unused and unsupported.
-
- *Ryuta Kamizono*
-
-* Make `table_name=` reset current statement cache,
- so queries are not run against the previous table name.
-
- *namusyaka*
-
-* Allow `ActiveRecord::Base#as_json` to be passed a frozen Hash.
-
- *Isaac Betesh*
-
-* Fix inspection behavior when the :id column is not primary key.
-
- *namusyaka*
-
-* Deprecate locking records with unpersisted changes.
-
- *Marc Schütz*
-
-* Remove deprecated behavior that halts callbacks when the return is false.
-
- *Rafael Mendonça França*
-
-* Deprecate `ColumnDumper#migration_keys`.
-
- *Ryuta Kamizono*
-
-* Fix `association_primary_key_type` for reflections with symbol primary key.
-
- Fixes #27864.
-
- *Daniel Colson*
-
-* Virtual/generated column support for MySQL 5.7.5+ and MariaDB 5.2.0+.
-
- MySQL generated columns: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
- MariaDB virtual columns: https://mariadb.com/kb/en/mariadb/virtual-computed-columns/
-
- Declare virtual columns with `t.virtual name, type: …, as: "expression"`.
- Pass `stored: true` to persist the generated value (false by default).
-
- Example:
-
- create_table :generated_columns do |t|
- t.string :name
- t.virtual :upper_name, type: :string, as: "UPPER(name)"
- t.virtual :name_length, type: :integer, as: "LENGTH(name)", stored: true
- t.index :name_length # May be indexed, too!
- end
-
- *Ryuta Kamizono*
-
-* Deprecate `initialize_schema_migrations_table` and `initialize_internal_metadata_table`.
-
- *Ryuta Kamizono*
-
-* Support foreign key creation for SQLite3.
-
- *Ryuta Kamizono*
-
-* Place generated migrations into the path set by `config.paths["db/migrate"]`.
-
- *Kevin Glowacz*
-
-* Raise `ActiveRecord::InvalidForeignKey` when a foreign key constraint fails on SQLite3.
-
- *Ryuta Kamizono*
-
-* Add the touch option to `#increment!` and `#decrement!`.
-
- *Hiroaki Izu*
-
-* Deprecate passing a class to the `class_name` because it eagerloads more classes than
- necessary and potentially creates circular dependencies.
-
- *Kir Shatrov*
-
-* Raise error when has_many through is defined before through association.
-
- Fixes #26834.
-
- *Chris Holmes*
-
-* Deprecate passing `name` to `indexes`.
-
- *Ryuta Kamizono*
-
-* Remove deprecated tasks: `db:test:clone`, `db:test:clone_schema`, `db:test:clone_structure`.
-
- *Rafel Mendonça França*
-
-* Compare deserialized values for `PostgreSQL::OID::Hstore` types when
- calling `ActiveRecord::Dirty#changed_in_place?`.
-
- Fixes #27502.
-
- *Jon Moss*
-
-* Raise `ArgumentError` when passing an `ActiveRecord::Base` instance to `.find`,
- `.exists?` and `.update`.
-
- *Rafael Mendonça França*
-
-* Respect precision option for arrays of timestamps.
-
- Fixes #27514.
-
- *Sean Griffin*
-
-* Optimize slow model instantiation when using STI and `store_full_sti_class = false` option.
-
- *Konstantin Lazarev*
-
-* Add `touch` option to counter cache modifying methods.
-
- Works when updating, resetting, incrementing and decrementing counters:
-
- # Touches `updated_at`/`updated_on`.
- Topic.increment_counter(:messages_count, 1, touch: true)
- Topic.decrement_counter(:messages_count, 1, touch: true)
-
- # Touches `last_discussed_at`.
- Topic.reset_counters(18, :messages, touch: :last_discussed_at)
-
- # Touches `updated_at` and `last_discussed_at`.
- Topic.update_counters(18, messages_count: 5, touch: %i( updated_at last_discussed_at ))
-
- Fixes #26724.
-
- *Jarred Trost*
-
-* Remove deprecated `#uniq`, `#uniq!`, and `#uniq_value`.
-
- *Ryuta Kamizono*
-
-* Remove deprecated `#insert_sql`, `#update_sql`, and `#delete_sql`.
-
- *Ryuta Kamizono*
-
-* Remove deprecated `#use_transactional_fixtures` configuration.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `#raise_in_transactional_callbacks` configuration.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `#load_schema_for`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated conditions parameter from `#destroy_all` and `#delete_all`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to passing arguments to `#select` when a block is provided.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to query using commas on LIMIT.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to passing a class as a value in a query.
-
- *Rafael Mendonça França*
-
-* Raise `ActiveRecord::IrreversibleOrderError` when using `last` with an irreversible
- order.
-
- *Rafael Mendonça França*
-
-* Raise when a `has_many :through` association has an ambiguous reflection name.
-
- *Rafael Mendonça França*
-
-* Raise when `ActiveRecord::Migration` is inherited from directly.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `original_exception` argument in `ActiveRecord::StatementInvalid#initialize`
- and `ActiveRecord::StatementInvalid#original_exception`.
-
- *Rafael Mendonça França*
-
-* `#tables` and `#table_exists?` return only tables and not views.
-
- All the deprecations on those methods were removed.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `name` argument from `#tables`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to passing a column to `#quote`.
-
- *Rafael Mendonça França*
-
-* Set `:time` as a timezone aware type and remove deprecation when
- `config.active_record.time_zone_aware_types` is not explicitly set.
-
- *Rafael Mendonça França*
-
-* Remove deprecated force reload argument in singular and collection association readers.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `activerecord.errors.messages.restrict_dependent_destroy.one` and
- `activerecord.errors.messages.restrict_dependent_destroy.many` i18n scopes.
-
- *Rafael Mendonça França*
-
-* Allow passing extra flags to `db:structure:load` and `db:structure:dump`
-
- Introduces `ActiveRecord::Tasks::DatabaseTasks.structure_(load|dump)_flags` to customize the
- eventual commands run against the database, e.g. mysqldump/pg_dump.
-
- *Kir Shatrov*
-
-* Notifications see frozen SQL string.
-
- Fixes #23774.
-
- *Richard Monette*
-
-* RuntimeErrors are no longer translated to `ActiveRecord::StatementInvalid`.
-
- *Richard Monette*
-
-* Change the schema cache format to use YAML instead of Marshal.
-
- *Kir Shatrov*
-
-* Support index length and order options using both string and symbol
- column names.
-
- Fixes #27243.
-
- *Ryuta Kamizono*
-
-* Raise `ActiveRecord::RangeError` when values that executed are out of range.
-
- *Ryuta Kamizono*
-
-* Raise `ActiveRecord::NotNullViolation` when a record cannot be inserted
- or updated because it would violate a not null constraint.
+ MySQL 8.0.1 and higher supports descending indexes: `DESC` in an index definition is no longer ignored.
+ See https://dev.mysql.com/doc/refman/8.0/en/descending-indexes.html.
*Ryuta Kamizono*
-* Emulate db trigger behaviour for after_commit :destroy, :update.
-
- Race conditions can occur when an ActiveRecord is destroyed
- twice or destroyed and updated. The callbacks should only be
- triggered once, similar to a SQL database trigger.
-
- *Stefan Budeanu*
-
-* Moved `DecimalWithoutScale`, `Text`, and `UnsignedInteger` from Active Model to Active Record.
-
- *Iain Beeston*
-
-* Fix `write_attribute` method to check whether an attribute is aliased or not, and
- use the aliased attribute name if needed.
-
- *Prathamesh Sonpatki*
-
-* Fix `read_attribute` method to check whether an attribute is aliased or not, and
- use the aliased attribute name if needed.
-
- Fixes #26417.
-
- *Prathamesh Sonpatki*
-
-* PostgreSQL & MySQL: Use big integer as primary key type for new tables.
-
- *Jon McCartie*, *Pavel Pravosud*
-
-* Change the type argument of `ActiveRecord::Base#attribute` to be optional.
- The default is now `ActiveRecord::Type::Value.new`, which provides no type
- casting behavior.
-
- *Sean Griffin*
-
-* Don't treat unsigned integers with zerofill as signed.
-
- Fixes #27125.
-
- *Ryuta Kamizono*
-
-* Fix the uniqueness validation scope with a polymorphic association.
-
- *Sergey Alekseev*
-
-* Raise `ActiveRecord::RecordNotFound` from collection `*_ids` setters
- for unknown IDs with a better error message.
-
- Changes the collection `*_ids` setters to cast provided IDs the data
- type of the primary key set in the association, not the model
- primary key.
-
- *Dominic Cleal*
-
-* For PostgreSQL >= 9.4 use `pgcrypto`'s `gen_random_uuid()` instead of
- `uuid-ossp`'s UUID generation function.
-
- *Yuji Yaginuma*, *Yaw Boakye*
-
-* Introduce `Model#reload_<association>` to bring back the behavior
- of `Article.category(true)` where `category` is a singular
- association.
-
- The force reloading of the association reader was deprecated
- in #20888. Unfortunately the suggested alternative of
- `article.reload.category` does not expose the same behavior.
-
- This patch adds a reader method with the prefix `reload_` for
- singular associations. This method has the same semantics as
- passing true to the association reader used to have.
-
- *Yves Senn*
-
-* Make sure eager loading `ActiveRecord::Associations` also loads
- constants defined in `ActiveRecord::Associations::Preloader`.
-
- *Yves Senn*
-
-* Allow `ActionController::Parameters`-like objects to be passed as
- values for Postgres HStore columns.
-
- Fixes #26904.
-
- *Jon Moss*
-
-* Added `stat` method to `ActiveRecord::ConnectionAdapters::ConnectionPool`.
-
- Example:
-
- ActiveRecord::Base.connection_pool.stat # =>
- { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
-
- *Pavel Evstigneev*
-
-* Avoid `unscope(:order)` when `limit_value` is presented for `count`
- and `exists?`.
-
- If `limit_value` is presented, records fetching order is very important
- for performance. We should not unscope the order in the case.
-
- *Ryuta Kamizono*
-
-* Fix an Active Record `DateTime` field `NoMethodError` caused by incomplete
- datetime.
-
- Fixes #24195.
-
- *Sen Zhang*
-
-* Allow `slice` to take an array of methods(without the need for splatting).
-
- *Cohen Carlisle*
-
-* Improved partial writes with HABTM and has many through associations
- to fire database query only if relation has been changed.
-
- Fixes #19663.
-
- *Mehmet Emin İNAÇ*
-
-* Deprecate passing arguments and block at the same time to
- `ActiveRecord::QueryMethods#select`.
-
- *Prathamesh Sonpatki*
-
-* Optimistic locking: Added ability to update `locking_column` value.
- Ignore optimistic locking if trying to update with new `locking_column` value.
+* Fix inconsistency with changed attributes when overriding AR attribute reader.
*bogdanvlviv*
-* Fixed: Optimistic locking does not work well with `null` in the database.
-
- Fixes #26024.
-
- *bogdanvlviv*
-
-* Fixed support for case insensitive comparisons of `text` columns in
- PostgreSQL.
-
- *Edho Arief*
-
-* Serialize JSON attribute value `nil` as SQL `NULL`, not JSON `null`.
-
- *Trung Duc Tran*
-
-* Return `true` from `update_attribute` when the value of the attribute
- to be updated is unchanged.
-
- Fixes #26593.
-
- *Prathamesh Sonpatki*
-
-* Always store errors details information with symbols.
-
- When the association is autosaved we were storing the details with
- string keys. This was creating inconsistency with other details that are
- added using the `Errors#add` method. It was also inconsistent with the
- `Errors#messages` storage.
-
- To fix this inconsistency we are always storing with symbols. This will
- cause a small breaking change because in those cases the details could
- be accessed as strings keys but now it can not.
-
- Fix #26499.
-
- *Rafael Mendonça França*, *Marcus Vieira*
-
-* Calling `touch` on a model using optimistic locking will now leave the model
- in a non-dirty state with no attribute changes.
-
- Fixes #26496.
-
- *Jakob Skjerning*
-
-* Using a mysql2 connection after it fails to reconnect will now have an error message
- saying the connection is closed rather than an undefined method error message.
-
- *Dylan Thacker-Smith*
-
-* PostgreSQL array columns will now respect the encoding of strings contained
- in the array.
-
- Fixes #26326.
-
- *Sean Griffin*
-
-* Inverse association instances will now be set before `after_find` or
- `after_initialize` callbacks are run.
-
- Fixes #26320.
-
- *Sean Griffin*
-
-* Remove unnecessarily association load when a `belongs_to` association has already been
- loaded then the foreign key is changed directly and the record saved.
-
- *James Coleman*
-
-* Remove standardized column types/arguments spaces in schema dump.
-
- *Tim Petricola*
-
-* Avoid loading records from database when they are already loaded using
- the `pluck` method on a collection.
-
- Fixes #25921.
-
- *Ryuta Kamizono*
-
-* Remove text default treated as an empty string in non-strict mode for
- consistency with other types.
-
- Strict mode controls how MySQL handles invalid or missing values in
- data-change statements such as INSERT or UPDATE. If strict mode is not
- in effect, MySQL inserts adjusted values for invalid or missing values
- and produces warnings.
-
- def test_mysql_not_null_defaults_non_strict
- using_strict(false) do
- with_mysql_not_null_table do |klass|
- record = klass.new
- assert_nil record.non_null_integer
- assert_nil record.non_null_string
- assert_nil record.non_null_text
- assert_nil record.non_null_blob
-
- record.save!
- record.reload
-
- assert_equal 0, record.non_null_integer
- assert_equal "", record.non_null_string
- assert_equal "", record.non_null_text
- assert_equal "", record.non_null_blob
- end
- end
- end
-
- https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict
-
- *Ryuta Kamizono*
-
-* SQLite3 migrations to add a column to an existing table can now be
- successfully rolled back when the column was given and invalid column
- type.
-
- Fixes #26087.
-
- *Travis O'Neill*
-
-* Deprecate `sanitize_conditions`. Use `sanitize_sql` instead.
-
- *Ryuta Kamizono*
-
-* Doing count on relations that contain LEFT OUTER JOIN Arel node no longer
- force a DISTINCT. This solves issues when using count after a left_joins.
-
- *Maxime Handfield Lapointe*
-
-* RecordNotFound raised by association.find exposes `id`, `primary_key` and
- `model` methods to be consistent with RecordNotFound raised by Record.find.
-
- *Michel Pigassou*
-
-* Hashes can once again be passed to setters of `composed_of`, if all of the
- mapping methods are methods implemented on `Hash`.
-
- Fixes #25978.
-
- *Sean Griffin*
-
-* Fix the SELECT statement in `#table_comment` for MySQL.
-
- *Takeshi Akima*
-
-* Virtual attributes will no longer raise when read on models loaded from the
- database.
-
- *Sean Griffin*
-
-* Support calling the method `merge` in `scope`'s lambda.
-
- *Yasuhiro Sugino*
-
-* Fixes multi-parameter attributes conversion with invalid params.
-
- *Hiroyuki Ishii*
-
-* Add newline between each migration in `structure.sql`.
-
- Keeps schema migration inserts as a single commit, but allows for easier
- git diffing.
-
- Fixes #25504.
-
- *Grey Baker*, *Norberto Lopes*
-
-* The flag `error_on_ignored_order_or_limit` has been deprecated in favor of
- the current `error_on_ignored_order`.
-
- *Xavier Noria*
-
-* Batch processing methods support `limit`:
-
- Post.limit(10_000).find_each do |post|
- # ...
- end
-
- It also works in `find_in_batches` and `in_batches`.
-
- *Xavier Noria*
-
-* Using `group` with an attribute that has a custom type will properly cast
- the hash keys after calling a calculation method like `count`.
-
- Fixes #25595.
-
- *Sean Griffin*
-
-* Fix the generated `#to_param` method to use `omission: ''` so that
- the resulting output is actually up to 20 characters, not
- effectively 17 to leave room for the default "...".
- Also call `#parameterize` before `#truncate` and make the
- `separator: /-/` to maximize the information included in the
- output.
-
- Fixes #23635.
-
- *Rob Biedenharn*
-
-* Ensure concurrent invocations of the connection reaper cannot allocate the
- same connection to two threads.
-
- Fixes #25585.
-
- *Matthew Draper*
-
-* Inspecting an object with an associated array of over 10 elements no longer
- truncates the array, preventing `inspect` from looping infinitely in some
- cases.
+* When calling the dynamic fixture accessor method with no arguments it now returns all fixtures of this type.
+ Previously this method always returned an empty array.
*Kevin McPhillips*
-* Removed the unused methods `ActiveRecord::Base.connection_id` and
- `ActiveRecord::Base.connection_id=`.
-
- *Sean Griffin*
-
-* Ensure hashes can be assigned to attributes created using `composed_of`.
-
- Fixes #25210.
-
- *Sean Griffin*
-
-* Fix logging edge case where if an attribute was of the binary type and
- was provided as a Hash.
-
- *Jon Moss*
-
-* Handle JSON deserialization correctly if the column default from database
- adapter returns `''` instead of `nil`.
-
- *Johannes Opper*
-
-* Introduce `ActiveRecord::TransactionSerializationError` for catching
- transaction serialization failures or deadlocks.
-
- *Erol Fornoles*
-
-* PostgreSQL: Fix `db:structure:load` silent failure on SQL error.
-
- The command line flag `-v ON_ERROR_STOP=1` should be used
- when invoking `psql` to make sure errors are not suppressed.
-
- Example:
-
- psql -v ON_ERROR_STOP=1 -q -f awesome-file.sql my-app-db
-
- Fixes #23818.
-
- *Ralin Chimev*
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4606c91ffd..e52c2004f3 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1647,6 +1647,9 @@ module ActiveRecord
# +:inverse_of+ to avoid an extra query during validation.
# NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
# you don't want to have association presence validated, use <tt>optional: true</tt>.
+ # [:default]
+ # Provide a callable (i.e. proc or lambda) to specify that the association should
+ # be initialized with a particular record before validation.
#
# Option examples:
# belongs_to :firm, foreign_key: "client_of"
@@ -1660,6 +1663,7 @@ module ActiveRecord
# belongs_to :comment, touch: true
# belongs_to :company, touch: :employees_last_updated_at
# belongs_to :user, optional: true
+ # belongs_to :account, default: -> { company.account }
def belongs_to(name, scope = nil, options = {})
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index badde9973f..120d75416c 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -25,7 +25,7 @@ module ActiveRecord
chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
scope.extending! Array(reflection.options[:extend])
- add_constraints(scope, owner, klass, reflection, chain_head, chain_tail)
+ add_constraints(scope, owner, reflection, chain_head, chain_tail)
end
def join_type
@@ -60,8 +60,8 @@ module ActiveRecord
table.create_join(table, table.create_on(constraint), join_type)
end
- def last_chain_scope(scope, table, reflection, owner, association_klass)
- join_keys = reflection.join_keys(association_klass)
+ def last_chain_scope(scope, table, reflection, owner)
+ join_keys = reflection.join_keys
key = join_keys.key
foreign_key = join_keys.foreign_key
@@ -80,8 +80,8 @@ module ActiveRecord
value_transformation.call(value)
end
- def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
- join_keys = reflection.join_keys(association_klass)
+ def next_chain_scope(scope, table, reflection, foreign_table, next_reflection)
+ join_keys = reflection.join_keys
key = join_keys.key
foreign_key = join_keys.foreign_key
@@ -120,10 +120,10 @@ module ActiveRecord
[runtime_reflection, previous_reflection]
end
- def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail)
+ def add_constraints(scope, owner, refl, chain_head, chain_tail)
owner_reflection = chain_tail
table = owner_reflection.alias_name
- scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
+ scope = last_chain_scope(scope, table, owner_reflection, owner)
reflection = chain_head
while reflection
@@ -132,7 +132,7 @@ module ActiveRecord
unless reflection == chain_tail
foreign_table = next_reflection.alias_name
- scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
+ scope = next_chain_scope(scope, table, reflection, foreign_table, next_reflection)
end
# Exclude the scope of the association itself, because that
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 64b2311911..a2432e389a 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -21,6 +21,10 @@ module ActiveRecord
self.target = record
end
+ def default(&block)
+ writer(instance_exec(&block)) if reader.nil?
+ end
+
def reset
super
@updated = false
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index a1609ab0fb..2b9dd8aae8 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- super + [:polymorphic, :touch, :counter_cache, :optional]
+ super + [:polymorphic, :touch, :counter_cache, :optional, :default]
end
def self.valid_dependent_options
@@ -16,6 +16,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
super
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
add_touch_callbacks(model, reflection) if reflection.options[:touch]
+ add_default_callbacks(model, reflection) if reflection.options[:default]
end
def self.define_accessors(mixin, reflection)
@@ -118,6 +119,12 @@ module ActiveRecord::Associations::Builder # :nodoc:
model.after_destroy callback.(:changes_to_save)
end
+ def self.add_default_callbacks(model, reflection)
+ model.before_validation lambda { |o|
+ o.association(reflection.name).default(&reflection.options[:default])
+ }
+ end
+
def self.add_destroy_callbacks(model, reflection)
model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 55bf2e0ff0..5d6676f0df 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -78,7 +78,7 @@ module ActiveRecord
# # #<Pet id: nil, name: "Choo-Choo">
# # ]
#
- # person.pets.select(:id, :name )
+ # person.pets.select(:id, :name)
# # => [
# # #<Pet id: 1, name: "Fancy-Fancy">,
# # #<Pet id: 2, name: "Spook">,
@@ -743,10 +743,6 @@ module ActiveRecord
# # ]
#--
- def uniq
- load_target.uniq
- end
-
def calculate(operation, column_name)
null_scope? ? scope.calculate(operation, column_name) : super
end
@@ -1121,7 +1117,7 @@ module ActiveRecord
SpawnMethods,
].flat_map { |klass|
klass.public_instance_methods(false)
- } - self.public_instance_methods(false) + [:scoping]
+ } - self.public_instance_methods(false) - [:select] + [:scoping]
delegate(*delegate_methods, to: :scope)
@@ -1154,8 +1150,9 @@ module ActiveRecord
end
def method_missing(method, *args, &block)
- if scope.respond_to?(method)
- scope.public_send(method, *args, &block)
+ if scope.respond_to?(method) && scope.extending_values.any?
+ extend(*scope.extending_values)
+ public_send(method, *args, &block)
else
super
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 87e0847ec1..8995b1e352 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -171,7 +171,7 @@ module ActiveRecord
chain = child.reflection.chain
foreign_table = parent.table
foreign_klass = parent.base_klass
- child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, chain)
+ child.join_constraints(foreign_table, foreign_klass, join_type, tables, chain)
end
def make_outer_joins(parent, child)
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 f5fcba1236..97cfec0302 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -23,7 +23,7 @@ module ActiveRecord
JoinInformation = Struct.new :joins, :binds
- def join_constraints(foreign_table, foreign_klass, node, join_type, tables, chain)
+ def join_constraints(foreign_table, foreign_klass, join_type, tables, chain)
joins = []
binds = []
tables = tables.reverse
@@ -34,35 +34,16 @@ module ActiveRecord
table = tables.shift
klass = reflection.klass
- join_keys = reflection.join_keys(klass)
+ join_keys = reflection.join_keys
key = join_keys.key
foreign_key = join_keys.foreign_key
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
- scope_chain_items = reflection.scopes.map do |item|
- if item.is_a?(Relation)
- item
- else
- ActiveRecord::Relation.create(klass, table, predicate_builder)
- .instance_exec(node, &item)
- end
- end
+ scope_chain_items = reflection.join_scopes(table, predicate_builder)
+ klass_scope = reflection.klass_join_scope(table, predicate_builder)
- klass_scope =
- if klass.current_scope
- klass.current_scope.clone.tap { |scope|
- scope.joins_values = []
- }
- else
- relation = ActiveRecord::Relation.create(
- klass,
- table,
- predicate_builder,
- )
- klass.send(:build_default_scope, relation)
- end
scope_chain_items.concat [klass_scope].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 6aa414ba6b..bd5003d63a 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -282,7 +282,7 @@ module ActiveRecord
#{attr_name} is not an attribute known to Active Record.
This behavior is deprecated and will be removed in the next
version of Rails. If you'd like #{attr_name} to be managed
- by Active Record, add `attribute :#{attr_name} to your class.
+ by Active Record, add `attribute :#{attr_name}` to your class.
EOW
mutations_from_database.deprecated_force_change(attr_name)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 3f2e86a98d..53dbbd8c21 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -825,7 +825,7 @@ module ActiveRecord
# end
#
# class Book < ActiveRecord::Base
- # establish_connection "library_db"
+ # establish_connection :library_db
# end
#
# class ScaryBook < Book
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index e5a24b2aca..f0c0fbab6c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -61,17 +61,6 @@ module ActiveRecord
lookup_cast_type(column.sql_type)
end
- def fetch_type_metadata(sql_type)
- cast_type = lookup_cast_type(sql_type)
- SqlTypeMetadata.new(
- sql_type: sql_type,
- type: cast_type.type,
- limit: cast_type.limit,
- precision: cast_type.precision,
- scale: cast_type.scale,
- )
- end
-
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
@@ -152,11 +141,18 @@ module ActiveRecord
"'#{quote_string(value.to_s)}'"
end
- private
-
- def type_casted_binds(binds)
+ def type_casted_binds(binds) # :nodoc:
+ if binds.first.is_a?(Array)
+ binds.map { |column, value| type_cast(value, column) }
+ else
binds.map { |attr| type_cast(attr.value_for_database) }
end
+ end
+
+ private
+ def lookup_cast_type(sql_type)
+ type_map.lookup(sql_type)
+ end
def id_value_for_database(value)
if primary_key = value.class.primary_key
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index c48a4acff8..a4fecc4a8e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -133,5 +133,6 @@ module ActiveRecord
end
end
end
+ SchemaCreation = AbstractAdapter::SchemaCreation # :nodoc:
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 5eb7787226..46d7f84efd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -2,8 +2,33 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
- # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- IndexDefinition = Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
+ # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
+ class IndexDefinition # :nodoc:
+ attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment
+
+ def initialize(
+ table, name,
+ unique = false,
+ columns = [],
+ lengths: {},
+ orders: {},
+ where: nil,
+ type: nil,
+ using: nil,
+ comment: nil
+ )
+ @table = table
+ @name = name
+ @unique = unique
+ @columns = columns
+ @lengths = lengths
+ @orders = orders
+ @where = where
+ @type = type
+ @using = using
+ @comment = comment
+ end
+ end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
@@ -116,16 +141,12 @@ module ActiveRecord
private
- def as_options(value, default = {})
- if value.is_a?(Hash)
- value
- else
- default
- end
+ def as_options(value)
+ value.is_a?(Hash) ? value : {}
end
def polymorphic_options
- as_options(polymorphic, options)
+ as_options(polymorphic)
end
def index_options
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 c44215cd43..13629dee7f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -31,6 +31,8 @@ module ActiveRecord
# Returns the relation names useable to back Active Record models.
# For most adapters this means all #tables and #views.
def data_sources
+ select_values(data_source_sql, "SCHEMA")
+ rescue NotImplementedError
tables | views
end
@@ -39,12 +41,14 @@ module ActiveRecord
# data_source_exists?(:ebooks)
#
def data_source_exists?(name)
+ select_values(data_source_sql(name), "SCHEMA").any? if name.present?
+ rescue NotImplementedError
data_sources.include?(name.to_s)
end
# Returns an array of table names defined in the database.
def tables
- raise NotImplementedError, "#tables is not implemented"
+ select_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
end
# Checks to see if the table +table_name+ exists on the database.
@@ -52,12 +56,14 @@ module ActiveRecord
# table_exists?(:developers)
#
def table_exists?(table_name)
+ select_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
+ rescue NotImplementedError
tables.include?(table_name.to_s)
end
# Returns an array of view names defined in the database.
def views
- raise NotImplementedError, "#views is not implemented"
+ select_values(data_source_sql(type: "VIEW"), "SCHEMA")
end
# Checks to see if the view +view_name+ exists on the database.
@@ -65,6 +71,8 @@ module ActiveRecord
# view_exists?(:ebooks)
#
def view_exists?(view_name)
+ select_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
+ rescue NotImplementedError
views.include?(view_name.to_s)
end
@@ -97,10 +105,12 @@ module ActiveRecord
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
end
- # Returns an array of Column objects for the table specified by +table_name+.
- # See the concrete implementation for details on the expected parameter values.
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name)
- raise NotImplementedError, "#columns is not implemented"
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |field|
+ new_column_from_field(table_name, field)
+ end
end
# Checks to see if a column exists in a given table.
@@ -334,18 +344,16 @@ module ActiveRecord
# part_id int NOT NULL,
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
#
- def create_join_table(table_1, table_2, options = {})
+ def create_join_table(table_1, table_2, column_options: {}, **options)
join_table_name = find_join_table_name(table_1, table_2, options)
- column_options = options.delete(:column_options) || {}
- column_options.reverse_merge!(null: false)
- type = column_options.delete(:type) || :integer
+ column_options.reverse_merge!(null: false, index: false)
- t1_column, t2_column = [table_1, table_2].map { |t| t.to_s.singularize.foreign_key }
+ t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
create_table(join_table_name, options.merge!(id: false)) do |td|
- td.send type, t1_column, column_options
- td.send type, t2_column, column_options
+ td.references t1_ref, column_options
+ td.references t2_ref, column_options
yield td if block_given?
end
end
@@ -968,16 +976,6 @@ module ActiveRecord
foreign_key_for(from_table, options_or_to_table).present?
end
- def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc:
- return unless supports_foreign_keys?
- foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
- end
-
- def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
- foreign_key_for(from_table, options_or_to_table) || \
- raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
- end
-
def foreign_key_column_for(table_name) # :nodoc:
prefix = Base.table_name_prefix
suffix = Base.table_name_suffix
@@ -993,23 +991,10 @@ module ActiveRecord
end
def dump_schema_information #:nodoc:
- versions = ActiveRecord::SchemaMigration.order("version").pluck(:version)
+ versions = ActiveRecord::SchemaMigration.all_versions
insert_versions_sql(versions)
end
- def insert_versions_sql(versions) # :nodoc:
- sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
-
- if versions.is_a?(Array)
- sql = "INSERT INTO #{sm_table} (version) VALUES\n"
- sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
- sql << ";\n\n"
- sql
- else
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
- end
- end
-
def initialize_schema_migrations_table # :nodoc:
ActiveRecord::SchemaMigration.create_table
end
@@ -1027,12 +1012,11 @@ module ActiveRecord
def assume_migrated_upto_version(version, migrations_paths)
migrations_paths = Array(migrations_paths)
version = version.to_i
- sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
- migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
- paths = migrations_paths.map { |p| "#{p}/[0-9]*_*.rb" }
- versions = Dir[*paths].map do |filename|
- filename.split("/").last.split("_").first.to_i
+ migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
+ versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
+ ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
end
unless migrated.include?(version)
@@ -1256,6 +1240,10 @@ module ActiveRecord
end
end
+ def schema_creation
+ SchemaCreation.new(self)
+ end
+
def create_table_definition(*args)
TableDefinition.new(*args)
end
@@ -1264,6 +1252,17 @@ module ActiveRecord
AlterTable.new create_table_definition(name)
end
+ def fetch_type_metadata(sql_type)
+ cast_type = lookup_cast_type(sql_type)
+ SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ end
+
def index_column_names(column_names)
if column_names.is_a?(String) && /\W/.match?(column_names)
column_names
@@ -1288,6 +1287,24 @@ module ActiveRecord
end
end
+ def foreign_key_for(from_table, options_or_to_table = {})
+ return unless supports_foreign_keys?
+ foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
+ end
+
+ def foreign_key_for!(from_table, options_or_to_table = {})
+ foreign_key_for(from_table, options_or_to_table) || \
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
+ end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "CASCADE"; :cascade
+ when "SET NULL"; :nullify
+ when "RESTRICT"; :restrict
+ end
+ end
+
def validate_index_length!(table_name, new_name, internal = false)
max_index_length = internal ? index_name_length : allowed_index_name_length
@@ -1307,6 +1324,27 @@ module ActiveRecord
def can_remove_index_by_name?(options)
options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
end
+
+ def insert_versions_sql(versions)
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
+
+ if versions.is_a?(Array)
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n"
+ sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
+ sql << ";\n\n"
+ sql
+ else
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
+ end
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ raise NotImplementedError
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ raise NotImplementedError
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 6bb072dd73..19b7821494 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -149,57 +149,67 @@ module ActiveRecord
end
def begin_transaction(options = {})
- run_commit_callbacks = !current_transaction.joinable?
- transaction =
- if @stack.empty?
- RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
- else
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
- run_commit_callbacks: run_commit_callbacks)
- end
+ @connection.lock.synchronize do
+ run_commit_callbacks = !current_transaction.joinable?
+ transaction =
+ if @stack.empty?
+ RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
+ else
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
+ run_commit_callbacks: run_commit_callbacks)
+ end
- @stack.push(transaction)
- transaction
+ @stack.push(transaction)
+ transaction
+ end
end
def commit_transaction
- transaction = @stack.last
+ @connection.lock.synchronize do
+ transaction = @stack.last
- begin
- transaction.before_commit_records
- ensure
- @stack.pop
- end
+ begin
+ transaction.before_commit_records
+ ensure
+ @stack.pop
+ end
- transaction.commit
- transaction.commit_records
+ transaction.commit
+ transaction.commit_records
+ end
end
def rollback_transaction(transaction = nil)
- transaction ||= @stack.pop
- transaction.rollback
- transaction.rollback_records
+ @connection.lock.synchronize do
+ transaction ||= @stack.pop
+ transaction.rollback
+ transaction.rollback_records
+ end
end
def within_new_transaction(options = {})
- transaction = begin_transaction options
- yield
- rescue Exception => error
- if transaction
- rollback_transaction
- after_failure_actions(transaction, error)
- end
- raise
- ensure
- unless error
- if Thread.current.status == "aborting"
- rollback_transaction if transaction
- else
- begin
- commit_transaction
- rescue Exception
- rollback_transaction(transaction) unless transaction.state.completed?
- raise
+ @connection.lock.synchronize do
+ begin
+ transaction = begin_transaction options
+ yield
+ rescue Exception => error
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
+ raise
+ ensure
+ unless error
+ if Thread.current.status == "aborting"
+ rollback_transaction if transaction
+ else
+ begin
+ commit_transaction
+ rescue Exception
+ rollback_transaction(transaction) unless transaction.state.completed?
+ raise
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index b31ce0a181..85d6fbe8b3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -74,7 +74,7 @@ module ActiveRecord
SIMPLE_INT = /\A\d+\z/
attr_accessor :visitor, :pool
- attr_reader :schema_cache, :owner, :logger
+ attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock
alias :in_use? :owner
def self.type_cast_config_to_integer(config)
@@ -93,8 +93,6 @@ module ActiveRecord
end
end
- attr_reader :prepared_statements
-
def initialize(connection, logger = nil, config = {}) # :nodoc:
super()
@@ -142,32 +140,8 @@ module ActiveRecord
end
end
- def collector
- if prepared_statements
- SQLString.new
- else
- BindCollector.new
- end
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::ToSql.new(self)
- end
-
- def valid_type?(type)
- false
- end
-
- def schema_creation
- SchemaCreation.new self
- end
-
- # Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name) # :nodoc:
- table_name = table_name.to_s
- column_definitions(table_name).map do |field|
- new_column_from_field(table_name, field)
- end
+ def valid_type?(type) # :nodoc:
+ !native_database_types[type].nil?
end
# this method must only be called while holding connection pool's mutex
@@ -232,10 +206,10 @@ module ActiveRecord
self.class::ADAPTER_NAME
end
- # Does this adapter support migrations?
- def supports_migrations?
- false
+ def supports_migrations? # :nodoc:
+ true
end
+ deprecate :supports_migrations?
def supports_primary_key? # :nodoc:
true
@@ -439,12 +413,15 @@ module ActiveRecord
# This is done under the hood by calling #active?. If the connection
# is no longer active, then this method will reconnect to the database.
def verify!(*ignored)
+ if ignored.size > 0
+ ActiveSupport::Deprecation.warn("Passing arguments to #verify method of the connection has no effect and has been deprecated. Please remove all arguments from the #verify method call.")
+ end
reconnect! unless active?
end
# Provides access to the underlying database driver for this adapter. For
# example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
- # and a PGconn object in case of PostgreSQLAdapter.
+ # and a PG::Connection object in case of PostgreSQLAdapter.
#
# This is useful for when you need to call a proprietary method such as
# PostgreSQL's lo_* methods.
@@ -480,14 +457,6 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
- Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
- end
-
- def lookup_cast_type(sql_type) # :nodoc:
- type_map.lookup(sql_type)
- end
-
def column_name_for_operation(operation, node) # :nodoc:
visitor.accept(node, collector).value
end
@@ -634,6 +603,18 @@ module ActiveRecord
columns(table_name).detect { |c| c.name == column_name } ||
raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
end
+
+ def collector
+ if prepared_statements
+ SQLString.new
+ else
+ BindCollector.new
+ end
+ end
+
+ def arel_visitor
+ Arel::Visitors::ToSql.new(self)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 5f86a11c2d..71fa7b929c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,6 +6,7 @@ require "active_record/connection_adapters/mysql/quoting"
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/schema_statements"
require "active_record/connection_adapters/mysql/type_metadata"
require "active_support/core_ext/string/strip"
@@ -15,19 +16,12 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include MySQL::Quoting
include MySQL::ColumnDumper
+ include MySQL::SchemaStatements
def update_table_definition(table_name, base) # :nodoc:
MySQL::Table.new(table_name, base)
end
- def schema_creation # :nodoc:
- MySQL::SchemaCreation.new(self)
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::MySQL.new(self)
- end
-
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -54,9 +48,6 @@ module ActiveRecord
json: { name: "json" },
}
- INDEX_TYPES = [:fulltext, :spatial]
- INDEX_USINGS = [:btree, :hash]
-
class StatementPool < ConnectionAdapters::StatementPool
private def dealloc(stmt)
stmt[:stmt].close
@@ -89,11 +80,6 @@ module ActiveRecord
/mariadb/i.match?(full_version)
end
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations?
- true
- end
-
def supports_bulk_alter? #:nodoc:
true
end
@@ -104,10 +90,8 @@ module ActiveRecord
true
end
- # Technically MySQL allows to create indexes with the sort order syntax
- # but at the moment (5.5) it doesn't yet implement them
def supports_index_sort_order?
- true
+ !mariadb? && version >= "8.0.1"
end
def supports_transaction_isolation?
@@ -174,10 +158,6 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(*args) #:nodoc:
- MySQL::Column.new(*args)
- end
-
# Must return the MySQL error number from the exception, if the exception has an
# error number.
def error_number(exception) # :nodoc:
@@ -315,109 +295,18 @@ module ActiveRecord
show_variable "collation_database"
end
- def tables # :nodoc:
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
- sql << " AND table_schema = #{quote(@config[:database])}"
-
- select_values(sql, "SCHEMA")
- end
-
- def views # :nodoc:
- select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA")
- end
-
- def data_sources # :nodoc:
- sql = "SELECT table_name FROM information_schema.tables "
- sql << "WHERE table_schema = #{quote(@config[:database])}"
-
- select_values(sql, "SCHEMA")
- end
-
- def table_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- schema, name = extract_schema_qualified_name(table_name)
-
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
- sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def data_source_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- schema, name = extract_schema_qualified_name(table_name)
-
- sql = "SELECT table_name FROM information_schema.tables "
- sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def view_exists?(view_name) # :nodoc:
- return false unless view_name.present?
-
- schema, name = extract_schema_qualified_name(view_name)
-
- sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
- sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
end
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) #:nodoc:
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
- indexes = []
- current_index = nil
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
- each_hash(result) do |row|
- if current_index != row[:Key_name]
- next if row[:Key_name] == "PRIMARY" # skip the primary key
- current_index = row[:Key_name]
-
- mysql_index_type = row[:Index_type].downcase.to_sym
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence)
- end
-
- indexes.last.columns << row[:Column_name]
- indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
- end
- end
-
- indexes
- end
-
- def new_column_from_field(table_name, field) # :nodoc:
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- default, default_function = nil, field[:Default]
- else
- default, default_function = field[:Default], nil
- end
- new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
- end
-
def table_comment(table_name) # :nodoc:
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
select_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT table_comment
FROM information_schema.tables
- WHERE table_schema = #{quote(schema)}
- AND table_name = #{quote(name)}
+ WHERE table_schema = #{scope[:schema]}
+ AND table_name = #{scope[:name]}
SQL
end
@@ -517,7 +406,7 @@ module ActiveRecord
def foreign_keys(table_name)
raise ArgumentError unless table_name.present?
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
SELECT fk.referenced_table_name AS 'to_table',
@@ -530,9 +419,9 @@ module ActiveRecord
JOIN information_schema.referential_constraints rc
USING (constraint_schema, constraint_name)
WHERE fk.referenced_column_name IS NOT NULL
- AND fk.table_schema = #{quote(schema)}
- AND fk.table_name = #{quote(name)}
- AND rc.table_name = #{quote(name)}
+ AND fk.table_schema = #{scope[:schema]}
+ AND fk.table_name = #{scope[:name]}
+ AND rc.table_name = #{scope[:name]}
SQL
fk_info.map do |row|
@@ -604,14 +493,14 @@ module ActiveRecord
def primary_keys(table_name) # :nodoc:
raise ArgumentError unless table_name.present?
- schema, name = extract_schema_qualified_name(table_name)
+ scope = quoted_scope(table_name)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT column_name
FROM information_schema.key_column_usage
WHERE constraint_name = 'PRIMARY'
- AND table_schema = #{quote(schema)}
- AND table_name = #{quote(name)}
+ AND table_schema = #{scope[:schema]}
+ AND table_name = #{scope[:name]}
ORDER BY ordinal_position
SQL
end
@@ -648,10 +537,6 @@ module ActiveRecord
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
- def valid_type?(type)
- !native_database_types[type].nil?
- end
-
def default_index_type?(index) # :nodoc:
index.using == :btree || super
end
@@ -716,10 +601,6 @@ module ActiveRecord
end
end
- def fetch_type_metadata(sql_type, extra = "")
- MySQL::TypeMetadata.new(super(sql_type), extra: extra)
- end
-
def add_index_length(quoted_columns, **options)
if length = options[:length]
case length
@@ -786,11 +667,11 @@ module ActiveRecord
def change_column_sql(table_name, column_name, type, options = {})
column = column_for(table_name, column_name)
- unless options_include_default?(options)
+ unless options.key?(:default)
options[:default] = column.default
end
- unless options.has_key?(:null)
+ unless options.key?(:null)
options[:null] = column.null
end
@@ -922,19 +803,12 @@ module ActiveRecord
end
end
- def extract_foreign_key_action(specifier) # :nodoc:
- case specifier
- when "CASCADE"; :cascade
- when "SET NULL"; :nullify
- end
- end
-
def create_table_info(table_name) # :nodoc:
select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(*args) # :nodoc:
- MySQL::TableDefinition.new(*args)
+ def arel_visitor
+ Arel::Visitors::MySQL.new(self)
end
def mismatched_foreign_key(message)
@@ -949,12 +823,6 @@ module ActiveRecord
)
end
- def extract_schema_qualified_name(string) # :nodoc:
- schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
- schema, name = @config[:database], schema unless name
- [schema, name]
- end
-
def integer_to_sql(limit) # :nodoc:
case limit
when 1; "tinyint"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
new file mode 100644
index 0000000000..571edffec7
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -0,0 +1,112 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module SchemaStatements # :nodoc:
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
+ indexes = []
+ current_index = nil
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
+ each_hash(result) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == "PRIMARY" # skip the primary key
+ current_index = row[:Key_name]
+
+ mysql_index_type = row[:Index_type].downcase.to_sym
+ case mysql_index_type
+ when :fulltext, :spatial
+ index_type = mysql_index_type
+ when :btree, :hash
+ index_using = mysql_index_type
+ end
+
+ indexes << IndexDefinition.new(
+ row[:Table],
+ row[:Key_name],
+ row[:Non_unique].to_i == 0,
+ type: index_type,
+ using: index_using,
+ comment: row[:Index_comment].presence
+ )
+ end
+
+ indexes.last.columns << row[:Column_name]
+ indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
+ indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
+ end
+ end
+
+ indexes
+ end
+
+ private
+ def schema_creation
+ MySQL::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ MySQL::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ default, default_function = nil, field[:Default]
+ else
+ default, default_function = field[:Default], nil
+ end
+
+ MySQL::Column.new(
+ field[:Field],
+ default,
+ type_metadata,
+ field[:Null] == "YES",
+ table_name,
+ default_function,
+ field[:Collation],
+ comment: field[:Comment].presence
+ )
+ end
+
+ def fetch_type_metadata(sql_type, extra = "")
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra)
+ end
+
+ def extract_foreign_key_action(specifier)
+ super unless specifier == "RESTRICT"
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+
+ sql = "SELECT table_name FROM information_schema.tables"
+ sql << " WHERE table_schema = #{scope[:schema]}"
+ sql << " AND table_name = #{scope[:name]}" if scope[:name]
+ sql << " AND table_type = #{scope[:type]}" if scope[:type]
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ schema, name = extract_schema_qualified_name(name)
+ scope = {}
+ scope[:schema] = schema ? quote(schema) : "database()"
+ scope[:name] = quote(name) if name
+ scope[:type] = quote(type) if type
+ scope
+ end
+
+ def extract_schema_qualified_name(string)
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
+ schema, name = nil, schema unless name
+ [schema, name]
+ end
+ end
+ end
+ end
+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
index 24dcf852e1..9ad6a6c0d0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -2,6 +2,8 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
+ undef to_yaml if method_defined?(:to_yaml)
+
attr_reader :extra
def initialize(type_metadata, extra: "")
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 e1a75f8e5e..a73a8c1726 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -23,7 +23,7 @@ module ActiveRecord
when ::String
type_cast_array(@pg_decoder.decode(value), :deserialize)
when Data
- deserialize(value.values)
+ type_cast_array(value.values, :deserialize)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
index 8f9d6e7f9b..702fa8175c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def deserialize(value)
return if value.nil?
return value.to_s if value.is_a?(Type::Binary::Data)
- PGconn.unescape_bytea(super)
+ PG::Connection.unescape_bytea(super)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 6663448a99..da8d0c6992 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -33,7 +33,7 @@ module ActiveRecord
# Quotes schema names for use in SQL queries.
def quote_schema_name(name)
- PGconn.quote_ident(name)
+ PG::Connection.quote_ident(name)
end
def quote_table_name_for_assignment(table, attr)
@@ -42,7 +42,7 @@ module ActiveRecord
# Quotes column names for use in SQL queries.
def quote_column_name(name) # :nodoc:
- @quoted_column_names[name] ||= PGconn.quote_ident(super).freeze
+ @quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
end
# Quote date/time values for use in SQL input.
@@ -77,6 +77,9 @@ module ActiveRecord
end
private
+ def lookup_cast_type(sql_type)
+ super(select_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
+ end
def _quote(value)
case value
@@ -105,7 +108,7 @@ module ActiveRecord
case value
when Type::Binary::Data
# Return a bind param hash with format as binary.
- # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
+ # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
# for more information
{ value: value.to_s, format: 1 }
when OID::Xml::Data, OID::Bit::Data
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 44a7338bf5..730e7c7137 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -6,8 +6,50 @@ module ActiveRecord
true
end
- def disable_referential_integrity # :nodoc:
+ def disable_referential_integrity(&block) # :nodoc:
if supports_disable_referential_integrity?
+ if supports_alter_constraint?
+ disable_referential_integrity_with_alter_constraint(&block)
+ else
+ disable_referential_integrity_with_disable_trigger(&block)
+ end
+ else
+ yield
+ end
+ end
+
+ private
+
+ def disable_referential_integrity_with_alter_constraint
+ tables_constraints = execute(<<-SQL).values
+ SELECT table_name, constraint_name
+ FROM information_schema.table_constraints
+ WHERE constraint_type = 'FOREIGN KEY'
+ AND is_deferrable = 'NO'
+ SQL
+
+ execute(
+ tables_constraints.collect { |table, constraint|
+ "ALTER TABLE #{quote_table_name(table)} ALTER CONSTRAINT #{constraint} DEFERRABLE"
+ }.join(";")
+ )
+
+ begin
+ transaction do
+ execute("SET CONSTRAINTS ALL DEFERRED")
+
+ yield
+ end
+ ensure
+ execute(
+ tables_constraints.collect { |table, constraint|
+ "ALTER TABLE #{quote_table_name(table)} ALTER CONSTRAINT #{constraint} NOT DEFERRABLE"
+ }.join(";")
+ )
+ end
+ end
+
+ def disable_referential_integrity_with_disable_trigger
original_exception = nil
begin
@@ -39,10 +81,7 @@ Rails needs superuser privileges to disable referential integrity.
end
rescue ActiveRecord::ActiveRecordError
end
- else
- yield
end
- end
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 a61d920a73..5b483ad4ab 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -54,81 +54,13 @@ module ActiveRecord
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
- # Returns the list of all tables in the schema search path.
- def tables
- select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", "SCHEMA")
- end
-
- def data_sources # :nodoc
- select_values(<<-SQL, "SCHEMA")
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND n.nspname = ANY (current_schemas(false))
- SQL
- end
-
- def views # :nodoc:
- select_values(<<-SQL, "SCHEMA")
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND n.nspname = ANY (current_schemas(false))
- SQL
- end
-
- # Returns true if table exists.
- # If the schema is not specified as part of +name+ then it will only find tables within
- # the current schema search path (regardless of permissions to access tables in other schemas)
- def table_exists?(name)
- name = Utils.extract_schema_qualified_name(name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT tablename
- FROM pg_tables
- WHERE tablename = #{quote(name.identifier)}
- AND schemaname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
- def data_source_exists?(name) # :nodoc:
- name = Utils.extract_schema_qualified_name(name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND c.relname = #{quote(name.identifier)}
- AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
- def view_exists?(view_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(view_name.to_s)
- return false unless name.identifier
-
- select_values(<<-SQL, "SCHEMA").any?
- SELECT c.relname
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND c.relname = #{quote(name.identifier)}
- AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- SQL
- end
-
def drop_table(table_name, options = {}) # :nodoc:
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
# Returns true if schema exists.
def schema_exists?(name)
- select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", "SCHEMA").to_i > 0
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
end
# Verifies existence of an index with a given name.
@@ -138,8 +70,8 @@ module ActiveRecord
Passing default to #index_name_exists? is deprecated without replacement.
MSG
end
- table = Utils.extract_schema_qualified_name(table_name.to_s)
- index = Utils.extract_schema_qualified_name(index_name.to_s)
+ table = quoted_scope(table_name)
+ index = quoted_scope(index_name)
select_value(<<-SQL, "SCHEMA").to_i > 0
SELECT COUNT(*)
@@ -148,9 +80,9 @@ module ActiveRecord
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 i.relname = '#{index.identifier}'
- AND t.relname = '#{table.identifier}'
- AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
+ AND i.relname = #{index[:name]}
+ AND t.relname = #{table[:name]}
+ AND n.nspname = #{index[:schema]}
SQL
end
@@ -162,7 +94,7 @@ module ActiveRecord
MSG
end
- table = Utils.extract_schema_qualified_name(table_name.to_s)
+ scope = quoted_scope(table_name)
result = query(<<-SQL, "SCHEMA")
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
@@ -176,8 +108,8 @@ module ActiveRecord
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))'}
+ AND t.relname = #{scope[:name]}
+ AND n.nspname = #{scope[:schema]}
ORDER BY i.relname
SQL
@@ -208,27 +140,17 @@ module ActiveRecord
]
end
- IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
- end.compact
- end
-
- def new_column_from_field(table_name, field) # :nondoc:
- column_name, type, default, notnull, oid, fmod, collation, comment = field
- oid = oid.to_i
- fmod = fmod.to_i
- type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
- default_value = extract_value_from_default(default)
- default_function = extract_default_function(default_value, default)
- PostgreSQLColumn.new(
- column_name,
- default_value,
- type_metadata,
- !notnull,
- table_name,
- default_function,
- collation,
- comment: comment.presence
- )
+ IndexDefinition.new(
+ table_name,
+ index_name,
+ unique,
+ columns,
+ orders: orders,
+ where: where,
+ using: using.to_sym,
+ comment: comment.presence
+ )
+ end
end
def table_options(table_name) # :nodoc:
@@ -239,22 +161,22 @@ module ActiveRecord
# Returns a comment stored in database for given table
def table_comment(table_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(table_name.to_s)
- if name.identifier
+ scope = quoted_scope(table_name, type: "BASE TABLE")
+ if scope[:name]
select_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
FROM pg_catalog.pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relname = #{quote(name.identifier)}
- AND c.relkind IN ('r') -- (r)elation/table
- AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
+ WHERE c.relname = #{scope[:name]}
+ AND c.relkind IN (#{scope[:type]})
+ AND n.nspname = #{scope[:schema]}
SQL
end
end
# Returns the current database name.
def current_database
- select_value("select current_database()", "SCHEMA")
+ select_value("SELECT current_database()", "SCHEMA")
end
# Returns the current schema name.
@@ -430,18 +352,18 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
- name = Utils.extract_schema_qualified_name(table_name.to_s)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
- SELECT column_name
- FROM information_schema.key_column_usage kcu
- JOIN information_schema.table_constraints tc
- ON kcu.table_name = tc.table_name
- AND kcu.table_schema = tc.table_schema
- AND kcu.constraint_name = tc.constraint_name
- WHERE constraint_type = 'PRIMARY KEY'
- AND kcu.table_name = #{quote(name.identifier)}
- AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
- ORDER BY kcu.ordinal_position
+ SELECT a.attname
+ FROM (
+ SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
+ FROM pg_index
+ WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
+ AND indisprimary
+ ) i
+ JOIN pg_attribute a
+ ON a.attrelid = i.indrelid
+ AND a.attnum = i.indkey[i.idx]
+ ORDER BY i.idx
SQL
end
@@ -489,7 +411,7 @@ module ActiveRecord
end
execute sql
- change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
+ change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
@@ -579,6 +501,7 @@ module ActiveRecord
end
def foreign_keys(table_name)
+ scope = quoted_scope(table_name)
fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
FROM pg_constraint c
@@ -588,8 +511,8 @@ module ActiveRecord
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
JOIN pg_namespace t3 ON c.connamespace = t3.oid
WHERE c.contype = 'f'
- AND t1.relname = #{quote(table_name)}
- AND t3.nspname = ANY (current_schemas(false))
+ AND t1.relname = #{scope[:name]}
+ AND t3.nspname = #{scope[:schema]}
ORDER BY c.conname
SQL
@@ -607,18 +530,6 @@ module ActiveRecord
end
end
- def extract_foreign_key_action(specifier) # :nodoc:
- case specifier
- when "c"; :cascade
- when "n"; :nullify
- when "r"; :restrict
- end
- end
-
- def index_name_length
- 63
- end
-
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
sql = \
@@ -666,17 +577,84 @@ module ActiveRecord
[super, *order_columns].join(", ")
end
- def fetch_type_metadata(column_name, sql_type, oid, fmod)
- cast_type = get_oid_type(oid, fmod, column_name, sql_type)
- simple_type = SqlTypeMetadata.new(
- sql_type: sql_type,
- type: cast_type.type,
- limit: cast_type.limit,
- precision: cast_type.precision,
- scale: cast_type.scale,
- )
- PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
- end
+ private
+ def schema_creation
+ PostgreSQL::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ PostgreSQL::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ column_name, type, default, notnull, oid, fmod, collation, comment = field
+ type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
+ default_value = extract_value_from_default(default)
+ default_function = extract_default_function(default_value, default)
+
+ PostgreSQLColumn.new(
+ column_name,
+ default_value,
+ type_metadata,
+ !notnull,
+ table_name,
+ default_function,
+ collation,
+ comment: comment.presence
+ )
+ end
+
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
+ simple_type = SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
+ end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "c"; :cascade
+ when "n"; :nullify
+ when "r"; :restrict
+ end
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+ scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
+
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
+ sql << " WHERE n.nspname = #{scope[:schema]}"
+ sql << " AND c.relname = #{scope[:name]}" if scope[:name]
+ sql << " AND c.relkind IN (#{scope[:type]})"
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ schema, name = extract_schema_qualified_name(name)
+ type = \
+ case type
+ when "BASE TABLE"
+ "'r'"
+ when "VIEW"
+ "'v','m'"
+ end
+ scope = {}
+ scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
+ scope[:name] = quote(name) if name
+ scope[:type] = type if type
+ scope
+ end
+
+ def extract_schema_qualified_name(string)
+ name = Utils.extract_schema_qualified_name(string.to_s)
+ [name.schema, name.identifier]
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
index 311988625f..f57179ae59 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -1,6 +1,8 @@
module ActiveRecord
module ConnectionAdapters
class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
+ undef to_yaml if method_defined?(:to_yaml)
+
attr_reader :oid, :fmod, :array
def initialize(type_metadata, oid: nil, fmod: nil)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
index a3f9ce6d64..aa7940188a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -19,9 +19,9 @@ module ActiveRecord
def quoted
if schema
- PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
+ PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier)
else
- PGconn.quote_ident(identifier)
+ PG::Connection.quote_ident(identifier)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index c89e29ba44..7ae9bd9a5f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -3,6 +3,7 @@ gem "pg", "~> 0.18"
require "pg"
require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/statement_pool"
require "active_record/connection_adapters/postgresql/column"
require "active_record/connection_adapters/postgresql/database_statements"
require "active_record/connection_adapters/postgresql/explain_pretty_printer"
@@ -15,7 +16,6 @@ require "active_record/connection_adapters/postgresql/schema_dumper"
require "active_record/connection_adapters/postgresql/schema_statements"
require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
-require "active_record/connection_adapters/statement_pool"
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -29,11 +29,11 @@ module ActiveRecord
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
- # Forward only valid config params to PGconn.connect.
- valid_conn_param_keys = PGconn.conndefaults_hash.keys + [:requiressl]
+ # Forward only valid config params to PG::Connection.connect.
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
conn_params.slice!(*valid_conn_param_keys)
- # The postgres drivers don't allow the creation of an unconnected PGconn object,
+ # The postgres drivers don't allow the creation of an unconnected PG::Connection object,
# so just pass a nil connection object for the time being.
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
end
@@ -121,14 +121,6 @@ module ActiveRecord
include PostgreSQL::DatabaseStatements
include PostgreSQL::ColumnDumper
- def schema_creation # :nodoc:
- PostgreSQL::SchemaCreation.new self
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::PostgreSQL.new(self)
- end
-
# Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
@@ -201,8 +193,8 @@ module ActiveRecord
end
def connection_active?
- @connection.status == PGconn::CONNECTION_OK
- rescue PGError
+ @connection.status == PG::CONNECTION_OK
+ rescue PG::Error
false
end
end
@@ -215,7 +207,7 @@ module ActiveRecord
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
- @table_alias_length = nil
+ @max_identifier_length = nil
connect
add_pg_encoders
@@ -247,45 +239,48 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.query "SELECT 1"
+ @lock.synchronize do
+ @connection.query "SELECT 1"
+ end
true
- rescue PGError
+ rescue PG::Error
false
end
# Close then reopen the connection.
def reconnect!
- super
- @connection.reset
- configure_connection
+ @lock.synchronize do
+ super
+ @connection.reset
+ configure_connection
+ end
end
def reset!
- clear_cache!
- reset_transaction
- unless @connection.transaction_status == ::PG::PQTRANS_IDLE
- @connection.query "ROLLBACK"
+ @lock.synchronize do
+ clear_cache!
+ reset_transaction
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
+ @connection.query "ROLLBACK"
+ end
+ @connection.query "DISCARD ALL"
+ configure_connection
end
- @connection.query "DISCARD ALL"
- configure_connection
end
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
- super
- @connection.close rescue nil
+ @lock.synchronize do
+ super
+ @connection.close rescue nil
+ end
end
def native_database_types #:nodoc:
NATIVE_DATABASE_TYPES
end
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations?
- true
- end
-
def set_standard_conforming_strings
execute("SET standard_conforming_strings = on", "SCHEMA")
end
@@ -306,8 +301,8 @@ module ActiveRecord
true
end
- # Range datatypes weren't introduced until PostgreSQL 9.2
def supports_ranges?
+ # Range datatypes weren't introduced until PostgreSQL 9.2
postgresql_version >= 90200
end
@@ -319,6 +314,12 @@ module ActiveRecord
postgresql_version >= 90400
end
+ def supports_alter_constraint?
+ # PostgreSQL 9.4 introduces ALTER TABLE ... ALTER CONSTRAINT but it has a bug and fixed in 9.4.2
+ # https://www.postgresql.org/docs/9.4/static/release-9-4-2.html
+ postgresql_version >= 90402
+ end
+
def get_advisory_lock(lock_id) # :nodoc:
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
@@ -363,8 +364,9 @@ module ActiveRecord
# Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
- @table_alias_length ||= query("SHOW max_identifier_length", "SCHEMA")[0][0].to_i
+ @max_identifier_length ||= select_value("SHOW max_identifier_length", "SCHEMA").to_i
end
+ alias index_name_length table_alias_length
# Set the authorized user for this session
def session_auth=(user)
@@ -376,19 +378,10 @@ module ActiveRecord
@use_insert_returning
end
- def valid_type?(type)
- !native_database_types[type].nil?
- end
-
def update_table_definition(table_name, base) #:nodoc:
PostgreSQL::Table.new(table_name, base)
end
- def lookup_cast_type(sql_type) # :nodoc:
- oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first["oid"].to_i
- super(oid)
- end
-
def column_name_for_operation(operation, node) # :nodoc:
OPERATION_ALIASES.fetch(operation) { operation.downcase }
end
@@ -422,7 +415,7 @@ module ActiveRecord
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
- case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
+ case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
when UNIQUE_VIOLATION
RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
@@ -659,7 +652,7 @@ module ActiveRecord
CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze
def is_cached_plan_failure?(e)
pgerror = e.cause
- code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
rescue
false
@@ -698,7 +691,7 @@ module ActiveRecord
# Connects to a PostgreSQL server and sets up the adapter depending on the
# connected server's characteristics.
def connect
- @connection = PGconn.connect(@connection_parameters)
+ @connection = PG.connect(@connection_parameters)
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
@@ -785,8 +778,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(*args)
- PostgreSQL::TableDefinition.new(*args)
+ def arel_visitor
+ Arel::Visitors::PostgreSQL.new(self)
end
def can_perform_case_insensitive_comparison_for?(column)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
new file mode 100644
index 0000000000..e02491edb6
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -0,0 +1,92 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module SchemaStatements # :nodoc:
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
+ index_sql = select_value(<<-SQL, "SCHEMA")
+ SELECT sql
+ FROM sqlite_master
+ WHERE name = #{quote(row['name'])} AND type = 'index'
+ UNION ALL
+ SELECT sql
+ FROM sqlite_temp_master
+ WHERE name = #{quote(row['name'])} AND type = 'index'
+ SQL
+
+ /\sWHERE\s+(?<where>.+)$/i =~ index_sql
+
+ columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
+ col["name"]
+ end
+
+ IndexDefinition.new(
+ table_name,
+ row["name"],
+ row["unique"] != 0,
+ columns,
+ where: where
+ )
+ end
+ end
+
+ private
+ def schema_creation
+ SQLite3::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ SQLite3::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ default = \
+ case field["dflt_value"]
+ when /^null$/i
+ nil
+ when /^'(.*)'$/m
+ $1.gsub("''", "'")
+ when /^"(.*)"$/m
+ $1.gsub('""', '"')
+ else
+ field["dflt_value"]
+ end
+
+ type_metadata = fetch_type_metadata(field["type"])
+ Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"])
+ end
+
+ def data_source_sql(name = nil, type: nil)
+ scope = quoted_scope(name, type: type)
+ scope[:type] ||= "'table','view'"
+
+ sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
+ sql << " AND name = #{scope[:name]}" if scope[:name]
+ sql << " AND type IN (#{scope[:type]})"
+ sql
+ end
+
+ def quoted_scope(name = nil, type: nil)
+ type = \
+ case type
+ when "BASE TABLE"
+ "'table'"
+ when "VIEW"
+ "'view'"
+ end
+ scope = {}
+ scope[:name] = quote(name) if name
+ scope[:type] = type if type
+ scope
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 16ef195bfc..e2c05ccc4e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -5,6 +5,7 @@ require "active_record/connection_adapters/sqlite3/quoting"
require "active_record/connection_adapters/sqlite3/schema_creation"
require "active_record/connection_adapters/sqlite3/schema_definitions"
require "active_record/connection_adapters/sqlite3/schema_dumper"
+require "active_record/connection_adapters/sqlite3/schema_statements"
gem "sqlite3", "~> 1.3.6"
require "sqlite3"
@@ -55,6 +56,7 @@ module ActiveRecord
include SQLite3::Quoting
include SQLite3::ColumnDumper
+ include SQLite3::SchemaStatements
NATIVE_DATABASE_TYPES = {
primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -82,14 +84,6 @@ module ActiveRecord
SQLite3::Table.new(table_name, base)
end
- def schema_creation # :nodoc:
- SQLite3::SchemaCreation.new self
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::SQLite.new(self)
- end
-
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
@@ -117,11 +111,6 @@ module ActiveRecord
true
end
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations? #:nodoc:
- true
- end
-
def requires_reloading?
true
end
@@ -163,10 +152,6 @@ module ActiveRecord
true
end
- def valid_type?(type)
- true
- end
-
# Returns 62. SQLite supports index names up to 64
# characters. The rest is used by Rails internally to perform
# temporary rename operations
@@ -274,92 +259,6 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def data_sources # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def views # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
- end
-
- def table_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(table_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def data_source_exists?(table_name) # :nodoc:
- return false unless table_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(table_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def view_exists?(view_name) # :nodoc:
- return false unless view_name.present?
-
- sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
- sql << " AND name = #{quote(view_name)}"
-
- select_values(sql, "SCHEMA").any?
- end
-
- def new_column_from_field(table_name, field) # :nondoc:
- case field["dflt_value"]
- when /^null$/i
- field["dflt_value"] = nil
- when /^'(.*)'$/m
- field["dflt_value"] = $1.gsub("''", "'")
- when /^"(.*)"$/m
- field["dflt_value"] = $1.gsub('""', '"')
- end
-
- collation = field["collation"]
- sql_type = field["type"]
- type_metadata = fetch_type_metadata(sql_type)
- new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) #:nodoc:
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
- sql = <<-SQL
- SELECT sql
- FROM sqlite_master
- WHERE name=#{quote(row['name'])} AND type='index'
- UNION ALL
- SELECT sql
- FROM sqlite_temp_master
- WHERE name=#{quote(row['name'])} AND type='index'
- SQL
- index_sql = exec_query(sql).first["sql"]
- match = /\sWHERE\s+(.+)$/i.match(index_sql)
- where = match[1] if match
- IndexDefinition.new(
- table_name,
- row["name"],
- row["unique"] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
- col["name"]
- }, nil, nil, where)
- end
- end
-
def primary_keys(table_name) # :nodoc:
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
@@ -420,11 +319,10 @@ module ActiveRecord
def change_column(table_name, column_name, type, options = {}) #:nodoc:
alter_table(table_name) do |definition|
- include_default = options_include_default?(options)
definition[column_name].instance_eval do
self.type = type
self.limit = options[:limit] if options.include?(:limit)
- self.default = options[:default] if include_default
+ self.default = options[:default] if options.include?(:default)
self.null = options[:null] if options.include?(:null)
self.precision = options[:precision] if options.include?(:precision)
self.scale = options[:scale] if options.include?(:scale)
@@ -545,7 +443,7 @@ module ActiveRecord
end
def sqlite_version
- @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)"))
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value("SELECT sqlite_version(*)"))
end
def translate_exception(exception, message)
@@ -606,16 +504,8 @@ module ActiveRecord
end
end
- def create_table_definition(*args)
- SQLite3::TableDefinition.new(*args)
- end
-
- def extract_foreign_key_action(specifier)
- case specifier
- when "CASCADE"; :cascade
- when "SET NULL"; :nullify
- when "RESTRICT"; :restrict
- end
+ def arel_visitor
+ Arel::Visitors::SQLite.new(self)
end
def configure_connection
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 0028dc0edb..8f78330d4a 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -559,7 +559,6 @@ module ActiveRecord
@marked_for_destruction = false
@destroyed_by_association = nil
@new_record = true
- @txn = nil
@_start_transaction_state = {}
@transaction_state = nil
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index 08d42f3dd4..d9912cfb22 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module DynamicMatchers #:nodoc:
def respond_to_missing?(name, include_private = false)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index e79167d568..c19216702c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -70,13 +70,32 @@ module ActiveRecord
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixture's data may also be accessed by
- # using a special dynamic method, which has the same name as the model, and accepts the
- # name of the fixture to instantiate:
+ # using a special dynamic method, which has the same name as the model.
#
- # test "find" do
+ # Passing in a fixture name to this dynamic method returns the fixture matching this name:
+ #
+ # test "find one" do
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
# end
#
+ # Passing in multiple fixture names returns all fixtures matching these names:
+ #
+ # test "find all by name" do
+ # assert_equal 2, web_sites(:rubyonrails, :google).length
+ # end
+ #
+ # Passing in no arguments returns all fixtures:
+ #
+ # test "find all" do
+ # assert_equal 2, web_sites.length
+ # end
+ #
+ # Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
+ #
+ # test "find by name that does not exist" do
+ # assert_raise(StandardError) { web_sites(:reddit) }
+ # end
+ #
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
# following tests:
#
@@ -909,6 +928,8 @@ module ActiveRecord
define_method(accessor_name) do |*fixture_names|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
+ return_single_record = fixture_names.size == 1
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
@fixture_cache[fs_name] ||= {}
@@ -923,7 +944,7 @@ module ActiveRecord
end
end
- instances.size == 1 ? instances.first : instances
+ return_single_record ? instances.first : instances
end
private accessor_name
end
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index 174f716152..1a937dbcf7 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveRecord
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 2659c60f1f..78ce9f8291 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -47,8 +47,6 @@ module ActiveRecord
# self.locking_column = :lock_person
# end
#
- # Please note that the optimistic locking will be ignored if you update the
- # locking column's value.
module Optimistic
extend ActiveSupport::Concern
@@ -80,13 +78,11 @@ module ActiveRecord
def _update_record(attribute_names = self.attribute_names)
return super unless locking_enabled?
-
- lock_col = self.class.locking_column
-
- return super if attribute_names.include?(lock_col)
return 0 if attribute_names.empty?
begin
+ lock_col = self.class.locking_column
+
previous_lock_value = read_attribute_before_type_cast(lock_col)
increment_lock
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index ea101946f4..2297c77835 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -44,17 +44,17 @@ module ActiveRecord
private
def type_casted_binds(binds, casted_binds)
- casted_binds || binds.map { |attr| type_cast attr.value_for_database }
+ casted_binds || ActiveRecord::Base.connection.type_casted_binds(binds)
end
- def render_bind(attr, type_casted_value)
- value = if attr.type.binary? && attr.value
- "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
- else
- type_casted_value
+ def render_bind(attr, value)
+ if attr.is_a?(Array)
+ attr = attr.first
+ elsif attr.type.binary? && attr.value
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
end
- [attr.name, value]
+ [attr && attr.name, value]
end
def colorize_payload_name(name, payload_name)
@@ -89,10 +89,6 @@ module ActiveRecord
def logger
ActiveRecord::Base.logger
end
-
- def type_cast(value)
- ActiveRecord::Base.connection.type_cast(value)
- end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 40f6226315..4e1df1432c 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -548,12 +548,10 @@ module ActiveRecord
end
def call(env)
- if connection.supports_migrations?
- mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
- if @last_check < mtime
- ActiveRecord::Migration.check_pending!(connection)
- @last_check = mtime
- end
+ mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
+ if @last_check < mtime
+ ActiveRecord::Migration.check_pending!(connection)
+ @last_check = mtime
end
@app.call(env)
end
@@ -1027,10 +1025,11 @@ module ActiveRecord
def schema_migrations_table_name
SchemaMigration.table_name
end
+ deprecate :schema_migrations_table_name
def get_all_versions(connection = Base.connection)
- if connection.table_exists?(schema_migrations_table_name)
- SchemaMigration.all.map { |x| x.version.to_i }.sort
+ if SchemaMigration.table_exists?
+ SchemaMigration.all_versions.map(&:to_i)
else
[]
end
@@ -1058,10 +1057,6 @@ module ActiveRecord
Array(@migrations_paths)
end
- def match_to_migration_filename?(filename) # :nodoc:
- Migration::MigrationFilenameRegexp.match?(File.basename(filename))
- end
-
def parse_migration_filename(filename) # :nodoc:
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
end
@@ -1069,9 +1064,7 @@ module ActiveRecord
def migrations(paths)
paths = Array(paths)
- files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
-
- migrations = files.map do |file|
+ migrations = migration_files(paths).map do |file|
version, name, scope = parse_migration_filename(file)
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
@@ -1083,6 +1076,30 @@ module ActiveRecord
migrations.sort_by(&:version)
end
+ def migrations_status(paths)
+ paths = Array(paths)
+
+ db_list = ActiveRecord::SchemaMigration.normalized_versions
+
+ file_list = migration_files(paths).map do |file|
+ version, name, scope = parse_migration_filename(file)
+ raise IllegalMigrationNameError.new(file) unless version
+ version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
+ status = db_list.delete(version) ? "up" : "down"
+ [status, version, (name + scope).humanize]
+ end.compact
+
+ db_list.map! do |version|
+ ["up", version, "********** NO FILE **********"]
+ end
+
+ (db_list + file_list).sort_by { |_, version, _| version }
+ end
+
+ def migration_files(paths)
+ Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
+ end
+
private
def move(direction, migrations_paths, steps)
@@ -1098,8 +1115,6 @@ module ActiveRecord
end
def initialize(direction, migrations, target_version = nil)
- raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
-
@direction = direction
@target_version = target_version
@migrated_versions = nil
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 85032ce470..188dd0acef 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -11,7 +11,10 @@ module ActiveRecord
const_get(name)
end
- V5_1 = Current
+ V5_2 = Current
+
+ class V5_1 < V5_2
+ end
class V5_0 < V5_1
module TableDefinition
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 2bb7ed6d5e..26966f9433 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -4,7 +4,7 @@ module ActiveRecord
[]
end
- def delete_all(_conditions = nil)
+ def delete_all
0
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 36689f6559..c4a22398f0 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -9,7 +9,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :in_batches, to: :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :merge, to: :all
+ :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
delegate :pluck, :ids, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 0276d41494..73518ca144 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -166,5 +166,13 @@ end_warning
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
end
+
+ initializer "active_record.clear_active_connections" do
+ config.after_initialize do
+ ActiveSupport.on_load(:active_record) do
+ clear_active_connections!
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 246d330b76..f3e2df8786 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -77,6 +77,8 @@ db_namespace = namespace :db do
namespace :migrate do
# desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
task redo: [:environment, :load_config] do
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
+
if ENV["VERSION"]
db_namespace["migrate:down"].invoke
db_namespace["migrate:up"].invoke
@@ -91,16 +93,17 @@ db_namespace = namespace :db do
# desc 'Runs the "up" for a given migration VERSION.'
task up: [:environment, :load_config] do
+ raise "VERSION is required" if ENV["VERSION"] && ENV["VERSION"].empty?
+
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required" unless version
ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
db_namespace["_dump"].invoke
end
# desc 'Runs the "down" for a given migration VERSION.'
task down: [:environment, :load_config] do
+ raise "VERSION is required - To go down one migration, use db:rollback" if ENV["VERSION"] && ENV["VERSION"].empty?
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required - To go down one migration, run db:rollback" unless version
ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
db_namespace["_dump"].invoke
end
@@ -110,28 +113,13 @@ db_namespace = namespace :db do
unless ActiveRecord::SchemaMigration.table_exists?
abort "Schema migrations table does not exist yet."
end
- db_list = ActiveRecord::SchemaMigration.normalized_versions
-
- file_list =
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths.flat_map do |path|
- Dir.foreach(path).map do |file|
- next unless ActiveRecord::Migrator.match_to_migration_filename?(file)
-
- version, name, scope = ActiveRecord::Migrator.parse_migration_filename(file)
- version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
- status = db_list.delete(version) ? "up" : "down"
- [status, version, (name + scope).humanize]
- end.compact
- end
- db_list.map! do |version|
- ["up", version, "********** NO FILE **********"]
- end
# output
puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
- (db_list + file_list).sort_by { |_, version, _| version }.each do |status, version, name|
+ paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
+ ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name|
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
end
puts
@@ -288,8 +276,7 @@ db_namespace = namespace :db do
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
- if ActiveRecord::Base.connection.supports_migrations? &&
- ActiveRecord::SchemaMigration.table_exists?
+ if ActiveRecord::SchemaMigration.table_exists?
File.open(filename, "a") do |f|
f.puts ActiveRecord::Base.connection.dump_schema_information
f.print "\n"
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 61a2279292..24ca8b0be4 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -172,8 +172,8 @@ module ActiveRecord
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
- def join_keys(association_klass)
- JoinKeys.new(foreign_key, active_record_primary_key)
+ def join_keys
+ get_join_keys klass
end
# Returns a list of scopes that should be applied for this Reflection
@@ -187,6 +187,30 @@ module ActiveRecord
end
deprecate :scope_chain
+ def join_scopes(table, predicate_builder) # :nodoc:
+ if scope
+ [ActiveRecord::Relation.create(klass, table, predicate_builder)
+ .instance_exec(&scope)]
+ else
+ []
+ end
+ end
+
+ def klass_join_scope(table, predicate_builder) # :nodoc:
+ if klass.current_scope
+ klass.current_scope.clone.tap { |scope|
+ scope.joins_values = []
+ }
+ else
+ relation = ActiveRecord::Relation.create(
+ klass,
+ table,
+ predicate_builder,
+ )
+ klass.send(:build_default_scope, relation)
+ end
+ end
+
def constraints
chain.map(&:scopes).flatten
end
@@ -260,6 +284,20 @@ module ActiveRecord
def chain
collect_join_chain
end
+
+ def get_join_keys(association_klass)
+ JoinKeys.new(join_pk(association_klass), join_fk)
+ end
+
+ private
+
+ def join_pk(_)
+ foreign_key
+ end
+
+ def join_fk
+ active_record_primary_key
+ end
end
# Base class for AggregateReflection and AssociationReflection. Objects of
@@ -687,11 +725,6 @@ module ActiveRecord
end
end
- def join_keys(association_klass)
- key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
- JoinKeys.new(key, foreign_key)
- end
-
def join_id_for(owner) # :nodoc:
owner[foreign_key]
end
@@ -701,6 +734,14 @@ module ActiveRecord
def calculate_constructable(macro, options)
!polymorphic?
end
+
+ def join_fk
+ foreign_key
+ end
+
+ def join_pk(klass)
+ polymorphic? ? association_primary_key(klass) : association_primary_key
+ end
end
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
@@ -720,7 +761,7 @@ module ActiveRecord
class ThroughReflection < AbstractReflection #:nodoc:
attr_reader :delegate_reflection
delegate :foreign_key, :foreign_type, :association_foreign_key,
- :active_record_primary_key, :type, to: :source_reflection
+ :active_record_primary_key, :type, :get_join_keys, to: :source_reflection
def initialize(delegate_reflection)
@delegate_reflection = delegate_reflection
@@ -806,6 +847,10 @@ module ActiveRecord
source_reflection.scopes + super
end
+ def join_scopes(table, predicate_builder) # :nodoc:
+ source_reflection.join_scopes(table, predicate_builder) + super
+ end
+
def source_type_scope
through_reflection.klass.where(foreign_type => options[:source_type])
end
@@ -816,10 +861,6 @@ module ActiveRecord
through_reflection.has_scope?
end
- def join_keys(association_klass)
- source_reflection.join_keys(association_klass)
- end
-
# A through association is nested if there would be more than one join table
def nested?
source_reflection.through_reflection? || through_reflection.through_reflection?
@@ -954,6 +995,7 @@ module ActiveRecord
end
private
+
def actual_source_reflection # FIXME: this is a horrible name
source_reflection.send(:actual_source_reflection)
end
@@ -990,6 +1032,15 @@ module ActiveRecord
end
end
+ def join_scopes(table, predicate_builder) # :nodoc:
+ scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
+ if @previous_reflection.options[:source_type]
+ scopes + [@previous_reflection.source_type_scope]
+ else
+ scopes
+ end
+ end
+
def klass
@reflection.klass
end
@@ -1006,10 +1057,6 @@ module ActiveRecord
@reflection.plural_name
end
- def join_keys(association_klass)
- @reflection.join_keys(association_klass)
- end
-
def type
@reflection.type
end
@@ -1023,6 +1070,10 @@ module ActiveRecord
source_type = @previous_reflection.options[:source_type]
lambda { |object| where(type => source_type) }
end
+
+ def get_join_keys(association_klass)
+ @reflection.get_join_keys(association_klass)
+ end
end
class RuntimeReflection < PolymorphicReflection # :nodoc:
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 61ee09bcc8..5775eda5a5 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -261,10 +261,6 @@ module ActiveRecord
coder.represent_seq(nil, records)
end
- def as_json(options = nil) #:nodoc:
- records.as_json(options)
- end
-
# Returns size of the records.
def size
loaded? ? @records.length : count(:all)
@@ -639,7 +635,9 @@ module ActiveRecord
end
def inspect
- entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
+ subject = loaded? ? records : self
+ entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
+
entries[10] = "..." if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 35c670f1a1..9cabd1af13 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -37,11 +37,8 @@ module ActiveRecord
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
def count(column_name = nil)
- if block_given?
- to_a.count { |*block_args| yield(*block_args) }
- else
- calculate(:count, column_name)
- end
+ return super() if block_given?
+ calculate(:count, column_name)
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -75,8 +72,8 @@ module ActiveRecord
# #calculate for examples with options.
#
# Person.sum(:age) # => 4562
- def sum(column_name = nil, &block)
- return super(&block) if block_given?
+ def sum(column_name = nil)
+ return super() if block_given?
calculate(:sum, column_name)
end
@@ -232,7 +229,7 @@ module ActiveRecord
query_builder = build_count_subquery(spawn, column_name, distinct)
else
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
- relation = unscope(:order)
+ relation = unscope(:order).distinct!(false)
column = aggregate_column(column_name)
@@ -282,7 +279,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
- select_values += select_values unless having_clause.empty?
+ select_values += self.select_values unless having_clause.empty?
select_values.concat group_columns.map { |aliaz, field|
if field.respond_to?(:as)
@@ -292,7 +289,7 @@ module ActiveRecord
end
}
- relation = except(:group)
+ relation = except(:group).distinct!(false)
relation.group_values = group_fields
relation.select_values = select_values
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index d3ba724507..50378f9d99 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -36,9 +36,9 @@ module ActiveRecord
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
- delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
+ delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join,
:[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
- :to_sentence, :to_formatted_s,
+ :to_sentence, :to_formatted_s, :as_json,
:shuffle, :split, :index, to: :records
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 4548944fe6..a1459c87c6 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -147,7 +147,7 @@ module ActiveRecord
def last(limit = nil)
return find_last(limit) if loaded? || limit_value
- result = limit(limit || 1)
+ result = limit(limit)
result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
result = result.reverse_order!
@@ -312,16 +312,7 @@ module ActiveRecord
relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
return false if ActiveRecord::NullRelation === relation
- relation = relation.except(:select, :distinct).select(ONE_AS_ONE).limit(1)
-
- case conditions
- when Array, Hash
- relation = relation.where(conditions)
- else
- unless conditions == :none
- relation = relation.where(primary_key => conditions)
- end
- end
+ relation = construct_relation_for_exists(relation, conditions)
connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
rescue ::RangeError
@@ -391,6 +382,19 @@ module ActiveRecord
end
end
+ def construct_relation_for_exists(relation, conditions)
+ relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+
+ case conditions
+ when Array, Hash
+ relation.where!(conditions)
+ else
+ relation.where!(primary_key => conditions) unless conditions == :none
+ end
+
+ relation
+ end
+
def construct_join_dependency(joins = [], eager_loading: true)
including = eager_load_values + includes_values
ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading)
@@ -401,8 +405,7 @@ module ActiveRecord
end
def apply_join_dependency(relation, join_dependency)
- relation = relation.except(:includes, :eager_load, :preload)
- relation = relation.joins join_dependency
+ relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency)
if using_limitable_reflections?(join_dependency.reflections)
relation
@@ -430,140 +433,142 @@ module ActiveRecord
reflections.none?(&:collection?)
end
- private
+ def find_with_ids(*ids)
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
- def find_with_ids(*ids)
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
+ expects_array = ids.first.kind_of?(Array)
+ return ids.first if expects_array && ids.first.empty?
- expects_array = ids.first.kind_of?(Array)
- return ids.first if expects_array && ids.first.empty?
+ ids = ids.flatten.compact.uniq
- ids = ids.flatten.compact.uniq
-
- case ids.size
- when 0
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
- when 1
- result = find_one(ids.first)
- expects_array ? [ result ] : result
- else
- find_some(ids)
- end
- rescue ::RangeError
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
+ case ids.size
+ when 0
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
+ when 1
+ result = find_one(ids.first)
+ expects_array ? [ result ] : result
+ else
+ find_some(ids)
end
+ rescue ::RangeError
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
+ end
- def find_one(id)
- if ActiveRecord::Base === id
- raise ArgumentError, <<-MSG.squish
- You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`.
- MSG
- end
-
- relation = where(primary_key => id)
- record = relation.take
-
- raise_record_not_found_exception!(id, 0, 1) unless record
-
- record
+ def find_one(id)
+ if ActiveRecord::Base === id
+ raise ArgumentError, <<-MSG.squish
+ You are passing an instance of ActiveRecord::Base to `find`.
+ Please pass the id of the object by calling `.id`.
+ MSG
end
- def find_some(ids)
- return find_some_ordered(ids) unless order_values.present?
+ relation = where(primary_key => id)
+ record = relation.take
- result = where(primary_key => ids).to_a
+ raise_record_not_found_exception!(id, 0, 1) unless record
- expected_size =
- if limit_value && ids.size > limit_value
- limit_value
- else
- ids.size
- end
+ record
+ end
- # 11 ids with limit 3, offset 9 should give 2 results.
- if offset_value && (ids.size - offset_value < expected_size)
- expected_size = ids.size - offset_value
- end
+ def find_some(ids)
+ return find_some_ordered(ids) unless order_values.present?
+
+ result = where(primary_key => ids).to_a
- if result.size == expected_size
- result
+ expected_size =
+ if limit_value && ids.size > limit_value
+ limit_value
else
- raise_record_not_found_exception!(ids, result.size, expected_size)
+ ids.size
end
+
+ # 11 ids with limit 3, offset 9 should give 2 results.
+ if offset_value && (ids.size - offset_value < expected_size)
+ expected_size = ids.size - offset_value
end
- def find_some_ordered(ids)
- ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
+ if result.size == expected_size
+ result
+ else
+ raise_record_not_found_exception!(ids, result.size, expected_size)
+ end
+ end
- result = except(:limit, :offset).where(primary_key => ids).records
+ def find_some_ordered(ids)
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
- if result.size == ids.size
- pk_type = @klass.type_for_attribute(primary_key)
+ result = except(:limit, :offset).where(primary_key => ids).records
- 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
+ if result.size == ids.size
+ pk_type = @klass.type_for_attribute(primary_key)
- def find_take
- if loaded?
- records.first
- else
- @take ||= limit(1).records.first
- end
+ 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_with_limit(limit)
- if loaded?
- records.take(limit)
- else
- limit(limit).to_a
- end
+ def find_take
+ if loaded?
+ records.first
+ else
+ @take ||= limit(1).records.first
end
+ end
- def find_nth(index)
- @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
+ def find_take_with_limit(limit)
+ if loaded?
+ records.take(limit)
+ else
+ limit(limit).to_a
end
+ end
- def find_nth_with_limit(index, limit)
- if loaded?
- records[index, limit] || []
+ def find_nth(index)
+ @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
+ end
+
+ def find_nth_with_limit(index, limit)
+ if loaded?
+ records[index, limit] || []
+ else
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
else
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
- else
- self
- end
+ self
+ end
+ if limit_value.nil? || index < limit_value
relation = relation.offset(offset_index + index) unless index.zero?
relation.limit(limit).to_a
+ else
+ []
end
end
+ end
- def find_nth_from_last(index)
- if loaded?
- records[-index]
+ def find_nth_from_last(index)
+ if loaded?
+ records[-index]
+ else
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
else
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
- else
- self
- end
-
- relation.to_a[-index]
- # TODO: can be made more performant on large result sets by
- # for instance, last(index)[-index] (which would require
- # refactoring the last(n) finder method to make test suite pass),
- # or by using a combination of reverse_order, limit, and offset,
- # e.g., reverse_order.offset(index-1).first
+ self
end
- end
- def find_last(limit)
- limit ? records.last(limit) : records.last
+ relation.to_a[-index]
+ # TODO: can be made more performant on large result sets by
+ # for instance, last(index)[-index] (which would require
+ # refactoring the last(n) finder method to make test suite pass),
+ # or by using a combination of reverse_order, limit, and offset,
+ # e.g., reverse_order.offset(index-1).first
end
+ end
+
+ def find_last(limit)
+ limit ? records.last(limit) : records.last
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 18ae10a652..183fe91c05 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,13 +1,14 @@
+require "active_record/relation/predicate_builder/array_handler"
+require "active_record/relation/predicate_builder/base_handler"
+require "active_record/relation/predicate_builder/basic_object_handler"
+require "active_record/relation/predicate_builder/range_handler"
+require "active_record/relation/predicate_builder/relation_handler"
+
+require "active_record/relation/predicate_builder/association_query_value"
+require "active_record/relation/predicate_builder/polymorphic_array_value"
+
module ActiveRecord
class PredicateBuilder # :nodoc:
- require "active_record/relation/predicate_builder/array_handler"
- require "active_record/relation/predicate_builder/association_query_handler"
- require "active_record/relation/predicate_builder/base_handler"
- require "active_record/relation/predicate_builder/basic_object_handler"
- require "active_record/relation/predicate_builder/polymorphic_array_handler"
- require "active_record/relation/predicate_builder/range_handler"
- require "active_record/relation/predicate_builder/relation_handler"
-
delegate :resolve_column_aliases, to: :table
def initialize(table)
@@ -20,8 +21,6 @@ module ActiveRecord
register_handler(RangeHandler::RangeWithBinds, RangeHandler.new)
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
- register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
- register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
end
def build_from_hash(attributes)
@@ -92,9 +91,27 @@ module ActiveRecord
attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
result[column_name] = attrs
binds += bvs
- next
- when value.is_a?(Relation)
- binds += value.bound_attributes
+ when table.associated_with?(column_name)
+ # Find the foreign key when using queries such as:
+ # Post.where(author: author)
+ #
+ # For polymorphic relationships, find the foreign key and type:
+ # PriceEstimate.where(estimate_of: treasure)
+ associated_table = table.associated_table(column_name)
+ if associated_table.polymorphic_association?
+ case value.is_a?(Array) ? value.first : value
+ when Base, Relation
+ value = [value] unless value.is_a?(Array)
+ klass = PolymorphicArrayValue
+ end
+ end
+
+ klass ||= AssociationQueryValue
+ result[column_name] = klass.new(associated_table, value).queries.map do |query|
+ attrs, bvs = create_binds_for_hash(query)
+ binds.concat(bvs)
+ attrs
+ end
when value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype)
first = value.begin
last = value.end
@@ -112,17 +129,10 @@ module ActiveRecord
if can_be_bound?(column_name, value)
result[column_name] = Arel::Nodes::BindParam.new
binds << build_bind_param(column_name, value)
+ elsif value.is_a?(Relation)
+ binds.concat(value.bound_attributes)
end
end
-
- # Find the foreign key when using queries such as:
- # Post.where(author: author)
- #
- # For polymorphic relationships, find the foreign key and type:
- # PriceEstimate.where(estimate_of: treasure)
- if table.associated_with?(column_name)
- result[column_name] = AssociationQueryHandler.value_for(table, column_name, value)
- end
end
[result, binds]
@@ -155,7 +165,6 @@ module ActiveRecord
end
def can_be_bound?(column_name, value)
- return if table.associated_with?(column_name)
case value
when Array, Range
table.type(column_name).respond_to?(:subtype)
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 88b6c37d43..1068e700e2 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -6,11 +6,11 @@ module ActiveRecord
end
def call(attribute, value)
+ return attribute.in([]) if value.empty?
+ return queries_predicates(value) if value.all? { |v| v.is_a?(Hash) }
+
values = value.map { |x| x.is_a?(Base) ? x.id : x }
nils, values = values.partition(&:nil?)
-
- return attribute.in([]) if values.empty? && nils.empty?
-
ranges, values = values.partition { |v| v.is_a?(Range) }
values_predicate =
@@ -26,7 +26,7 @@ module ActiveRecord
array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
array_predicates.unshift(values_predicate)
- array_predicates.inject { |composite, predicate| composite.or(predicate) }
+ array_predicates.inject(&:or)
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
@@ -40,6 +40,17 @@ module ActiveRecord
other
end
end
+
+ private
+ def queries_predicates(queries)
+ if queries.size > 1
+ queries.map do |query|
+ Arel::Nodes::And.new(predicate_builder.build_from_hash(query))
+ end.inject(&:or)
+ else
+ predicate_builder.build_from_hash(queries.first)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
deleted file mode 100644
index 29860ec677..0000000000
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-module ActiveRecord
- class PredicateBuilder
- class AssociationQueryHandler # :nodoc:
- def self.value_for(table, column, value)
- associated_table = table.associated_table(column)
- klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base)
- PolymorphicArrayValue
- else
- AssociationQueryValue
- end
-
- klass.new(associated_table, value)
- end
-
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
- def call(attribute, value)
- queries = {}
-
- table = value.associated_table
- if value.base_class
- queries[table.association_foreign_type.to_s] = value.base_class.name
- end
-
- queries[table.association_foreign_key.to_s] = value.ids
- predicate_builder.build_from_hash(queries)
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :predicate_builder
- end
-
- class AssociationQueryValue # :nodoc:
- attr_reader :associated_table, :value
-
- def initialize(associated_table, value)
- @associated_table = associated_table
- @value = value
- end
-
- def ids
- case value
- when Relation
- value.select(primary_key)
- when Array
- value.map { |v| convert_to_id(v) }
- else
- convert_to_id(value)
- end
- end
-
- def base_class
- if associated_table.polymorphic_association?
- @base_class ||= polymorphic_base_class_from_value
- end
- end
-
- private
-
- def primary_key
- associated_table.association_primary_key(base_class)
- end
-
- def polymorphic_base_class_from_value
- case value
- when Relation
- value.klass.base_class
- when Base
- value.class.base_class
- end
- end
-
- def convert_to_id(value)
- case value
- when Base
- value._read_attribute(primary_key)
- else
- value
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
new file mode 100644
index 0000000000..2fe0f81cab
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
@@ -0,0 +1,41 @@
+module ActiveRecord
+ class PredicateBuilder
+ class AssociationQueryValue # :nodoc:
+ attr_reader :associated_table, :value
+
+ def initialize(associated_table, value)
+ @associated_table = associated_table
+ @value = value
+ end
+
+ def queries
+ [associated_table.association_foreign_key.to_s => ids]
+ end
+
+ private
+ def ids
+ case value
+ when Relation
+ value.select_values.empty? ? value.select(primary_key) : value
+ when Array
+ value.map { |v| convert_to_id(v) }
+ else
+ convert_to_id(value)
+ end
+ end
+
+ def primary_key
+ associated_table.association_primary_key
+ end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key)
+ else
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
deleted file mode 100644
index 335124c952..0000000000
--- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module ActiveRecord
- class PredicateBuilder
- class PolymorphicArrayHandler # :nodoc:
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
- def call(attribute, value)
- table = value.associated_table
- queries = value.type_to_ids_mapping.map do |type, ids|
- { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
- end
-
- predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
-
- if predicates.size > 1
- type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
- type_and_ids_predicates.inject(&:or)
- else
- predicates.first
- end
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :predicate_builder
- end
-
- class PolymorphicArrayValue # :nodoc:
- attr_reader :associated_table, :values
-
- def initialize(associated_table, values)
- @associated_table = associated_table
- @values = values
- end
-
- def type_to_ids_mapping
- default_hash = Hash.new { |hsh, key| hsh[key] = [] }
- values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
- end
-
- private
-
- def primary_key(value)
- associated_table.association_primary_key(base_class(value))
- end
-
- def base_class(value)
- value.class.base_class
- end
-
- def convert_to_id(value)
- value._read_attribute(primary_key(value))
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
new file mode 100644
index 0000000000..9bb2f8c8dc
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
@@ -0,0 +1,49 @@
+module ActiveRecord
+ class PredicateBuilder
+ class PolymorphicArrayValue # :nodoc:
+ attr_reader :associated_table, :values
+
+ def initialize(associated_table, values)
+ @associated_table = associated_table
+ @values = values
+ end
+
+ def queries
+ type_to_ids_mapping.map do |type, ids|
+ {
+ associated_table.association_foreign_type.to_s => type,
+ associated_table.association_foreign_key.to_s => ids.size > 1 ? ids : ids.first
+ }
+ end
+ end
+
+ private
+ def type_to_ids_mapping
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
+ end
+
+ def primary_key(value)
+ associated_table.association_primary_key(base_class(value))
+ end
+
+ def base_class(value)
+ case value
+ when Base
+ value.class.base_class
+ when Relation
+ value.klass.base_class
+ end
+ end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key(value))
+ when Relation
+ value.select(primary_key(value))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4ee413c805..1178dec706 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1130,7 +1130,12 @@ module ActiveRecord
arel_attribute(arg).asc
when Hash
arg.map { |field, dir|
- arel_attribute(field).send(dir.downcase)
+ case field
+ when Arel::Nodes::SqlLiteral
+ field.send(dir.downcase)
+ else
+ arel_attribute(field).send(dir.downcase)
+ end
}
else
arg
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 417b24c7bb..119910ee79 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -148,10 +148,10 @@ module ActiveRecord
(binds_index...(binds_index + binds_contains)).each do |i|
except_binds[i] = true
end
-
- binds_index += binds_contains
end
+ binds_index += binds_contains if binds_contains
+
except
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 15533f0151..94d63765c9 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -47,9 +47,18 @@ module ActiveRecord
@options = options
end
- def header(stream)
- define_params = @version ? "version: #{@version}" : ""
+ # turns 20170404131909 into "2017_04_04_131909"
+ def formatted_version
+ stringified = @version.to_s
+ return stringified unless stringified.length == 14
+ stringified.insert(4, "_").insert(7, "_").insert(10, "_")
+ end
+ def define_params
+ @version ? "version: #{formatted_version}" : ""
+ end
+
+ def header(stream)
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
@@ -85,7 +94,7 @@ HEADER
end
def tables(stream)
- sorted_tables = @connection.data_sources.sort - @connection.views
+ sorted_tables = @connection.tables.sort
sorted_tables.each do |table_name|
table(table_name, stream) unless ignored?(table_name)
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 5efbcff96a..f59737afb0 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -39,7 +39,11 @@ module ActiveRecord
end
def normalized_versions
- pluck(:version).map { |v| normalize_migration_number v }
+ all_versions.map { |v| normalize_migration_number v }
+ end
+
+ def all_versions
+ order(:version).pluck(:version)
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 82604a915f..46fa8a70a3 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -162,9 +162,11 @@ module ActiveRecord
end
def migrate
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
+
verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- scope = ENV["SCOPE"]
+ scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose
Migrator.migrate(migrations_paths, version) do |migration|
scope.blank? || scope == migration.scope
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 5155ced0e2..f1af90c1e8 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -1,8 +1,11 @@
+require "tempfile"
+
module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze
+ SQL_COMMENT_BEGIN = "--".freeze
delegate :connection, :establish_connection, :clear_active_connections!,
to: ActiveRecord::Base
@@ -65,6 +68,7 @@ module ActiveRecord
end
args << configuration["database"]
run_cmd("pg_dump", args, "dumping")
+ remove_sql_header_comments(filename)
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
end
@@ -110,6 +114,22 @@ module ActiveRecord
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
msg
end
+
+ def remove_sql_header_comments(filename)
+ removing_comments = true
+ tempfile = Tempfile.open("uncommented_structure.sql")
+ begin
+ File.foreach(filename) do |line|
+ unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
+ tempfile << line
+ removing_comments = false
+ end
+ end
+ ensure
+ tempfile.close
+ end
+ FileUtils.mv(tempfile.path, filename)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 08417aaa0f..45795fa287 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -123,7 +123,7 @@ module ActiveRecord
# # statement will cause a PostgreSQL error, even though the unique
# # constraint is no longer violated:
# Number.create(i: 1)
- # # => "PGError: ERROR: current transaction is aborted, commands
+ # # => "PG::Error: ERROR: current transaction is aborted, commands
# # ignored until end of transaction block"
# end
#
@@ -283,7 +283,7 @@ module ActiveRecord
fire_on = Array(options[:on])
assert_valid_transaction_action(fire_on)
options[:if] = Array(options[:if])
- options[:if] << "transaction_include_any_action?(#{fire_on})"
+ options[:if].unshift("transaction_include_any_action?(#{fire_on})")
end
end
diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb
index 7ce33e9cd3..53a5e205da 100644
--- a/activerecord/lib/active_record/type/decimal_without_scale.rb
+++ b/activerecord/lib/active_record/type/decimal_without_scale.rb
@@ -4,6 +4,10 @@ module ActiveRecord
def type
:decimal
end
+
+ def type_cast_for_schema(value)
+ value.to_s.inspect
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 6af05c1860..edbd20a6c1 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,6 +1,8 @@
module ActiveRecord
module Type
class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
+ undef to_yaml if method_defined?(:to_yaml)
+
include ActiveModel::Type::Helpers::Mutable
attr_reader :subtype, :coder
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
index 43075077b9..47c0981a49 100644
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
def db_migrate_path
- if defined?(Rails) && Rails.application
+ if defined?(Rails.application) && Rails.application
Rails.application.config.paths["db/migrate"].to_ary.first
else
"db/migrate"
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0c9d1dff9d..601d575c0e 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -30,6 +30,16 @@ module ActiveRecord
assert_nothing_raised { Book.destroy(0) }
end
+ def test_valid_column
+ @connection.native_database_types.each_key do |type|
+ assert @connection.valid_type?(type)
+ end
+ end
+
+ def test_invalid_column
+ assert_not @connection.valid_type?(:foobar)
+ end
+
def test_tables
tables = @connection.tables
assert_includes tables, "accounts"
@@ -206,6 +216,15 @@ module ActiveRecord
assert result.is_a?(ActiveRecord::Result)
end
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_select_all_with_legacy_binds
+ post = Post.create!(title: "foo", body: "bar")
+ expected = @connection.select_all("SELECT * FROM posts WHERE id = #{post.id}")
+ result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new.to_sql}", nil, [[nil, post.id]])
+ assert_equal expected.to_hash, result.to_hash
+ end
+ end
+
def test_select_methods_passing_a_association_relation
author = Author.create!(name: "john")
Post.create!(author: author, title: "foo", body: "bar")
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 2fa39282fb..58698d59db 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -38,7 +38,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal :string, string_column.type
end
- test "test type casting with emulated booleans" do
+ test "type casting with emulated booleans" do
emulate_booleans true
boolean = BooleanType.create!(archived: true, published: true)
@@ -55,7 +55,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal 0, @connection.type_cast(false)
end
- test "test type casting without emulated booleans" do
+ test "type casting without emulated booleans" do
emulate_booleans false
boolean = BooleanType.create!(archived: true, published: true)
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index ae9ea1c573..a2faf43b0d 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -42,7 +42,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
@connection.update("set @@wait_timeout=1")
sleep 2
assert !@connection.active?
-
+ ensure
# Repair all fixture connections so other tests won't break.
@fixture_connections.each(&:verify!)
end
@@ -63,6 +63,18 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
assert @connection.active?
end
+ def test_verify_with_args_is_deprecated
+ assert_deprecated do
+ @connection.verify!(option: true)
+ end
+ assert_deprecated do
+ @connection.verify!([])
+ end
+ assert_deprecated do
+ @connection.verify!({})
+ end
+ end
+
def test_execute_after_disconnect
@connection.disconnect!
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index aab3dcb724..565130c38f 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -17,17 +17,6 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
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_columns_for_distinct_zero_orders
assert_equal "posts.id",
@conn.columns_for_distinct("posts.id", [])
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 505c297cd4..99175e8091 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -89,6 +89,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
Thread.new do
other_conn = ActiveRecord::Base.connection
other_conn.execute("SET standard_conforming_strings = off")
+ other_conn.execute("SET escape_string_warning = off")
end.join
test_via_to_sql
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 3cbd4ca212..c52d9e37cc 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -105,7 +105,7 @@ module ActiveRecord
end
def test_table_alias_length_logs_name
- @connection.instance_variable_set("@table_alias_length", nil)
+ @connection.instance_variable_set("@max_identifier_length", nil)
@connection.table_alias_length
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
@@ -177,7 +177,7 @@ module ActiveRecord
assert_not_equal original_connection_pid, new_connection_pid,
"umm -- looks like you didn't break the connection, because we're still " \
"successfully querying with the same connection pid."
-
+ ensure
# Repair all fixture connections so other tests won't break.
@fixture_connections.each(&:verify!)
end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 93558ac4d2..d4e627001c 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -16,6 +16,7 @@ module PostgresqlJSONSharedTestCases
@connection.create_table("json_data_type") do |t|
t.public_send column_type, "payload", default: {} # t.json 'payload', default: {}
t.public_send column_type, "settings" # t.json 'settings'
+ t.public_send column_type, "objects", array: true # t.json 'objects', array: true
end
rescue ActiveRecord::StatementInvalid
skip "do not test on PostgreSQL without #{column_type} type."
@@ -75,6 +76,15 @@ module PostgresqlJSONSharedTestCases
assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
end
+ def test_deserialize_with_array
+ x = JsonDataType.new(objects: ["foo" => "bar"])
+ assert_equal ["foo" => "bar"], x.objects
+ x.save!
+ assert_equal ["foo" => "bar"], x.objects
+ x.reload
+ assert_equal ["foo" => "bar"], x.objects
+ end
+
def test_type_cast_json
type = JsonDataType.type_for_attribute("payload")
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 3054f0271f..003e6e62e7 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -21,17 +21,6 @@ module ActiveRecord
end
end
- def test_valid_column
- with_example_table do
- column = @connection.columns("ex").find { |col| col.name == "id" }
- assert @connection.valid_type?(column.type)
- end
- end
-
- def test_invalid_column
- assert_not @connection.valid_type?(:foobar)
- end
-
def test_primary_key
with_example_table do
assert_equal "id", @connection.primary_key("ex")
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index 0ff04bfa27..c5c540cebc 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -1,111 +1,150 @@
require "cases/helper"
require "support/connection_helper"
-class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
- self.use_transactional_tests = false
+if ActiveRecord::Base.connection.respond_to?(:supports_alter_constraint?) &&
+ ActiveRecord::Base.connection.supports_alter_constraint?
+ class PostgreSQLReferentialIntegrityWithAlterConstraintTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
- include ConnectionHelper
+ include ConnectionHelper
- IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql|
- sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/)
- end
+ IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql|
+ sql.match(/SET CONSTRAINTS ALL DEFERRED/)
+ end
- module MissingSuperuserPrivileges
- def execute(sql)
- if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
- super "BROKEN;" rescue nil # put transaction in broken state
- raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege"
- else
- super
+ module ProgrammerMistake
+ def execute(sql)
+ if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
+ raise ArgumentError, "something is not right."
+ else
+ super
+ end
end
end
- end
- module ProgrammerMistake
- def execute(sql)
- if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
- raise ArgumentError, "something is not right."
- else
- super
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ reset_connection
+ end
+
+ def test_errors_bubble_up
+ @connection.extend ProgrammerMistake
+
+ assert_raises ArgumentError do
+ @connection.disable_referential_integrity {}
end
end
end
+else
+ class PostgreSQLReferentialIntegrityWithDisableTriggerTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
- def setup
- @connection = ActiveRecord::Base.connection
- end
+ include ConnectionHelper
- def teardown
- reset_connection
- if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges)
- raise "MissingSuperuserPrivileges patch was not removed"
+ IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql|
+ sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/)
end
- end
- def test_should_reraise_invalid_foreign_key_exception_and_show_warning
- @connection.extend MissingSuperuserPrivileges
+ module MissingSuperuserPrivileges
+ def execute(sql)
+ if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
+ super "BROKEN;" rescue nil # put transaction in broken state
+ raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege"
+ else
+ super
+ end
+ end
+ end
- warning = capture(:stderr) do
- e = assert_raises(ActiveRecord::InvalidForeignKey) do
- @connection.disable_referential_integrity do
- raise ActiveRecord::InvalidForeignKey, "Should be re-raised"
+ module ProgrammerMistake
+ def execute(sql)
+ if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
+ raise ArgumentError, "something is not right."
+ else
+ super
end
end
- assert_equal "Should be re-raised", e.message
end
- assert_match (/WARNING: Rails was not able to disable referential integrity/), warning
- assert_match (/cause: PG::InsufficientPrivilege/), warning
- end
- def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised
- @connection.extend MissingSuperuserPrivileges
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
- warning = capture(:stderr) do
- e = assert_raises(ActiveRecord::StatementInvalid) do
- @connection.disable_referential_integrity do
- raise ActiveRecord::StatementInvalid, "Should be re-raised"
+ def teardown
+ reset_connection
+ if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges)
+ raise "MissingSuperuserPrivileges patch was not removed"
+ end
+ end
+
+ def test_should_reraise_invalid_foreign_key_exception_and_show_warning
+ @connection.extend MissingSuperuserPrivileges
+
+ warning = capture(:stderr) do
+ e = assert_raises(ActiveRecord::InvalidForeignKey) do
+ @connection.disable_referential_integrity do
+ raise ActiveRecord::InvalidForeignKey, "Should be re-raised"
+ end
end
+ assert_equal "Should be re-raised", e.message
end
- assert_equal "Should be re-raised", e.message
+ assert_match (/WARNING: Rails was not able to disable referential integrity/), warning
+ assert_match (/cause: PG::InsufficientPrivilege/), warning
end
- assert warning.blank?, "expected no warnings but got:\n#{warning}"
- end
- def test_does_not_break_transactions
- @connection.extend MissingSuperuserPrivileges
+ def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised
+ @connection.extend MissingSuperuserPrivileges
- @connection.transaction do
- @connection.disable_referential_integrity do
- assert_transaction_is_not_broken
+ warning = capture(:stderr) do
+ e = assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.disable_referential_integrity do
+ raise ActiveRecord::StatementInvalid, "Should be re-raised"
+ end
+ end
+ assert_equal "Should be re-raised", e.message
end
- assert_transaction_is_not_broken
+ assert warning.blank?, "expected no warnings but got:\n#{warning}"
end
- end
- def test_does_not_break_nested_transactions
- @connection.extend MissingSuperuserPrivileges
+ def test_does_not_break_transactions
+ @connection.extend MissingSuperuserPrivileges
- @connection.transaction do
- @connection.transaction(requires_new: true) do
+ @connection.transaction do
@connection.disable_referential_integrity do
assert_transaction_is_not_broken
end
+ assert_transaction_is_not_broken
end
- assert_transaction_is_not_broken
end
- end
- def test_only_catch_active_record_errors_others_bubble_up
- @connection.extend ProgrammerMistake
+ def test_does_not_break_nested_transactions
+ @connection.extend MissingSuperuserPrivileges
- assert_raises ArgumentError do
- @connection.disable_referential_integrity {}
+ @connection.transaction do
+ @connection.transaction(requires_new: true) do
+ @connection.disable_referential_integrity do
+ assert_transaction_is_not_broken
+ end
+ end
+ assert_transaction_is_not_broken
+ end
end
- end
- private
+ def test_only_catch_active_record_errors_others_bubble_up
+ @connection.extend ProgrammerMistake
- def assert_transaction_is_not_broken
- assert_equal 1, @connection.select_value("SELECT 1")
+ assert_raises ArgumentError do
+ @connection.disable_referential_integrity {}
+ end
end
+
+ private
+
+ def assert_transaction_is_not_broken
+ assert_equal 1, @connection.select_value("SELECT 1")
+ end
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 7b065ff320..75e30e4fe9 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -91,6 +91,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
+ @connection.execute "CREATE TABLE #{SCHEMA2_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
@connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
end
@@ -361,7 +362,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
def test_primary_key_assuming_schema_search_path
- with_schema_search_path(SCHEMA_NAME) do
+ with_schema_search_path("#{SCHEMA_NAME}, #{SCHEMA2_NAME}") do
assert_equal "id", @connection.primary_key(PK_TABLE_NAME), "primary key should be found"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
index eb9978a898..146b619a4b 100644
--- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
@@ -3,13 +3,13 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
- class InactivePGconn
+ class InactivePgConnection
def query(*args)
- raise PGError
+ raise PG::Error
end
def status
- PGconn::CONNECTION_BAD
+ PG::CONNECTION_BAD
end
end
@@ -31,7 +31,7 @@ module ActiveRecord
end
def test_dealloc_does_not_raise_on_inactive_connection
- cache = StatementPool.new InactivePGconn.new, 10
+ cache = StatementPool.new InactivePgConnection.new, 10
cache["foo"] = "bar"
assert_nothing_raised { cache.clear }
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 6aa6a79705..52e4a38cae 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -13,6 +13,10 @@ module PostgresqlUUIDHelper
def uuid_function
connection.supports_pgcrypto_uuid? ? "gen_random_uuid()" : "uuid_generate_v4()"
end
+
+ def uuid_default
+ connection.supports_pgcrypto_uuid? ? {} : { default: uuid_function }
+ end
end
class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
@@ -178,7 +182,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
t.uuid "other_uuid_2", default: "my_uuid_generator()"
end
- connection.create_table("pg_uuids_3", id: :uuid) do |t|
+ connection.create_table("pg_uuids_3", id: :uuid, **uuid_default) do |t|
t.string "name"
end
end
@@ -320,10 +324,10 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
setup do
connection.transaction do
- connection.create_table("pg_uuid_posts", id: :uuid) do |t|
+ connection.create_table("pg_uuid_posts", id: :uuid, **uuid_default) do |t|
t.string "title"
end
- connection.create_table("pg_uuid_comments", id: :uuid) do |t|
+ connection.create_table("pg_uuid_comments", id: :uuid, **uuid_default) do |t|
t.references :uuid_post, type: :uuid
t.string "content"
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index a6afb7816b..2179d1294c 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -49,22 +49,6 @@ module ActiveRecord
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
-
- # sqlite3 databases should be able to support any type and not just the
- # ones mentioned in the native_database_types.
- #
- # Therefore test_invalid column should always return true even if the
- # type is not valid.
- def test_invalid_column
- assert @conn.valid_type?(:foobar)
- end
-
def test_column_types
owner = Owner.create!(name: "hello".encode("ascii-8bit"))
owner.reload
@@ -276,8 +260,7 @@ module ActiveRecord
def test_tables_logs_name
sql = <<-SQL
- SELECT name FROM sqlite_master
- WHERE type = 'table' AND name <> 'sqlite_sequence'
+ SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
@conn.tables
@@ -295,8 +278,7 @@ module ActiveRecord
def test_table_exists_logs_name
with_example_table do
sql = <<-SQL
- SELECT name FROM sqlite_master
- WHERE type = 'table' AND name <> 'sqlite_sequence' AND name = 'ex'
+ SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND name = 'ex' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
assert @conn.table_exists?("ex")
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 397ac599b9..5b608d8e83 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -1,146 +1,143 @@
require "cases/helper"
-if ActiveRecord::Base.connection.supports_migrations?
+class ActiveRecordSchemaTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ setup do
+ @original_verbose = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+ @connection = ActiveRecord::Base.connection
+ ActiveRecord::SchemaMigration.drop_table
+ end
- class ActiveRecordSchemaTest < ActiveRecord::TestCase
- self.use_transactional_tests = false
+ teardown do
+ @connection.drop_table :fruits rescue nil
+ @connection.drop_table :nep_fruits rescue nil
+ @connection.drop_table :nep_schema_migrations rescue nil
+ @connection.drop_table :has_timestamps rescue nil
+ @connection.drop_table :multiple_indexes rescue nil
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ ActiveRecord::Migration.verbose = @original_verbose
+ end
- setup do
- @original_verbose = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
- @connection = ActiveRecord::Base.connection
- ActiveRecord::SchemaMigration.drop_table
- end
+ def test_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_equal "version", ActiveRecord::SchemaMigration.primary_key
- teardown do
- @connection.drop_table :fruits rescue nil
- @connection.drop_table :nep_fruits rescue nil
- @connection.drop_table :nep_schema_migrations rescue nil
- @connection.drop_table :has_timestamps rescue nil
- @connection.drop_table :multiple_indexes rescue nil
- ActiveRecord::SchemaMigration.delete_all rescue nil
- ActiveRecord::Migration.verbose = @original_verbose
+ ActiveRecord::SchemaMigration.create_table
+ assert_difference "ActiveRecord::SchemaMigration.count", 1 do
+ ActiveRecord::SchemaMigration.create version: 12
end
+ ensure
+ ActiveRecord::SchemaMigration.drop_table
+ ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type
+ end
- def test_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_equal "version", ActiveRecord::SchemaMigration.primary_key
-
- ActiveRecord::SchemaMigration.create_table
- assert_difference "ActiveRecord::SchemaMigration.count", 1 do
- ActiveRecord::SchemaMigration.create version: 12
+ def test_schema_define
+ ActiveRecord::Schema.define(version: 7) do
+ create_table :fruits do |t|
+ t.column :color, :string
+ t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
+ t.column :texture, :string
+ t.column :flavor, :string
end
- ensure
- ActiveRecord::SchemaMigration.drop_table
- ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type
end
- def test_schema_define
- ActiveRecord::Schema.define(version: 7) do
- create_table :fruits do |t|
- t.column :color, :string
- t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
- t.column :texture, :string
- t.column :flavor, :string
- end
- end
-
- assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
- assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
- assert_equal 7, ActiveRecord::Migrator::current_version
- end
+ assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
+ assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
+ assert_equal 7, ActiveRecord::Migrator::current_version
+ end
- def test_schema_define_w_table_name_prefix
- table_name = ActiveRecord::SchemaMigration.table_name
- old_table_name_prefix = ActiveRecord::Base.table_name_prefix
- ActiveRecord::Base.table_name_prefix = "nep_"
- ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}"
- ActiveRecord::Schema.define(version: 7) do
- create_table :fruits do |t|
- t.column :color, :string
- t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
- t.column :texture, :string
- t.column :flavor, :string
- end
+ def test_schema_define_w_table_name_prefix
+ table_name = ActiveRecord::SchemaMigration.table_name
+ old_table_name_prefix = ActiveRecord::Base.table_name_prefix
+ ActiveRecord::Base.table_name_prefix = "nep_"
+ ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}"
+ ActiveRecord::Schema.define(version: 7) do
+ create_table :fruits do |t|
+ t.column :color, :string
+ t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
+ t.column :texture, :string
+ t.column :flavor, :string
end
- assert_equal 7, ActiveRecord::Migrator::current_version
- ensure
- ActiveRecord::Base.table_name_prefix = old_table_name_prefix
- ActiveRecord::SchemaMigration.table_name = table_name
end
+ assert_equal 7, ActiveRecord::Migrator::current_version
+ ensure
+ ActiveRecord::Base.table_name_prefix = old_table_name_prefix
+ ActiveRecord::SchemaMigration.table_name = table_name
+ end
- def test_schema_raises_an_error_for_invalid_column_type
- assert_raise NoMethodError do
- ActiveRecord::Schema.define(version: 8) do
- create_table :vegetables do |t|
- t.unknown :color
- end
+ def test_schema_raises_an_error_for_invalid_column_type
+ assert_raise NoMethodError do
+ ActiveRecord::Schema.define(version: 8) do
+ create_table :vegetables do |t|
+ t.unknown :color
end
end
end
+ end
- def test_schema_subclass
- Class.new(ActiveRecord::Schema).define(version: 9) do
- create_table :fruits
- end
- assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
+ def test_schema_subclass
+ Class.new(ActiveRecord::Schema).define(version: 9) do
+ create_table :fruits
end
+ assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
+ end
- def test_normalize_version
- assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118")
- assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2")
- assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017")
- assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947")
- end
+ def test_normalize_version
+ assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118")
+ assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2")
+ assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017")
+ assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947")
+ end
- def test_schema_load_with_multiple_indexes_for_column_of_different_names
- ActiveRecord::Schema.define do
- create_table :multiple_indexes do |t|
- t.string "foo"
- t.index ["foo"], name: "multiple_indexes_foo_1"
- t.index ["foo"], name: "multiple_indexes_foo_2"
- end
+ def test_schema_load_with_multiple_indexes_for_column_of_different_names
+ ActiveRecord::Schema.define do
+ create_table :multiple_indexes do |t|
+ t.string "foo"
+ t.index ["foo"], name: "multiple_indexes_foo_1"
+ t.index ["foo"], name: "multiple_indexes_foo_2"
end
+ end
- indexes = @connection.indexes("multiple_indexes")
+ indexes = @connection.indexes("multiple_indexes")
- assert_equal 2, indexes.length
- assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort
- end
+ assert_equal 2, indexes.length
+ assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort
+ end
- def test_timestamps_without_null_set_null_to_false_on_create_table
- ActiveRecord::Schema.define do
- create_table :has_timestamps do |t|
- t.timestamps
- end
+ def test_timestamps_without_null_set_null_to_false_on_create_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps do |t|
+ t.timestamps
end
-
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
end
- def test_timestamps_without_null_set_null_to_false_on_change_table
- ActiveRecord::Schema.define do
- create_table :has_timestamps
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ end
- change_table :has_timestamps do |t|
- t.timestamps default: Time.now
- end
- end
+ def test_timestamps_without_null_set_null_to_false_on_change_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ change_table :has_timestamps do |t|
+ t.timestamps default: Time.now
+ end
end
- def test_timestamps_without_null_set_null_to_false_on_add_timestamps
- ActiveRecord::Schema.define do
- create_table :has_timestamps
- add_timestamps :has_timestamps, default: Time.now
- end
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ end
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ def test_timestamps_without_null_set_null_to_false_on_add_timestamps
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
+ add_timestamps :has_timestamps, default: Time.now
end
+
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 5875a1871f..5b08ba1358 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -116,6 +116,26 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
ActiveRecord::Base.belongs_to_required_by_default = original_value
end
+ def test_default
+ david = developers(:david)
+ jamis = developers(:jamis)
+
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "ships"
+ def self.name; "Temp"; end
+ belongs_to :developer, default: -> { david }
+ end
+
+ ship = model.create!
+ assert_equal david, ship.developer
+
+ ship = model.create!(developer: jamis)
+ assert_equal jamis, ship.developer
+
+ ship.update!(developer: nil)
+ assert_equal david, ship.developer
+ end
+
def test_default_scope_on_relations_is_not_cached
counter = 0
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 5fd2411f6f..7721bd5cd9 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -7,7 +7,7 @@ require "models/computer"
require "models/company"
class AssociationCallbacksTest < ActiveRecord::TestCase
- fixtures :posts, :authors, :projects, :developers
+ fixtures :posts, :authors, :author_addresses, :projects, :developers
def setup
@david = authors(:david)
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index ddb5c7a4aa..3638c87968 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -12,7 +12,7 @@ require "models/vertex"
require "models/edge"
class CascadedEagerLoadingTest < ActiveRecord::TestCase
- fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments,
+ fixtures :authors, :author_addresses, :mixins, :companies, :posts, :topics, :accounts, :comments,
:categorizations, :people, :categories, :edges, :vertices
def test_eager_association_loading_with_cascaded_two_levels
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index 5d1c1c4b9b..16eff15026 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -1,147 +1,146 @@
require "cases/helper"
-if ActiveRecord::Base.connection.supports_migrations?
- class EagerSingularizationTest < ActiveRecord::TestCase
- class Virus < ActiveRecord::Base
- belongs_to :octopus
- end
-
- class Octopus < ActiveRecord::Base
- has_one :virus
- end
-
- class Pass < ActiveRecord::Base
- belongs_to :bus
- end
-
- class Bus < ActiveRecord::Base
- has_many :passes
- end
-
- class Mess < ActiveRecord::Base
- has_and_belongs_to_many :crises
- end
-
- class Crisis < ActiveRecord::Base
- has_and_belongs_to_many :messes
- has_many :analyses, dependent: :destroy
- has_many :successes, through: :analyses
- has_many :dresses, dependent: :destroy
- has_many :compresses, through: :dresses
- end
-
- class Analysis < ActiveRecord::Base
- belongs_to :crisis
- belongs_to :success
- end
-
- class Success < ActiveRecord::Base
- has_many :analyses, dependent: :destroy
- has_many :crises, through: :analyses
- end
-
- class Dress < ActiveRecord::Base
- belongs_to :crisis
- has_many :compresses
- end
-
- class Compress < ActiveRecord::Base
- belongs_to :dress
- end
-
- def setup
- connection.create_table :viri do |t|
- t.column :octopus_id, :integer
- t.column :species, :string
- end
- connection.create_table :octopi do |t|
- t.column :species, :string
- end
- connection.create_table :passes do |t|
- t.column :bus_id, :integer
- t.column :rides, :integer
- end
- connection.create_table :buses do |t|
- t.column :name, :string
- end
- connection.create_table :crises_messes, id: false do |t|
- t.column :crisis_id, :integer
- t.column :mess_id, :integer
- end
- connection.create_table :messes do |t|
- t.column :name, :string
- end
- connection.create_table :crises do |t|
- t.column :name, :string
- end
- connection.create_table :successes do |t|
- t.column :name, :string
- end
- connection.create_table :analyses do |t|
- t.column :crisis_id, :integer
- t.column :success_id, :integer
- end
- connection.create_table :dresses do |t|
- t.column :crisis_id, :integer
- end
- connection.create_table :compresses do |t|
- t.column :dress_id, :integer
- end
- end
-
- teardown do
- connection.drop_table :viri
- connection.drop_table :octopi
- connection.drop_table :passes
- connection.drop_table :buses
- connection.drop_table :crises_messes
- connection.drop_table :messes
- connection.drop_table :crises
- connection.drop_table :successes
- connection.drop_table :analyses
- connection.drop_table :dresses
- connection.drop_table :compresses
- end
+class EagerSingularizationTest < ActiveRecord::TestCase
+ class Virus < ActiveRecord::Base
+ belongs_to :octopus
+ end
- def connection
- ActiveRecord::Base.connection
+ class Octopus < ActiveRecord::Base
+ has_one :virus
+ end
+
+ class Pass < ActiveRecord::Base
+ belongs_to :bus
+ end
+
+ class Bus < ActiveRecord::Base
+ has_many :passes
+ end
+
+ class Mess < ActiveRecord::Base
+ has_and_belongs_to_many :crises
+ end
+
+ class Crisis < ActiveRecord::Base
+ has_and_belongs_to_many :messes
+ has_many :analyses, dependent: :destroy
+ has_many :successes, through: :analyses
+ has_many :dresses, dependent: :destroy
+ has_many :compresses, through: :dresses
+ end
+
+ class Analysis < ActiveRecord::Base
+ belongs_to :crisis
+ belongs_to :success
+ end
+
+ class Success < ActiveRecord::Base
+ has_many :analyses, dependent: :destroy
+ has_many :crises, through: :analyses
+ end
+
+ class Dress < ActiveRecord::Base
+ belongs_to :crisis
+ has_many :compresses
+ end
+
+ class Compress < ActiveRecord::Base
+ belongs_to :dress
+ end
+
+ def setup
+ connection.create_table :viri do |t|
+ t.column :octopus_id, :integer
+ t.column :species, :string
end
+ connection.create_table :octopi do |t|
+ t.column :species, :string
+ end
+ connection.create_table :passes do |t|
+ t.column :bus_id, :integer
+ t.column :rides, :integer
+ end
+ connection.create_table :buses do |t|
+ t.column :name, :string
+ end
+ connection.create_table :crises_messes, id: false do |t|
+ t.column :crisis_id, :integer
+ t.column :mess_id, :integer
+ end
+ connection.create_table :messes do |t|
+ t.column :name, :string
+ end
+ connection.create_table :crises do |t|
+ t.column :name, :string
+ end
+ connection.create_table :successes do |t|
+ t.column :name, :string
+ end
+ connection.create_table :analyses do |t|
+ t.column :crisis_id, :integer
+ t.column :success_id, :integer
+ end
+ connection.create_table :dresses do |t|
+ t.column :crisis_id, :integer
+ end
+ connection.create_table :compresses do |t|
+ t.column :dress_id, :integer
+ end
+ end
- def test_eager_no_extra_singularization_belongs_to
- assert_nothing_raised do
- Virus.all.merge!(includes: :octopus).to_a
- end
+ teardown do
+ connection.drop_table :viri
+ connection.drop_table :octopi
+ connection.drop_table :passes
+ connection.drop_table :buses
+ connection.drop_table :crises_messes
+ connection.drop_table :messes
+ connection.drop_table :crises
+ connection.drop_table :successes
+ connection.drop_table :analyses
+ connection.drop_table :dresses
+ connection.drop_table :compresses
+ end
+
+ def test_eager_no_extra_singularization_belongs_to
+ assert_nothing_raised do
+ Virus.all.merge!(includes: :octopus).to_a
end
+ end
- def test_eager_no_extra_singularization_has_one
- assert_nothing_raised do
- Octopus.all.merge!(includes: :virus).to_a
- end
+ def test_eager_no_extra_singularization_has_one
+ assert_nothing_raised do
+ Octopus.all.merge!(includes: :virus).to_a
end
+ end
- def test_eager_no_extra_singularization_has_many
- assert_nothing_raised do
- Bus.all.merge!(includes: :passes).to_a
- end
+ def test_eager_no_extra_singularization_has_many
+ assert_nothing_raised do
+ Bus.all.merge!(includes: :passes).to_a
end
+ end
- def test_eager_no_extra_singularization_has_and_belongs_to_many
- assert_nothing_raised do
- Crisis.all.merge!(includes: :messes).to_a
- Mess.all.merge!(includes: :crises).to_a
- end
+ def test_eager_no_extra_singularization_has_and_belongs_to_many
+ assert_nothing_raised do
+ Crisis.all.merge!(includes: :messes).to_a
+ Mess.all.merge!(includes: :crises).to_a
end
+ end
- def test_eager_no_extra_singularization_has_many_through_belongs_to
- assert_nothing_raised do
- Crisis.all.merge!(includes: :successes).to_a
- end
+ def test_eager_no_extra_singularization_has_many_through_belongs_to
+ assert_nothing_raised do
+ Crisis.all.merge!(includes: :successes).to_a
end
+ end
- def test_eager_no_extra_singularization_has_many_through_has_many
- assert_nothing_raised do
- Crisis.all.merge!(includes: :compresses).to_a
- end
+ def test_eager_no_extra_singularization_has_many_through_has_many
+ assert_nothing_raised do
+ Crisis.all.merge!(includes: :compresses).to_a
end
end
+
+ private
+ def connection
+ ActiveRecord::Base.connection
+ end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 974a3080d4..87d842f21d 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -36,6 +36,11 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
assert_equal comments(:greetings), posts(:welcome).comments.not_again.find_most_recent
end
+ def test_extension_with_dirty_target
+ comment = posts(:welcome).comments.build(body: "New comment")
+ assert_equal comment, posts(:welcome).comments.with_content("New comment")
+ end
+
def test_marshalling_extensions
david = developers(:david)
assert_equal projects(:action_controller), david.projects.find_most_recent
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ede3a44090..e2f044c139 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -40,7 +40,7 @@ require "models/zine"
require "models/interest"
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments
+ fixtures :authors, :author_addresses, :posts, :comments
def test_should_generate_valid_sql
author = authors(:david)
@@ -51,7 +51,7 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa
end
class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
- fixtures :authors, :essays, :subscribers, :subscriptions, :people
+ fixtures :authors, :author_addresses, :essays, :subscribers, :subscriptions, :people
def test_custom_primary_key_on_new_record_should_fetch_with_query
subscriber = Subscriber.new(nick: "webster132")
@@ -100,7 +100,7 @@ end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
- :developers_projects, :topics, :authors, :comments,
+ :developers_projects, :topics, :authors, :author_addresses, :comments,
:posts, :readers, :taggings, :cars, :jobs, :tags,
:categorizations, :zines, :interests
@@ -783,6 +783,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id)
end
+ def test_select_with_block_and_dirty_target
+ assert_equal 2, posts(:welcome).comments.select { true }.size
+ posts(:welcome).comments.build
+ assert_equal 3, posts(:welcome).comments.select { true }.size
+ end
+
def test_select_without_foreign_key
assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit
end
@@ -1897,7 +1903,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_many_on_loaded_association_should_not_use_query
firm = companies(:first_firm)
- firm.clients.collect # force load
+ firm.clients.load # force load
assert_no_queries { assert firm.clients.many? }
end
@@ -1936,7 +1942,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_none_on_loaded_association_should_not_use_query
firm = companies(:first_firm)
- firm.clients.collect # force load
+ firm.clients.load # force load
assert_no_queries { assert ! firm.clients.none? }
end
@@ -1971,7 +1977,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_one_on_loaded_association_should_not_use_query
firm = companies(:first_firm)
- firm.clients.collect # force load
+ firm.clients.load # force load
assert_no_queries { assert ! firm.clients.one? }
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 38a729d2d4..28b883586d 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -23,7 +23,7 @@ require "models/customer_carrier"
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
- :dashboards, :speedometers, :authors, :posts, :comments, :categories, :essays, :owners
+ :dashboards, :speedometers, :authors, :author_addresses, :posts, :comments, :categories, :essays, :owners
def setup
@member = members(:groucho)
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 7414869c8f..ddf5bc6f0b 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -10,7 +10,7 @@ require "models/tagging"
require "models/tag"
class InnerJoinAssociationTest < ActiveRecord::TestCase
- fixtures :authors, :essays, :posts, :comments, :categories, :categories_posts, :categorizations,
+ fixtures :authors, :author_addresses, :essays, :posts, :comments, :categories, :categories_posts, :categorizations,
:taggings, :tags
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index a4345f3857..c078cef064 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -19,7 +19,7 @@ require "models/car"
class AssociationsJoinModelTest < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
- fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
+ fixtures :posts, :authors, :author_addresses, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
# Reload edges table from fixtures as otherwise repeated test was failing
:edges
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 42dbbad1c8..2aca3523c4 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -7,7 +7,7 @@ require "models/categorization"
require "models/person"
class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
- fixtures :authors, :essays, :posts, :comments, :categorizations, :people
+ fixtures :authors, :essays, :posts, :comments, :categorizations, :people, :author_addresses
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index dc26f6a383..67ff7355b3 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -24,7 +24,7 @@ require "models/membership"
require "models/essay"
class NestedThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings,
+ fixtures :authors, :author_addresses, :books, :posts, :subscriptions, :subscribers, :tags, :taggings,
:people, :readers, :references, :jobs, :ratings, :comments, :members, :member_details,
:member_types, :sponsors, :clubs, :organizations, :categories, :categories_posts,
:categorizations, :memberships, :essays
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index f8b686721e..45e1803858 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -22,14 +22,21 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
@connection.drop_table "children", if_exists: true
end
- test "belongs_to associations are not required by default" do
- model = subclass_of(Child) do
- belongs_to :parent, inverse_of: false,
- class_name: "RequiredAssociationsTest::Parent"
- end
+ test "belongs_to associations can be optional by default" do
+ begin
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = false
+
+ model = subclass_of(Child) do
+ belongs_to :parent, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
+ end
- assert model.new.save
- assert model.new(parent: Parent.new).save
+ assert model.new.save
+ assert model.new(parent: Parent.new).save
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
+ end
end
test "required belongs_to associations have presence validated" do
@@ -46,6 +53,27 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
assert record.save
end
+ test "belongs_to associations can be required by default" do
+ begin
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = true
+
+ model = subclass_of(Child) do
+ belongs_to :parent, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
+ end
+
+ record = model.new
+ assert_not record.save
+ assert_equal ["Parent must exist"], record.errors.full_messages
+
+ record.parent = Parent.new
+ assert record.save
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
+ end
+ end
+
test "has_one associations are not required by default" do
model = subclass_of(Parent) do
has_one :child, inverse_of: false,
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 26056f6f63..4ab690bfc6 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -22,7 +22,7 @@ require "models/interest"
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
- :computers, :people, :readers, :authors, :author_favorites
+ :computers, :people, :readers, :authors, :author_addresses, :author_favorites
def test_eager_loading_should_not_change_count_of_children
liquid = Liquid.create(name: "salty")
@@ -111,7 +111,7 @@ class AssociationsTest < ActiveRecord::TestCase
end
class AssociationProxyTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects
+ fixtures :authors, :author_addresses, :posts, :categorizations, :categories, :developers, :projects, :developers_projects
def test_push_does_not_load_target
david = authors(:david)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 8efbc41da9..15c253890b 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -64,7 +64,7 @@ class LintTest < ActiveRecord::TestCase
end
class BasicsTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :categorizations, :categories, :posts
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 98d202dd79..6032aa9250 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -3,36 +3,36 @@ require "models/topic"
require "models/author"
require "models/post"
-module ActiveRecord
- class BindParameterTest < ActiveRecord::TestCase
- fixtures :topics, :authors, :posts
-
- class LogListener
- attr_accessor :calls
-
- def initialize
- @calls = []
+if ActiveRecord::Base.connection.supports_statement_cache? &&
+ ActiveRecord::Base.connection.prepared_statements
+ module ActiveRecord
+ class BindParameterTest < ActiveRecord::TestCase
+ fixtures :topics, :authors, :author_addresses, :posts
+
+ class LogListener
+ attr_accessor :calls
+
+ def initialize
+ @calls = []
+ end
+
+ def call(*args)
+ calls << args
+ end
end
- def call(*args)
- calls << args
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @subscriber = LogListener.new
+ @pk = Topic.columns_hash[Topic.primary_key]
+ @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
end
- end
- def setup
- super
- @connection = ActiveRecord::Base.connection
- @subscriber = LogListener.new
- @pk = Topic.columns_hash[Topic.primary_key]
- @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
- end
-
- teardown do
- ActiveSupport::Notifications.unsubscribe(@subscription)
- end
+ def teardown
+ ActiveSupport::Notifications.unsubscribe(@subscription)
+ end
- if ActiveRecord::Base.connection.supports_statement_cache? &&
- ActiveRecord::Base.connection.prepared_statements
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: "David")
scope = Author.from(subquery, "authors").where(id: 1)
@@ -56,43 +56,48 @@ module ActiveRecord
assert message, "expected a message with binds"
end
- def test_logs_bind_vars_after_type_cast
+ def test_logs_binds_after_type_cast
binds = [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- payload = {
- name: "SQL",
- sql: "select * from topics where id = ?",
- binds: binds,
- type_casted_binds: type_casted_binds
- }
- event = ActiveSupport::Notifications::Event.new(
- "foo",
- Time.now,
- Time.now,
- 123,
- payload)
-
- logger = Class.new(ActiveRecord::LogSubscriber) {
- attr_reader :debugs
- def initialize
- super
- @debugs = []
- end
-
- def debug(str)
- @debugs << str
- end
- }.new
-
- logger.sql event
- assert_match([[@pk.name, 10]].inspect, logger.debugs.first)
+ assert_logs_binds(binds)
end
- private
-
- def type_cast(value)
- ActiveRecord::Base.connection.type_cast(value)
+ def test_logs_legacy_binds_after_type_cast
+ binds = [[@pk, "10"]]
+ assert_logs_binds(binds)
end
+
+ private
+ def assert_logs_binds(binds)
+ payload = {
+ name: "SQL",
+ sql: "select * from topics where id = ?",
+ binds: binds,
+ type_casted_binds: @connection.type_casted_binds(binds)
+ }
+
+ event = ActiveSupport::Notifications::Event.new(
+ "foo",
+ Time.now,
+ Time.now,
+ 123,
+ payload)
+
+ logger = Class.new(ActiveRecord::LogSubscriber) {
+ attr_reader :debugs
+
+ def initialize
+ super
+ @debugs = []
+ end
+
+ def debug(str)
+ @debugs << str
+ end
+ }.new
+
+ logger.sql(event)
+ assert_match([[@pk.name, 10]].inspect, logger.debugs.first)
+ end
end
end
end
diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb
index bb2829b3c1..2c6a38ec35 100644
--- a/activerecord/test/cases/cache_key_test.rb
+++ b/activerecord/test/cases/cache_key_test.rb
@@ -15,7 +15,7 @@ module ActiveRecord
@connection.drop_table :cache_mes, if_exists: true
end
- test "test_cache_key_format_is_not_too_precise" do
+ test "cache_key format is not too precise" do
record = CacheMe.create
key = record.cache_key
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 1813534b62..3214d778d4 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -227,6 +227,20 @@ class CalculationsTest < ActiveRecord::TestCase
assert_match "credit_limit, firm_name", e.message
end
+ def test_apply_distinct_in_count
+ queries = assert_sql do
+ Account.distinct.count
+ Account.group(:firm_id).distinct.count
+ end
+
+ queries.each do |query|
+ # `table_alias_length` in `column_alias_for` would execute
+ # "SHOW max_identifier_length" statement in PostgreSQL adapter.
+ next if query == "SHOW max_identifier_length"
+ assert_match %r{\ASELECT(?! DISTINCT) COUNT\(DISTINCT\b}, query
+ end
+ end
+
def test_should_group_by_summed_field_having_condition
c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit)
assert_nil c[1]
@@ -235,7 +249,8 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_having_condition_from_select
- c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("MIN(credit_limit) > 50").sum(:credit_limit)
+ skip unless current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
+ c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit)
assert_nil c[1]
assert_equal 60, c[2]
assert_equal 53, c[9]
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index 59ef389326..a26a72712d 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -1,4 +1,3 @@
-
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index d230700119..90c8d21c43 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
def @adapter.native_database_types
{ string: "varchar" }
end
- @viz = @adapter.schema_creation
+ @viz = @adapter.send(:schema_creation)
end
# Avoid column definitions in create table statements like:
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index 63f67a9a16..c23be52a6c 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -72,9 +72,11 @@ if ActiveRecord::Base.connection.supports_comments?
end
def test_add_index_with_comment_later
- @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
- index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
- assert_equal "We need to see obvious comments", index.comment
+ unless current_adapter?(:OracleAdapter)
+ @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
+ index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
+ assert_equal "We need to see obvious comments", index.comment
+ end
end
def test_add_comment_to_column
@@ -112,8 +114,10 @@ if ActiveRecord::Base.connection.supports_comments?
assert_match %r[t\.string\s+"obvious"\n], output
assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
- assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
- assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
+ unless current_adapter?(:OracleAdapter)
+ assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
+ assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
+ end
end
def test_schema_dump_omits_blank_comments
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index afd0ac2dd4..7e88c9cf7a 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -307,14 +307,17 @@ module ActiveRecord
end
end
- def test_automatic_reconnect=
+ def test_automatic_reconnect_restores_after_disconnect
pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
assert pool.automatic_reconnect
assert pool.connection
pool.disconnect!
assert pool.connection
+ end
+ def test_automatic_reconnect_can_be_disabled
+ pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
pool.disconnect!
pool.automatic_reconnect = false
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index 3bc08f80ec..ad7da9de70 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -52,7 +52,7 @@ class DateTimeTest < ActiveRecord::TestCase
end
def test_assign_in_local_timezone
- now = DateTime.now
+ now = DateTime.civil(2017, 3, 1, 12, 0, 0)
with_timezone_config default: :local do
task = Task.new starting: now
assert_equal now, task.starting
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a43c06cd6e..721861975a 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -349,13 +349,14 @@ class DirtyTest < ActiveRecord::TestCase
def test_partial_update_with_optimistic_locking
person = Person.new(first_name: "foo")
- old_lock_version = person.lock_version
with_partial_writes Person, false do
assert_queries(2) { 2.times { person.save! } }
Person.where(id: person.id).update_all(first_name: "baz")
end
+ old_lock_version = person.lock_version
+
with_partial_writes Person, true do
assert_queries(0) { 2.times { person.save! } }
assert_equal old_lock_version, person.reload.lock_version
@@ -566,19 +567,17 @@ class DirtyTest < ActiveRecord::TestCase
travel_back
end
- if ActiveRecord::Base.connection.supports_migrations?
- class Testings < ActiveRecord::Base; end
- def test_field_named_field
- ActiveRecord::Base.connection.create_table :testings do |t|
- t.string :field
- end
- assert_nothing_raised do
- Testings.new.attributes
- end
- ensure
- ActiveRecord::Base.connection.drop_table :testings rescue nil
- ActiveRecord::Base.clear_cache!
+ class Testings < ActiveRecord::Base; end
+ def test_field_named_field
+ ActiveRecord::Base.connection.create_table :testings do |t|
+ t.string :field
end
+ assert_nothing_raised do
+ Testings.new.attributes
+ end
+ ensure
+ ActiveRecord::Base.connection.drop_table :testings rescue nil
+ ActiveRecord::Base.clear_cache!
end
def test_datetime_attribute_can_be_updated_with_fractional_seconds
@@ -672,6 +671,47 @@ class DirtyTest < ActiveRecord::TestCase
assert binary.changed?
end
+ test "changes is correct for subclass" do
+ foo = Class.new(Pirate) do
+ def catchphrase
+ super.upcase
+ end
+ end
+
+ pirate = foo.create!(catchphrase: "arrrr")
+
+ new_catchphrase = "arrrr matey!"
+
+ pirate.catchphrase = new_catchphrase
+ assert pirate.catchphrase_changed?
+
+ expected_changes = {
+ "catchphrase" => ["arrrr", new_catchphrase]
+ }
+
+ assert_equal new_catchphrase.upcase, pirate.catchphrase
+ assert_equal expected_changes, pirate.changes
+ end
+
+ test "changes is correct if override attribute reader" do
+ pirate = Pirate.create!(catchphrase: "arrrr")
+ def pirate.catchphrase
+ super.upcase
+ end
+
+ new_catchphrase = "arrrr matey!"
+
+ pirate.catchphrase = new_catchphrase
+ assert pirate.catchphrase_changed?
+
+ expected_changes = {
+ "catchphrase" => ["arrrr", new_catchphrase]
+ }
+
+ assert_equal new_catchphrase.upcase, pirate.catchphrase
+ assert_equal expected_changes, pirate.changes
+ end
+
test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do
test_type_class = Class.new(ActiveRecord::Type::Value) do
define_method(:changed_in_place?) do |*|
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index 3821e0c949..000ed27efb 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -143,6 +143,8 @@ module ActiveRecord
end
def test_dup_without_primary_key
+ skip if current_adapter?(:OracleAdapter)
+
klass = Class.new(ActiveRecord::Base) do
self.table_name = "parrots_pirates"
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index deec669935..a7b6333010 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -202,11 +202,29 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.first.replies.exists?
end
- # ensures +exists?+ runs valid SQL by excluding order value
- def test_exists_with_order
+ # Ensure +exists?+ runs without an error by excluding distinct value.
+ # See https://github.com/rails/rails/pull/26981.
+ def test_exists_with_order_and_distinct
assert_equal true, Topic.order(:id).distinct.exists?
end
+ # Ensure +exists?+ runs without an error by excluding order value.
+ def test_exists_with_order
+ assert_equal true, Topic.order("invalid sql here").exists?
+ end
+
+ def test_exists_with_joins
+ assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_left_joins
+ assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_eager_load
+ assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
def test_exists_with_includes_limit_and_empty_result
assert_equal false, Topic.includes(:replies).limit(0).exists?
assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists?
@@ -236,9 +254,9 @@ class FinderTest < ActiveRecord::TestCase
def test_exists_with_aggregate_having_three_mappings_with_one_difference
existing_address = customers(:david).address
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
def test_exists_does_not_instantiate_records
@@ -497,7 +515,7 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.offset(5).second_to_last
#test with limit
- # assert_nil Topic.limit(1).second # TODO: currently failing
+ assert_nil Topic.limit(1).second
assert_nil Topic.limit(1).second_to_last
end
@@ -526,9 +544,9 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.offset(5).third_to_last
# test with limit
- # assert_nil Topic.limit(1).third # TODO: currently failing
+ assert_nil Topic.limit(1).third
assert_nil Topic.limit(1).third_to_last
- # assert_nil Topic.limit(2).third # TODO: currently failing
+ assert_nil Topic.limit(2).third
assert_nil Topic.limit(2).third_to_last
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index afe761cb55..a0a6d3c7ef 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -95,6 +95,24 @@ class FixturesTest < ActiveRecord::TestCase
assert_nil(topics["second"]["author_email_address"])
end
+ def test_no_args_returns_all
+ all_topics = topics
+ assert_equal 5, all_topics.length
+ assert_equal "The First Topic", all_topics.first["title"]
+ assert_equal 5, all_topics.last.id
+ end
+
+ def test_no_args_record_returns_all_without_array
+ all_binaries = binaries
+ assert_kind_of(Array, all_binaries)
+ assert_equal 1, binaries.length
+ end
+
+ def test_nil_raises
+ assert_raise(StandardError) { topics(nil) }
+ assert_raise(StandardError) { topics([nil]) }
+ end
+
def test_inserts
create_fixtures("topics")
first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
@@ -104,64 +122,62 @@ class FixturesTest < ActiveRecord::TestCase
assert_nil(second_row["author_email_address"])
end
- if ActiveRecord::Base.connection.supports_migrations?
- def test_inserts_with_pre_and_suffix
- # Reset cache to make finds on the new table work
- ActiveRecord::FixtureSet.reset_cache
-
- ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t|
- t.column :title, :string
- t.column :author_name, :string
- t.column :author_email_address, :string
- t.column :written_on, :datetime
- t.column :bonus_time, :time
- t.column :last_read, :date
- t.column :content, :string
- t.column :approved, :boolean, default: true
- t.column :replies_count, :integer, default: 0
- t.column :parent_id, :integer
- t.column :type, :string, limit: 50
- end
+ def test_inserts_with_pre_and_suffix
+ # Reset cache to make finds on the new table work
+ ActiveRecord::FixtureSet.reset_cache
- # Store existing prefix/suffix
- old_prefix = ActiveRecord::Base.table_name_prefix
- old_suffix = ActiveRecord::Base.table_name_suffix
+ ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t|
+ t.column :title, :string
+ t.column :author_name, :string
+ t.column :author_email_address, :string
+ t.column :written_on, :datetime
+ t.column :bonus_time, :time
+ t.column :last_read, :date
+ t.column :content, :string
+ t.column :approved, :boolean, default: true
+ t.column :replies_count, :integer, default: 0
+ t.column :parent_id, :integer
+ t.column :type, :string, limit: 50
+ end
- # Set a prefix/suffix we can test against
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
+ # Store existing prefix/suffix
+ old_prefix = ActiveRecord::Base.table_name_prefix
+ old_suffix = ActiveRecord::Base.table_name_suffix
- other_topic_klass = Class.new(ActiveRecord::Base) do
- def self.name
- "OtherTopic"
- end
+ # Set a prefix/suffix we can test against
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+
+ other_topic_klass = Class.new(ActiveRecord::Base) do
+ def self.name
+ "OtherTopic"
end
+ end
- topics = [create_fixtures("other_topics")].flatten.first
+ topics = [create_fixtures("other_topics")].flatten.first
- # This checks for a caching problem which causes a bug in the fixtures
- # class-level configuration helper.
- assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create"
+ # This checks for a caching problem which causes a bug in the fixtures
+ # class-level configuration helper.
+ assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create"
- first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'")
- assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found"
- assert_equal("The First Topic", first_row["title"])
+ first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'")
+ assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found"
+ assert_equal("The First Topic", first_row["title"])
- second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'")
- assert_nil(second_row["author_email_address"])
+ second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'")
+ assert_nil(second_row["author_email_address"])
- assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym
- # This assertion should preferably be the last in the list, because calling
- # other_topic_klass.table_name sets a class-level instance variable
- assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym
+ assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym
+ # This assertion should preferably be the last in the list, because calling
+ # other_topic_klass.table_name sets a class-level instance variable
+ assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym
- ensure
- # Restore prefix/suffix to its previous values
- ActiveRecord::Base.table_name_prefix = old_prefix
- ActiveRecord::Base.table_name_suffix = old_suffix
+ ensure
+ # Restore prefix/suffix to its previous values
+ ActiveRecord::Base.table_name_prefix = old_prefix
+ ActiveRecord::Base.table_name_suffix = old_suffix
- ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil
- end
+ ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil
end
def test_insert_with_datetime
@@ -768,7 +784,7 @@ end
class FasterFixturesTest < ActiveRecord::TestCase
self.use_transactional_tests = false
- fixtures :categories, :authors
+ fixtures :categories, :authors, :author_addresses
def load_extra_fixture(name)
fixture = create_fixtures(name).first
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index d7aa091623..0678bb714f 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -1,4 +1,3 @@
-
require "cases/helper"
require "models/company"
require "models/developer"
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 5a1d066aef..9b4b61b16e 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -168,7 +168,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
end
class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments, :tags, :taggings
+ fixtures :authors, :author_addresses, :posts, :comments, :tags, :taggings
include JsonSerializationHelpers
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 23095618a4..3a3b8e51f9 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -247,17 +247,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "new title2", t2.title
end
- def test_lock_without_default_should_update_with_lock_col
- t1 = LockWithoutDefault.create(title: "title1", lock_version: 6)
-
- assert_equal 6, t1.lock_version
-
- t1.update(lock_version: 0)
- t1.reload
-
- assert_equal 0, t1.lock_version
- end
-
def test_lock_without_default_queries_count
t1 = LockWithoutDefault.create(title: "title1")
@@ -270,12 +259,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "title2", t1.title
assert_equal 1, t1.lock_version
- assert_queries(1) { t1.update(title: "title3", lock_version: 6) }
-
- t1.reload
- assert_equal "title3", t1.title
- assert_equal 6, t1.lock_version
-
t2 = LockWithoutDefault.new(title: "title1")
assert_queries(1) { t2.save! }
@@ -321,17 +304,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "new title2", t2.title
end
- def test_lock_with_custom_column_without_default_should_update_with_lock_col
- t1 = LockWithCustomColumnWithoutDefault.create(title: "title1", custom_lock_version: 6)
-
- assert_equal 6, t1.custom_lock_version
-
- t1.update(custom_lock_version: 0)
- t1.reload
-
- assert_equal 0, t1.custom_lock_version
- end
-
def test_lock_with_custom_column_without_default_queries_count
t1 = LockWithCustomColumnWithoutDefault.create(title: "title1")
@@ -344,12 +316,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "title2", t1.title
assert_equal 1, t1.custom_lock_version
- assert_queries(1) { t1.update(title: "title3", custom_lock_version: 6) }
-
- t1.reload
- assert_equal "title3", t1.title
- assert_equal 6, t1.custom_lock_version
-
t2 = LockWithCustomColumnWithoutDefault.new(title: "title1")
assert_queries(1) { t2.save! }
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 55c06da411..2329888345 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -225,6 +225,16 @@ module ActiveRecord
assert_nil TestModel.new.contributor
end
+ def test_change_column_to_drop_default_with_null_false
+ add_column "test_models", "contributor", :boolean, default: true, null: false
+ assert TestModel.new.contributor?
+
+ change_column "test_models", "contributor", :boolean, default: nil, null: false
+ TestModel.reset_column_information
+ assert_not TestModel.new.contributor?
+ assert_nil TestModel.new.contributor
+ end
+
def test_change_column_with_new_default
add_column "test_models", "administrator", :boolean, default: true
assert TestModel.new.administrator?
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 26b1bb4419..c4896f3d6e 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -12,7 +12,7 @@ module ActiveRecord
teardown do
%w(artists_musics musics_videos catalog).each do |table_name|
- connection.drop_table table_name if connection.table_exists?(table_name)
+ connection.drop_table table_name, if_exists: true
end
end
@@ -78,6 +78,17 @@ module ActiveRecord
assert_equal [%w(artist_id music_id)], connection.indexes(:artists_musics).map(&:columns)
end
+ def test_create_join_table_respects_reference_key_type
+ connection.create_join_table :artists, :musics do |t|
+ t.references :video
+ end
+
+ artist_id, music_id, video_id = connection.columns(:artists_musics).sort_by(&:name)
+
+ assert_equal video_id.sql_type, artist_id.sql_type
+ assert_equal video_id.sql_type, music_id.sql_type
+ end
+
def test_drop_join_table
connection.create_join_table :artists, :musics
connection.drop_join_table :artists, :musics
diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb
index 61f5a061b0..6970fdcc87 100644
--- a/activerecord/test/cases/migration/pending_migrations_test.rb
+++ b/activerecord/test/cases/migration/pending_migrations_test.rb
@@ -21,8 +21,6 @@ module ActiveRecord
end
def test_errors_if_pending
- @connection.expect :supports_migrations?, true
-
ActiveRecord::Migrator.stub :needs_migration?, true do
assert_raise ActiveRecord::PendingMigrationError do
@pending.call(nil)
@@ -31,22 +29,12 @@ module ActiveRecord
end
def test_checks_if_supported
- @connection.expect :supports_migrations?, true
@app.expect :call, nil, [:foo]
ActiveRecord::Migrator.stub :needs_migration?, false do
@pending.call(:foo)
end
end
-
- def test_doesnt_check_if_unsupported
- @connection.expect :supports_migrations?, false
- @app.expect :call, nil, [:foo]
-
- ActiveRecord::Migrator.stub :needs_migration?, true do
- @pending.call(:foo)
- end
- end
end
end
end
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index df15d7cb45..06c44c8c52 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -50,6 +50,13 @@ module ActiveRecord
assert column_exists?(table_name, :taggable_type, :string, default: "Photo")
end
+ def test_does_not_share_options_with_reference_type_column
+ add_reference table_name, :taggable, type: :integer, limit: 2, polymorphic: true
+ assert column_exists?(table_name, :taggable_id, :integer, limit: 2)
+ assert column_exists?(table_name, :taggable_type, :string)
+ assert_not column_exists?(table_name, :taggable_type, :string, limit: 2)
+ end
+
def test_creates_named_index
add_reference table_name, :tag, index: { name: "index_taggings_on_tag_id" }
assert index_exists?(table_name, :tag_id, name: "index_taggings_on_tag_id")
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 19588d28a2..7bcabd0cc6 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -80,7 +80,7 @@ module ActiveRecord
end
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
- connection.create_table :cats, id: :uuid
+ connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()"
assert_nothing_raised { rename_table :cats, :felines }
assert connection.table_exists? :felines
ensure
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index de16ecf442..da7875187a 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -337,20 +337,20 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_schema_migrations_table_name
- original_schema_migrations_table_name = ActiveRecord::Migrator.schema_migrations_table_name
+ original_schema_migrations_table_name = ActiveRecord::Base.schema_migrations_table_name
- assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "schema_migrations", ActiveRecord::SchemaMigration.table_name
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
Reminder.reset_table_name
- assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "prefix_schema_migrations_suffix", ActiveRecord::SchemaMigration.table_name
ActiveRecord::Base.schema_migrations_table_name = "changed"
Reminder.reset_table_name
- assert_equal "prefix_changed_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "prefix_changed_suffix", ActiveRecord::SchemaMigration.table_name
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
Reminder.reset_table_name
- assert_equal "changed", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "changed", ActiveRecord::SchemaMigration.table_name
ensure
ActiveRecord::Base.schema_migrations_table_name = original_schema_migrations_table_name
Reminder.reset_table_name
@@ -1142,4 +1142,12 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_deprecate_migration_keys
assert_deprecated { ActiveRecord::Base.connection.migration_keys }
end
+
+ def test_deprecate_supports_migrations
+ assert_deprecated { ActiveRecord::Base.connection.supports_migrations? }
+ end
+
+ def test_deprecate_schema_migrations_table_name
+ assert_deprecated { ActiveRecord::Migrator.schema_migrations_table_name }
+ end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 20d70b75ac..aadbc375af 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -124,6 +124,67 @@ class MigratorTest < ActiveRecord::TestCase
assert_equal migration_list.last, migrations.first
end
+ def test_migrations_status
+ path = MIGRATIONS_ROOT + "/valid"
+
+ ActiveRecord::SchemaMigration.create(version: 2)
+ ActiveRecord::SchemaMigration.create(version: 10)
+
+ assert_equal [
+ ["down", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["down", "003", "Innocent jointable"],
+ ["up", "010", "********** NO FILE **********"],
+ ], ActiveRecord::Migrator.migrations_status(path)
+ end
+
+ def test_migrations_status_in_subdirectories
+ path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
+
+ ActiveRecord::SchemaMigration.create(version: 2)
+ ActiveRecord::SchemaMigration.create(version: 10)
+
+ assert_equal [
+ ["down", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["down", "003", "Innocent jointable"],
+ ["up", "010", "********** NO FILE **********"],
+ ], ActiveRecord::Migrator.migrations_status(path)
+ end
+
+ def test_migrations_status_with_schema_define_in_subdirectories
+ path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
+ prev_paths = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = path
+
+ ActiveRecord::Schema.define(version: 3) do
+ end
+
+ assert_equal [
+ ["up", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["up", "003", "Innocent jointable"],
+ ], ActiveRecord::Migrator.migrations_status(path)
+ ensure
+ ActiveRecord::Migrator.migrations_paths = prev_paths
+ end
+
+ def test_migrations_status_from_two_directories
+ paths = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
+
+ ActiveRecord::SchemaMigration.create(version: "20100101010101")
+ ActiveRecord::SchemaMigration.create(version: "20160528010101")
+
+ assert_equal [
+ ["down", "20090101010101", "People have hobbies"],
+ ["down", "20090101010202", "People have descriptions"],
+ ["up", "20100101010101", "Valid with timestamps people have last names"],
+ ["down", "20100201010101", "Valid with timestamps we need reminders"],
+ ["down", "20100301010101", "Valid with timestamps innocent jointable"],
+ ["up", "20160528010101", "********** NO FILE **********"],
+ ], ActiveRecord::Migrator.migrations_status(paths)
+ end
+
def test_migrator_interleaved_migrations
pass_one = [Sensor.new("One", 1)]
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 12386635f6..5ded619716 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -183,6 +183,8 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
def test_create_without_primary_key_no_extra_query
+ skip if current_adapter?(:OracleAdapter)
+
klass = Class.new(ActiveRecord::Base) do
self.table_name = "dashboards"
end
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index a93061b516..24b678310d 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -10,7 +10,7 @@ require "models/person"
require "models/ship"
class ReadOnlyTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
+ fixtures :authors, :author_addresses, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
def test_cant_save_readonly_record
dev = Developer.find(1)
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 49d4aeafc9..8cb7b82015 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -33,7 +33,7 @@ module ActiveRecord
:map, :none?, :one?, :partition, :reject, :reverse,
:sample, :second, :sort, :sort_by, :third,
:to_ary, :to_set, :to_xml, :to_yaml, :join,
- :in_groups, :in_groups_of, :to_sentence, :to_formatted_s
+ :in_groups, :in_groups_of, :to_sentence, :to_formatted_s, :as_json
]
ARRAY_DELEGATES.each do |method|
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 278dac8171..9e95149ede 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -8,7 +8,7 @@ require "models/project"
require "models/rating"
class RelationMergingTest < ActiveRecord::TestCase
- fixtures :developers, :comments, :authors, :posts
+ fixtures :developers, :comments, :authors, :author_addresses, :posts
def test_relation_merging
devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3"))
@@ -114,7 +114,7 @@ class RelationMergingTest < ActiveRecord::TestCase
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
- fixtures :posts, :authors, :developers
+ fixtures :posts, :authors, :author_addresses, :developers
test "merging where relations" do
hello_by_bob = Post.where(body: "hello").joins(:author).
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 4f92f71a09..11ef0d8743 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -143,7 +143,7 @@ module ActiveRecord
assert_equal({ foo: "bar" }, relation.create_with_value)
end
- test "test_merge!" do
+ test "merge!" do
assert relation.merge!(select: :foo).equal?(relation)
assert_equal [:foo], relation.select_values
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index ed8ffddcf5..cbc466d6b8 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -15,7 +15,7 @@ require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :binaries, :essays, :cars, :treasures, :price_estimates
+ fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates
def test_where_copies_bind_params
author = authors(:david)
@@ -289,6 +289,11 @@ module ActiveRecord
assert_equal essays(:david_modest_proposal), essay
end
+ def test_where_on_association_with_select_relation
+ essay = Essay.where(author: Author.where(name: "David").select(:name)).take
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
def test_where_with_strong_parameters
protected_params = Class.new do
attr_reader :permitted
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 1241bb54a4..5fb32270b7 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -6,7 +6,7 @@ require "models/rating"
module ActiveRecord
class RelationTest < ActiveRecord::TestCase
- fixtures :posts, :comments, :authors
+ fixtures :posts, :comments, :authors, :author_addresses
FakeKlass = Struct.new(:table_name, :name) do
extend ActiveRecord::Delegation::DelegateCache
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0c94e891eb..fcf68b0f2a 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -22,7 +22,7 @@ require "models/categorization"
require "models/edge"
class RelationTest < ActiveRecord::TestCase
- fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
+ fixtures :authors, :author_addresses, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
:tags, :taggings, :cars, :minivans
class TopicWithCallbacks < ActiveRecord::Base
@@ -112,7 +112,7 @@ class RelationTest < ActiveRecord::TestCase
def test_loaded_first
topics = Topic.all.order("id ASC")
- topics.to_a # force load
+ topics.load # force load
assert_no_queries do
assert_equal "The First Topic", topics.first.title
@@ -123,7 +123,7 @@ class RelationTest < ActiveRecord::TestCase
def test_loaded_first_with_limit
topics = Topic.all.order("id ASC")
- topics.to_a # force load
+ topics.load # force load
assert_no_queries do
assert_equal ["The First Topic",
@@ -136,7 +136,7 @@ class RelationTest < ActiveRecord::TestCase
def test_first_get_more_than_available
topics = Topic.all.order("id ASC")
unloaded_first = topics.first(10)
- topics.to_a # force load
+ topics.load # force load
assert_no_queries do
loaded_first = topics.first(10)
@@ -218,17 +218,34 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fifth).title, topics.first.title
end
- def test_finding_with_reverted_assoc_order
+ def test_finding_with_arel_assoc_order
+ topics = Topic.order(Arel.sql("id") => :desc)
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
+ def test_finding_with_reversed_assoc_order
topics = Topic.order(id: :asc).reverse_order
assert_equal 5, topics.to_a.size
assert_equal topics(:fifth).title, topics.first.title
end
+ def test_finding_with_reversed_arel_assoc_order
+ topics = Topic.order(Arel.sql("id") => :asc).reverse_order
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
def test_reverse_order_with_function
topics = Topic.order("length(title)").reverse_order
assert_equal topics(:second).title, topics.first.title
end
+ def test_reverse_arel_assoc_order_with_function
+ topics = Topic.order(Arel.sql("length(title)") => :asc).reverse_order
+ assert_equal topics(:second).title, topics.first.title
+ end
+
def test_reverse_order_with_function_other_predicates
topics = Topic.order("author_name, length(title), id").reverse_order
assert_equal topics(:second).title, topics.first.title
@@ -251,6 +268,12 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_reverse_arel_assoc_order_with_multiargument_function
+ assert_nothing_raised do
+ Topic.order(Arel.sql("REPLACE(title, '', '')") => :asc).reverse_order
+ end
+ end
+
def test_reverse_order_with_nulls_first_or_last
assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order("title NULLS FIRST").reverse_order
@@ -1132,7 +1155,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.loaded?
best_posts = posts.where(comments_count: 0)
- best_posts.to_a # force load
+ best_posts.load # force load
assert_no_queries { assert_equal 9, best_posts.size }
end
@@ -1143,7 +1166,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.loaded?
best_posts = posts.where(comments_count: 0)
- best_posts.to_a # force load
+ best_posts.load # force load
assert_no_queries { assert_equal 9, best_posts.size }
end
@@ -1153,7 +1176,7 @@ class RelationTest < ActiveRecord::TestCase
assert_no_queries { assert_equal 0, posts.size }
assert ! posts.loaded?
- posts.to_a # force load
+ posts.load # force load
assert_no_queries { assert_equal 0, posts.size }
end
@@ -1182,7 +1205,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! no_posts.loaded?
best_posts = posts.where(comments_count: 0)
- best_posts.to_a # force load
+ best_posts.load # force load
assert_no_queries { assert_equal false, best_posts.empty? }
end
@@ -1878,6 +1901,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "#<ActiveRecord::Relation [#{Post.limit(10).map(&:inspect).join(', ')}, ...]>", relation.inspect
end
+ test "relations don't load all records in #inspect" do
+ assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
+ Post.all.inspect
+ end
+ end
+
test "already-loaded relations don't perform a new query in #inspect" do
relation = Post.limit(2)
relation.to_a
@@ -1947,6 +1976,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal p2.first.comments, comments
end
+ def test_unscope_specific_where_value
+ posts = Post.where(title: "Welcome to the weblog", body: "Such a lovely day")
+
+ assert_equal 1, posts.count
+ assert_equal 1, posts.unscope(where: :title).count
+ assert_equal 1, posts.unscope(where: :body).count
+ end
+
def test_unscope_removes_binds
left = Post.where(id: Arel::Nodes::BindParam.new)
column = Post.columns_hash["id"]
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 9584318e86..cb8d449ba9 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -182,7 +182,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:PostgreSQLAdapter)
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
elsif current_adapter?(:Mysql2Adapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
+ if ActiveRecord::Base.connection.supports_index_sort_order?
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, order: { rating: :desc }', index_definition
+ else
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
+ end
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
@@ -422,11 +426,12 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table :defaults, force: true do |t|
+ @connection.create_table :dump_defaults, force: true do |t|
t.string :string_with_default, default: "Hello!"
t.date :date_with_default, default: "2014-06-05"
t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
t.time :time_with_default, default: "07:17:04"
+ t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
end
if current_adapter?(:PostgreSQLAdapter)
@@ -438,17 +443,17 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
end
teardown do
- return unless @connection
- @connection.drop_table "defaults", if_exists: true
+ @connection.drop_table "dump_defaults", if_exists: true
end
def test_schema_dump_defaults_with_universally_supported_types
- output = dump_table_schema("defaults")
+ output = dump_table_schema("dump_defaults")
assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output
- assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output
- assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output
- assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output
+ assert_match %r{t\.date\s+"date_with_default",\s+default: "2014-06-05"}, output
+ assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: "2014-06-05 07:17:04"}, output
+ assert_match %r{t\.time\s+"time_with_default",\s+default: "2000-01-01 07:17:04"}, output
+ assert_match %r{t\.decimal\s+"decimal_with_default",\s+precision: 20,\s+scale: 10,\s+default: "1234567890.0123456789"}, output
end
def test_schema_dump_with_float_column_infinity_default
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 14fb2fbbfa..89fb434b27 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -10,8 +10,6 @@ require "concurrent/atomic/cyclic_barrier"
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
- self.use_transactional_tests = false
-
def test_default_scope
expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary)
received = DeveloperOrderedBySalary.all.collect(&:salary)
@@ -61,17 +59,6 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal "Jamis", DeveloperCalledJamis.create!.name
end
- unless in_memory_db?
- def test_default_scoping_with_threads
- 2.times do
- Thread.new {
- assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC"
- DeveloperOrderedBySalary.connection.close
- }.join
- end
- end
- end
-
def test_default_scope_with_inheritance
wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash
assert_equal "Jamis", wheres["name"]
@@ -435,29 +422,6 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal comment, CommentWithDefaultScopeReferencesAssociation.find_by(id: comment.id)
end
- unless in_memory_db?
- def test_default_scope_is_threadsafe
- threads = []
- assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
-
- barrier_1 = Concurrent::CyclicBarrier.new(2)
- barrier_2 = Concurrent::CyclicBarrier.new(2)
-
- threads << Thread.new do
- Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }
- assert_equal 1, ThreadsafeDeveloper.all.to_a.count
- ThreadsafeDeveloper.connection.close
- end
- threads << Thread.new do
- Thread.current[:default_scope_delay] = -> { barrier_2.wait }
- barrier_1.wait
- assert_equal 1, ThreadsafeDeveloper.all.to_a.count
- ThreadsafeDeveloper.connection.close
- end
- threads.each(&:join)
- end
- end
-
test "additional conditions are ANDed with the default scope" do
scope = DeveloperCalledJamis.where(name: "David")
assert_equal 2, scope.where_clause.ast.children.length
@@ -498,6 +462,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_with_abstract_class_scope_should_be_executed_in_correct_context
vegetarian_pattern, gender_pattern = if current_adapter?(:Mysql2Adapter)
[/`lions`.`is_vegetarian`/, /`lions`.`gender`/]
+ elsif current_adapter?(:OracleAdapter)
+ [/"LIONS"."IS_VEGETARIAN"/, /"LIONS"."GENDER"/]
else
[/"lions"."is_vegetarian"/, /"lions"."gender"/]
end
@@ -506,3 +472,37 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match gender_pattern, Lion.female.to_sql
end
end
+
+class DefaultScopingWithThreadTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def test_default_scoping_with_threads
+ 2.times do
+ Thread.new {
+ assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC"
+ DeveloperOrderedBySalary.connection.close
+ }.join
+ end
+ end
+
+ def test_default_scope_is_threadsafe
+ threads = []
+ assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
+
+ barrier_1 = Concurrent::CyclicBarrier.new(2)
+ barrier_2 = Concurrent::CyclicBarrier.new(2)
+
+ threads << Thread.new do
+ Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }
+ assert_equal 1, ThreadsafeDeveloper.all.to_a.count
+ ThreadsafeDeveloper.connection.close
+ end
+ threads << Thread.new do
+ Thread.current[:default_scope_delay] = -> { barrier_2.wait }
+ barrier_1.wait
+ assert_equal 1, ThreadsafeDeveloper.all.to_a.count
+ ThreadsafeDeveloper.connection.close
+ end
+ threads.each(&:join)
+ end
+end unless in_memory_db?
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index d261fd5321..0c2cffe0d3 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -23,8 +23,8 @@ class NamedScopingTest < ActiveRecord::TestCase
all_posts = Topic.base
assert_queries(1) do
- all_posts.collect
- all_posts.collect
+ all_posts.collect { true }
+ all_posts.collect { true }
end
end
@@ -167,7 +167,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_first_and_last_should_not_use_query_when_results_are_loaded
topics = Topic.base
- topics.reload # force load
+ topics.load # force load
assert_no_queries do
topics.first
topics.last
@@ -178,7 +178,7 @@ class NamedScopingTest < ActiveRecord::TestCase
topics = Topic.base
assert_queries(2) do
topics.empty? # use count query
- topics.collect # force load
+ topics.load # force load
topics.empty? # use loaded (no query)
end
end
@@ -187,7 +187,7 @@ class NamedScopingTest < ActiveRecord::TestCase
topics = Topic.base
assert_queries(2) do
topics.any? # use count query
- topics.collect # force load
+ topics.load # force load
topics.any? # use loaded (no query)
end
end
@@ -203,7 +203,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_any_should_not_fire_query_if_scope_loaded
topics = Topic.base
- topics.collect # force load
+ topics.load # force load
assert_no_queries { assert topics.any? }
end
@@ -217,7 +217,7 @@ class NamedScopingTest < ActiveRecord::TestCase
topics = Topic.base
assert_queries(2) do
topics.many? # use count query
- topics.collect # force load
+ topics.load # force load
topics.many? # use loaded (no query)
end
end
@@ -233,7 +233,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_many_should_not_fire_query_if_scope_loaded
topics = Topic.base
- topics.collect # force load
+ topics.load # force load
assert_no_queries { assert topics.many? }
end
@@ -384,7 +384,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_size_should_use_length_when_results_are_loaded
topics = Topic.base
- topics.reload # force load
+ topics.load # force load
assert_no_queries do
topics.size # use loaded (no query)
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index a1ae57fdbb..3fbff7664b 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -10,7 +10,7 @@ require "models/person"
require "models/reference"
class RelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+ fixtures :authors, :author_addresses, :developers, :projects, :comments, :posts, :developers_projects
setup do
developers(:david)
@@ -238,7 +238,7 @@ class RelationScopingTest < ActiveRecord::TestCase
end
class NestedRelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts
+ fixtures :authors, :author_addresses, :developers, :projects, :comments, :posts
def test_merge_options
Developer.where("salary = 80000").scoping do
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 5653fd83fd..baa41a3a47 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -350,6 +350,14 @@ module ActiveRecord
ENV["VERBOSE"], ENV["VERSION"] = verbose, version
end
+ def test_migrate_raise_error_on_empty_version
+ version = ENV["VERSION"]
+ ENV["VERSION"] = ""
+ assert_raise(RuntimeError, "Empty VERSION provided") { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ ensure
+ ENV["VERSION"] = version
+ end
+
def test_migrate_clears_schema_cache_afterward
ActiveRecord::Base.expects(:clear_cache!)
ActiveRecord::Tasks::DatabaseTasks.migrate
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index a23100c32a..512388af6b 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -217,17 +217,21 @@ if current_adapter?(:PostgreSQLAdapter)
class PostgreSQLStructureDumpTest < ActiveRecord::TestCase
def setup
- @connection = stub(structure_dump: true)
+ @connection = stub(schema_search_path: nil, structure_dump: true)
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
- @filename = "awesome-file.sql"
+ @filename = "/tmp/awesome-file.sql"
+ FileUtils.touch(@filename)
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
Kernel.stubs(:system)
- File.stubs(:open)
+ end
+
+ def teardown
+ FileUtils.rm_f(@filename)
end
def test_structure_dump
@@ -236,6 +240,15 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
end
+ def test_structure_dump_header_comments_removed
+ Kernel.stubs(:system).returns(true)
+ File.write(@filename, "-- header comment\n\n-- more header comment\n statement \n-- lower comment\n")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+
+ assert_equal [" statement \n", "-- lower comment\n"], File.readlines(@filename).first(2)
+ end
+
def test_structure_dump_with_extra_flags
expected_command = ["pg_dump", "-s", "-x", "-O", "-f", @filename, "--noop", "my-app-db"]
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 391bbe8877..eaa4dd09a9 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -551,3 +551,43 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
assert_equal [:rollback], @topic.history
end
end
+
+class CallbacksOnActionAndConditionTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class TopicWithCallbacksOnActionAndCondition < ActiveRecord::Base
+ self.table_name = :topics
+
+ after_commit(on: [:create, :update], if: :run_callback?) { |record| record.history << :create_or_update }
+
+ def clear_history
+ @history = []
+ end
+
+ def history
+ @history ||= []
+ end
+
+ def run_callback?
+ self.history << :run_callback?
+ true
+ end
+
+ attr_accessor :save_before_commit_history, :update_title
+ end
+
+ def test_callback_on_action_with_condition
+ topic = TopicWithCallbacksOnActionAndCondition.new
+ topic.save
+ assert_equal [:run_callback?, :create_or_update], topic.history
+
+ topic.clear_history
+ topic.approved = true
+ topic.save
+ assert_equal [:run_callback?, :create_or_update], topic.history
+
+ topic.clear_history
+ topic.destroy
+ assert_equal [], topic.history
+ end
+end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 111495c481..5c6d78b574 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -10,7 +10,7 @@ require "models/movie"
class TransactionTest < ActiveRecord::TestCase
self.use_transactional_tests = false
- fixtures :topics, :developers, :authors, :posts
+ fixtures :topics, :developers, :authors, :author_addresses, :posts
def setup
@first, @second = Topic.find(1, 2).sort_by(&:id)
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 277280b42e..28605d2f8e 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -385,7 +385,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
def test_validate_uniqueness_with_limit_and_utf8
if current_adapter?(:SQLite3Adapter)
- # Event.title has limit 5, but does SQLite doesn't truncate.
+ # Event.title has limit 5, but SQLite doesn't truncate.
e1 = Event.create(title: "一二三四五六七八")
assert e1.valid?, "Could not create an event with a unique 8 characters title"
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 07288568e8..1d21a2454f 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -154,7 +154,9 @@ if ActiveRecord::Base.connection.supports_views?
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
- if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ if current_adapter?(:Mysql2Adapter, :SQLServerAdapter) ||
+ current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.postgresql_version >= 90300
+
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :books
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 1571b31329..ab0e67cd9d 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -5,7 +5,7 @@ require "models/post"
require "models/author"
class YamlSerializationTest < ActiveRecord::TestCase
- fixtures :topics, :authors, :posts
+ fixtures :topics, :authors, :author_addresses, :posts
def test_to_yaml_with_time_with_zone_should_not_raise_exception
with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do
diff --git a/activerecord/test/models/essay.rb b/activerecord/test/models/essay.rb
index 13267fbc21..1f9772870e 100644
--- a/activerecord/test/models/essay.rb
+++ b/activerecord/test/models/essay.rb
@@ -1,4 +1,5 @@
class Essay < ActiveRecord::Base
+ belongs_to :author
belongs_to :writer, primary_key: :name, polymorphic: true
belongs_to :category, primary_key: :name
has_one :owner, primary_key: :name
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index e74aedb814..a2028b3eb9 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -59,6 +59,10 @@ class Post < ActiveRecord::Base
def the_association
proxy_association
end
+
+ def with_content(content)
+ self.detect { |comment| comment.body == content }
+ end
end
has_many :comments_with_extend, extend: NamedExtension, class_name: "Comment", foreign_key: "post_id" do
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 860c63b27c..e56e8fa36a 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -3,11 +3,13 @@ ActiveRecord::Schema.define do
enable_extension!("uuid-ossp", ActiveRecord::Base.connection)
enable_extension!("pgcrypto", ActiveRecord::Base.connection) if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
- create_table :uuid_parents, id: :uuid, force: true do |t|
+ uuid_default = connection.supports_pgcrypto_uuid? ? {} : { default: "uuid_generate_v4()" }
+
+ create_table :uuid_parents, id: :uuid, force: true, **uuid_default do |t|
t.string :name
end
- create_table :uuid_children, id: :uuid, force: true do |t|
+ create_table :uuid_children, id: :uuid, force: true, **uuid_default do |t|
t.string :name
t.uuid :uuid_parent_id
end
@@ -102,7 +104,7 @@ _SQL
end
create_table :uuid_items, force: true, id: false do |t|
- t.uuid :uuid, primary_key: true
+ t.uuid :uuid, primary_key: true, **uuid_default
t.string :title
end
end
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index bc5af36a28..1a609e13c3 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -10,7 +10,10 @@ module ARTest
end
def self.connection_config
- config["connections"][connection_name]
+ config.fetch("connections").fetch(connection_name) do
+ puts "Connection #{connection_name.inspect} not found. Available connections: #{config['connections'].keys.join(', ')}"
+ exit 1
+ end
end
def self.connect
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 6dc301e5b4..6146dd989f 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,429 +1,17 @@
-* Soft-deprecated the top-level `HashWithIndifferentAccess` constant.
- `ActiveSupport::HashWithIndifferentAccess` should be used instead.
-
- *Robin Dupret* (#28157)
-
-* In Core Extensions, make `MarshalWithAutoloading#load` pass through the second, optional
- argument for `Marshal#load( source [, proc] )`. This way we don't have to do
- `Marshal.method(:load).super_method.call(sourse, proc)` just to be able to pass a proc.
-
- *Jeff Latz*
-
-* `ActiveSupport::Gzip.decompress` now checks checksum and length in footer.
-
- *Dylan Thacker-Smith*
-
-
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Cache `ActiveSupport::TimeWithZone#to_datetime` before freezing.
-
- *Adam Rice*
-
-* Deprecate `.halt_callback_chains_on_return_false`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated behavior that halts callbacks when the return is false.
-
- *Rafael Mendonça França*
-
-* Deprecate passing string to `:if` and `:unless` conditional options
- on `set_callback` and `skip_callback`.
-
- *Ryuta Kamizono*
-
-* Raise `ArgumentError` when passing string to define callback.
-
- *Ryuta Kamizono*
-
-* Updated Unicode version to 9.0.0
-
- Now we can handle new emojis such like "👩‍👩‍👧‍👦" ("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").
-
- version 8.0.0
-
- "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 4
- "👩‍👩‍👧‍👦".mb_chars.reverse # => "👦👧‍👩‍👩‍"
-
- version 9.0.0
-
- "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 1
- "👩‍👩‍👧‍👦".mb_chars.reverse # => "👩‍👩‍👧‍👦"
-
- *Fumiaki MATSUSHIMA*
-
-* Changed `ActiveSupport::Inflector#transliterate` to raise `ArgumentError` when it receives
- anything except a string.
-
- *Kevin McPhillips*
-
-* Fixed bugs that `StringInquirer#respond_to_missing?` and
- `ArrayInquirer#respond_to_missing?` do not fallback to `super`.
-
- *Akira Matsuda*
-
-* Fix inconsistent results when parsing large durations and constructing durations from code
-
- ActiveSupport::Duration.parse('P3Y') == 3.years # It should be true
-
- Duration parsing made independent from any moment of time:
- Fixed length in seconds is assigned to each duration part during parsing.
-
- Changed duration of months and years in seconds to more accurate and logical:
-
- 1. The value of 365.2425 days in Gregorian year is more accurate
- as it accounts for every 400th non-leap year.
-
- 2. Month's length is bound to year's duration, which makes
- sensible comparisons like `12.months == 1.year` to be `true`
- and nonsensical ones like `30.days == 1.month` to be `false`.
-
- Calculations on times and dates with durations shouldn't be affected as
- duration's numeric value isn't used in calculations, only parts are used.
-
- Methods on `Numeric` like `2.days` now use these predefined durations
- to avoid duplicating of duration constants through the codebase and
- eliminate creation of intermediate durations.
-
- *Andrey Novikov, Andrew White*
-
-* Change return value of `Rational#duplicable?`, `ComplexClass#duplicable?`
- to false.
-
- *utilum*
-
-* Change return value of `NilClass#duplicable?`, `FalseClass#duplicable?`,
- `TrueClass#duplicable?`, `Symbol#duplicable?` and `Numeric#duplicable?`
- to true with Ruby 2.4+. These classes can dup with Ruby 2.4+.
-
- *Yuji Yaginuma*
-
-* Remove deprecated class `ActiveSupport::Concurrency::Latch`.
+* Add support for `:offset` and `:zone` to `ActiveSupport::TimeWithZone#change`
*Andrew White*
-* Remove deprecated separator argument from `parameterize`.
+* Add support for `:offset` to `Time#change`
- *Andrew White*
-
-* Remove deprecated method `Numeric#to_formatted_s`.
-
- *Andrew White*
-
-* Remove deprecated method `alias_method_chain`.
-
- *Andrew White*
-
-* Remove deprecated constant `MissingSourceFile`.
-
- *Andrew White*
-
-* Remove deprecated methods `Module.qualified_const_defined?`,
- `Module.qualified_const_get` and `Module.qualified_const_set`.
-
- *Andrew White*
-
-* Remove deprecated `:prefix` option from `number_to_human_size`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/time/marshal.rb`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/struct.rb`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`.
+ Fixes #28723.
*Andrew White*
-* Remove deprecated method `Module.local_constants`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`.
-
- *Andrew White*
-
-* Ensure duration parsing is consistent across DST changes.
-
- Previously `ActiveSupport::Duration.parse` used `Time.current` and
- `Time#advance` to calculate the number of seconds in the duration
- from an arbitrary collection of parts. However as `advance` tries to
- be consistent across DST boundaries this meant that either the
- duration was shorter or longer depending on the time of year.
-
- This was fixed by using an absolute reference point in UTC which
- isn't subject to DST transitions. An arbitrary date of Jan 1st, 2000
- was chosen for no other reason that it seemed appropriate.
-
- Additionally, duration parsing should now be marginally faster as we
- are no longer creating instances of `ActiveSupport::TimeWithZone`
- every time we parse a duration string.
-
- Fixes #26941.
-
- *Andrew White*
-
-* Use `Hash#compact` and `Hash#compact!` from Ruby 2.4. Old Ruby versions
- will continue to get these methods from Active Support as before.
-
- *Prathamesh Sonpatki*
-
-* Fix `ActiveSupport::TimeZone#strptime`.
- Support for timestamps in format of seconds (%s) and milliseconds (%Q).
-
- Fixes #26840.
-
- *Lev Denisov*
-
-* Fix `DateAndTime::Calculations#copy_time_to`. Copy `nsec` instead of `usec`.
-
- Jumping forward or backward between weeks now preserves nanosecond digits.
-
- *Josua Schmid*
-
-* Fix `ActiveSupport::TimeWithZone#in` across DST boundaries.
-
- Previously calls to `in` were being sent to the non-DST aware
- method `Time#since` via `method_missing`. It is now aliased to
- the DST aware `ActiveSupport::TimeWithZone#+` which handles
- transitions across DST boundaries, e.g:
-
- Time.zone = "US/Eastern"
-
- t = Time.zone.local(2016,11,6,1)
- # => Sun, 06 Nov 2016 01:00:00 EDT -05:00
-
- t.in(1.hour)
- # => Sun, 06 Nov 2016 01:00:00 EST -05:00
-
- Fixes #26580.
-
- *Thomas Balthazar*
-
-* Remove unused parameter `options = nil` for `#clear` of
- `ActiveSupport::Cache::Strategy::LocalCache::LocalStore` and
- `ActiveSupport::Cache::Strategy::LocalCache`.
-
- *Yosuke Kabuto*
-
-* Fix `thread_mattr_accessor` subclass no longer overwrites parent.
-
- Assigning a value to a subclass using `thread_mattr_accessor` no
- longer changes the value of the parent class. This brings the
- behavior inline with the documentation.
-
- Given:
-
- class Account
- thread_mattr_accessor :user
- end
-
- class Customer < Account
- end
-
- Account.user = "DHH"
- Customer.user = "Rafael"
-
- Before:
-
- Account.user # => "Rafael"
-
- After:
-
- Account.user # => "DHH"
-
- *Shinichi Maeshima*
-
-* Since weeks are no longer converted to days, add `:weeks` to the list of
- parts that `ActiveSupport::TimeWithZone` will recognize as possibly being
- of variable duration to take account of DST transitions.
-
- Fixes #26039.
-
- *Andrew White*
-
-* Defines `Regexp.match?` for Ruby versions prior to 2.4. The predicate
- has the same interface, but it does not have the performance boost. Its
- purpose is to be able to write 2.4 compatible code.
-
- *Xavier Noria*
-
-* Allow `MessageEncryptor` to take advantage of authenticated encryption modes.
-
- AEAD modes like `aes-256-gcm` provide both confidentiality and data
- authenticity, eliminating the need to use `MessageVerifier` to check if the
- encrypted data has been tampered with. This speeds up encryption/decryption
- and results in shorter cipher text.
-
- *Bart de Water*
-
-* Introduce `assert_changes` and `assert_no_changes`.
-
- `assert_changes` is a more general `assert_difference` that works with any
- value.
-
- assert_changes 'Error.current', from: nil, to: 'ERR' do
- expected_bad_operation
- end
-
- Can be called with strings, to be evaluated in the binding (context) of
- the block given to the assertion, or a lambda.
-
- assert_changes -> { Error.current }, from: nil, to: 'ERR' do
- expected_bad_operation
- end
-
- The `from` and `to` arguments are compared with the case operator (`===`).
-
- assert_changes 'Error.current', from: nil, to: Error do
- expected_bad_operation
- end
-
- This is pretty useful, if you need to loosely compare a value. For example,
- you need to test a token has been generated and it has that many random
- characters.
-
- user = User.start_registration
- assert_changes 'user.token', to: /\w{32}/ do
- user.finish_registration
- end
-
- *Genadi Samokovarov*
-
-* Fix `ActiveSupport::TimeZone#strptime`. Now raises `ArgumentError` when the
- given time doesn't match the format. The error is the same as the one given
- by Ruby's `Date.strptime`. Previously it raised
- `NoMethodError: undefined method empty? for nil:NilClass.` due to a bug.
-
- Fixes #25701.
-
- *John Gesimondo*
-
-* `travel/travel_to` travel time helpers, now raise on nested calls,
- as this can lead to confusing time stubbing.
-
- Instead of:
-
- travel_to 2.days.from_now do
- # 2 days from today
- travel_to 3.days.from_now do
- # 5 days from today
- end
- end
-
- preferred way to achieve above is:
-
- travel 2.days do
- # 2 days from today
- end
-
- travel 5.days do
- # 5 days from today
- end
-
- *Vipul A M*
-
-* Support parsing JSON time in ISO8601 local time strings in
- `ActiveSupport::JSON.decode` when `parse_json_times` is enabled.
- Strings in the format of `YYYY-MM-DD hh:mm:ss` (without a `Z` at
- the end) will be parsed in the local timezone (`Time.zone`). In
- addition, date strings (`YYYY-MM-DD`) are now parsed into `Date`
- objects.
-
- *Grzegorz Witek*
-
-* Fixed `ActiveSupport::Logger.broadcast` so that calls to `#silence` now
- properly delegate to all loggers. Silencing now properly suppresses logging
- to both the log and the console.
-
- *Kevin McPhillips*
-
-* Remove deprecated arguments in `assert_nothing_raised`.
-
- *Rafel Mendonça França*
-
-* `Date.to_s` doesn't produce too many spaces. For example, `to_s(:short)`
- will now produce `01 Feb` instead of ` 1 Feb`.
-
- Fixes #25251.
-
- *Sean Griffin*
-
-* Introduce `Module#delegate_missing_to`.
-
- When building a decorator, a common pattern emerges:
-
- class Partition
- def initialize(first_event)
- @events = [ first_event ]
- end
-
- def people
- if @events.first.detail.people.any?
- @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- else
- @events.collect(&:creator).uniq
- end
- end
-
- private
- def respond_to_missing?(name, include_private = false)
- @events.respond_to?(name, include_private)
- end
-
- def method_missing(method, *args, &block)
- @events.send(method, *args, &block)
- end
- end
-
- With `Module#delegate_missing_to`, the above is condensed to:
-
- class Partition
- delegate_missing_to :@events
-
- def initialize(first_event)
- @events = [ first_event ]
- end
-
- def people
- if @events.first.detail.people.any?
- @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- else
- @events.collect(&:creator).uniq
- end
- end
- end
-
- *Genadi Samokovarov*, *DHH*
+* Add `fetch_values` for `HashWithIndifferentAccess`
-* Rescuable: If a handler doesn't match the exception, check for handlers
- matching the exception's cause.
+ The method was originally added to `Hash` in Ruby 2.3.0.
- *Jeremy Daer*
+ *Josh Pencheon*
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index fea072d91c..56fe1457d0 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -28,6 +28,7 @@ module ActiveSupport
@pruning = false
end
+ # Delete all data stored in a given cache store.
def clear(options = nil)
synchronize do
@data.clear
@@ -83,6 +84,7 @@ module ActiveSupport
modify_value(name, -amount, options)
end
+ # Deletes cache entries if the cache key matches a given pattern.
def delete_matched(matcher, options = nil)
options = merged_options(options)
instrument(:delete_matched, matcher.inspect) do
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
index 174cb72b1e..4c3679e4bf 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
@@ -28,13 +28,13 @@ module ActiveSupport
response[2] = ::Rack::BodyProxy.new(response[2]) do
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
end
+ cleanup_on_body_close = true
response
rescue Rack::Utils::InvalidParameterError
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
[400, {}, []]
- rescue Exception
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
- raise
+ ensure
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
+ cleanup_on_body_close
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
index 16a6789f8d..88a34128c9 100644
--- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
+++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
@@ -1,7 +1,7 @@
class Array
# The human way of thinking about adding stuff to the end of a list is with append.
- alias_method :append, :<<
+ alias_method :append, :push unless [].respond_to?(:append)
# The human way of thinking about adding stuff to the beginning of a list is with prepend.
- alias_method :prepend, :unshift
+ alias_method :prepend, :unshift unless [].respond_to?(:prepend)
end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index d553406dff..0f59c754fe 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -79,6 +79,9 @@ class Date
# date.to_time(:local) # => 2007-11-10 00:00:00 0800
#
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
+ #
+ # NOTE: The :local timezone is Ruby's *process* timezone, i.e. ENV['TZ'].
+ # If the *application's* timezone is needed, then use +in_time_zone+ instead.
def to_time(form = :local)
raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
::Time.send(form, year, month, day)
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
index db95ae0db5..ab80392460 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -10,13 +10,5 @@ module DateAndTime
# this behavior, but new apps will have an initializer that sets
# this to true, because the new behavior is preferred.
mattr_accessor(:preserve_timezone, instance_writer: false) { false }
-
- def to_time
- if preserve_timezone
- @_to_time_with_instance_offset ||= getlocal(utc_offset)
- else
- @_to_time_with_system_offset ||= getlocal
- end
- end
end
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 70d5c9af8e..7a9eb8c266 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -47,13 +47,23 @@ class DateTime
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0)
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0)
def change(options)
+ if new_nsec = options[:nsec]
+ raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
+ new_fraction = Rational(new_nsec, 1000000000)
+ else
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+ new_fraction = Rational(new_usec, 1000000)
+ end
+
+ raise ArgumentError, "argument out of range" if new_fraction >= 1
+
::DateTime.civil(
options.fetch(:year, year),
options.fetch(:month, month),
options.fetch(:day, day),
options.fetch(:hour, hour),
options.fetch(:min, options[:hour] ? 0 : min),
- options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction,
options.fetch(:offset, offset),
options.fetch(:start, start)
)
@@ -122,7 +132,7 @@ class DateTime
# Returns a new DateTime representing the end of the day (23:59:59).
def end_of_day
- change(hour: 23, min: 59, sec: 59)
+ change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_day :end_of_day
@@ -134,7 +144,7 @@ class DateTime
# Returns a new DateTime representing the end of the hour (hh:59:59).
def end_of_hour
- change(min: 59, sec: 59)
+ change(min: 59, sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_hour :end_of_hour
@@ -146,7 +156,7 @@ class DateTime
# Returns a new DateTime representing the end of the minute (hh:mm:59).
def end_of_minute
- change(sec: 59)
+ change(sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_minute :end_of_minute
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
index 30bb7f4a60..eb8b8b2c65 100644
--- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -1,5 +1,15 @@
require "active_support/core_ext/date_and_time/compatibility"
class DateTime
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ remove_possible_method :to_time
+
+ # Either return an instance of `Time` with the same UTC offset
+ # as +self+ or an instance of `Time` representing the same time
+ # in the the local system timezone depending on the setting of
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 90d7d2947f..4f120d4b45 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -1,28 +1,50 @@
module Enumerable
- # Calculates a sum from the elements.
+ # Enumerable#sum was added in Ruby 2.4 but it only works with Numeric elements
+ # when we omit an identity.
#
- # payments.sum { |p| p.price * p.tax_rate }
- # payments.sum(&:price)
- #
- # The latter is a shortcut for:
- #
- # payments.inject(0) { |sum, p| sum + p.price }
- #
- # It can also calculate the sum without the use of a block.
- #
- # [5, 15, 10].sum # => 30
- # ['foo', 'bar'].sum # => "foobar"
- # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
- #
- # The default sum of an empty list is zero. You can override this default:
- #
- # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def sum(identity = nil, &block)
- if block_given?
- map(&block).sum(identity)
- else
- sum = identity ? inject(identity, :+) : inject(:+)
- sum || identity || 0
+ # We tried shimming it to attempt the fast native method, rescue TypeError,
+ # and fall back to the compatible implementation, but that's much slower than
+ # just calling the compat method in the first place.
+ if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false)
+ # We can't use Refinements here because Refinements with Module which will be prepended
+ # doesn't work well https://bugs.ruby-lang.org/issues/13446
+ alias :_original_sum_with_required_identity :sum
+ private :_original_sum_with_required_identity
+ # Calculates a sum from the elements.
+ #
+ # payments.sum { |p| p.price * p.tax_rate }
+ # payments.sum(&:price)
+ #
+ # The latter is a shortcut for:
+ #
+ # payments.inject(0) { |sum, p| sum + p.price }
+ #
+ # It can also calculate the sum without the use of a block.
+ #
+ # [5, 15, 10].sum # => 30
+ # ['foo', 'bar'].sum # => "foobar"
+ # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
+ #
+ # The default sum of an empty list is zero. You can override this default:
+ #
+ # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
+ def sum(identity = nil, &block)
+ if identity
+ _original_sum_with_required_identity(identity, &block)
+ elsif block_given?
+ map(&block).sum(identity)
+ else
+ inject(:+) || 0
+ end
+ end
+ else
+ def sum(identity = nil, &block)
+ if block_given?
+ map(&block).sum(identity)
+ else
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
index efb9d1b8a0..061c959442 100644
--- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -12,6 +12,7 @@ class Hash
def reverse_merge(other_hash)
other_hash.merge(self)
end
+ alias_method :with_defaults, :reverse_merge
# Destructive +reverse_merge+.
def reverse_merge!(other_hash)
@@ -19,4 +20,5 @@ class Hash
merge!(other_hash) { |key, left, right| left }
end
alias_method :reverse_update, :reverse_merge!
+ alias_method :with_defaults!, :reverse_merge!
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index cdf27f49ad..85ab739095 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -267,7 +267,10 @@ class Module
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def respond_to_missing?(name, include_private = false)
- #{target}.respond_to?(name, include_private)
+ # It may look like an oversight, but we deliberately do not pass
+ # +include_private+, because they do not get delegated.
+
+ #{target}.respond_to?(name) || super
end
def method_missing(method, *args, &block)
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index ea81df2bd8..b028df97ee 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -106,8 +106,8 @@ require "bigdecimal"
class BigDecimal
# BigDecimals are duplicable:
#
- # BigDecimal.new("1.2").duplicable? # => true
- # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
+ # BigDecimal.new("1.2").duplicable? # => true
+ # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
@@ -124,21 +124,31 @@ class Method
end
class Complex
- # Complexes are not duplicable:
- #
- # Complex(1).duplicable? # => false
- # Complex(1).dup # => TypeError: can't copy Complex
- def duplicable?
- false
+ begin
+ Complex(1).dup
+ rescue TypeError
+
+ # Complexes are not duplicable:
+ #
+ # Complex(1).duplicable? # => false
+ # Complex(1).dup # => TypeError: can't copy Complex
+ def duplicable?
+ false
+ end
end
end
class Rational
- # Rationals are not duplicable:
- #
- # Rational(1).duplicable? # => false
- # Rational(1).dup # => TypeError: can't copy Rational
- def duplicable?
- false
+ begin
+ Rational(1).dup
+ rescue TypeError
+
+ # Rationals are not duplicable:
+ #
+ # Rational(1).duplicable? # => false
+ # Rational(1).dup # => TypeError: can't copy Rational
+ def duplicable?
+ false
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index cf39b1d312..3a44e08630 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -62,6 +62,17 @@ class Object
#
# Hence the inherited default for `if` key is ignored.
#
+ # NOTE: You cannot call class methods implicitly inside of with_options.
+ # You can access these methods using the class name instead:
+ #
+ # class Phone < ActiveRecord::Base
+ # enum phone_number_type: [home: 0, office: 1, mobile: 2]
+ #
+ # with_options presence: true do
+ # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
+ # end
+ # end
+ #
def with_options(options, &block)
option_merger = ActiveSupport::OptionMerger.new(self, options)
block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 7a2fc5c1b5..b27cfe53f4 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -100,12 +100,17 @@ class String
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
# used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
- def titleize
- ActiveSupport::Inflector.titleize(self)
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
+ # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(keep_id_suffix: false)
+ ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
end
alias_method :titlecase, :titleize
@@ -202,7 +207,7 @@ class String
ActiveSupport::Inflector.classify(self)
end
- # Capitalizes the first word, turns underscores into spaces, and strips a
+ # Capitalizes the first word, turns underscores into spaces, and (by default)strips a
# trailing '_id' if present.
# Like +titleize+, this is meant for creating pretty output.
#
@@ -210,12 +215,17 @@ class String
# optional parameter +capitalize+ to false.
# By default, this parameter is true.
#
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- # 'author_id'.humanize(capitalize: false) # => "author"
- # '_id'.humanize # => "Id"
- def humanize(options = {})
- ActiveSupport::Inflector.humanize(self, options)
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
+ # 'employee_salary'.humanize # => "Employee salary"
+ # 'author_id'.humanize # => "Author"
+ # 'author_id'.humanize(capitalize: false) # => "author"
+ # '_id'.humanize # => "Id"
+ # 'author_id'.humanize(keep_id_suffix: true) # => "Author Id"
+ def humanize(capitalize: true, keep_id_suffix: false)
+ ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
end
# Converts just the first character to uppercase.
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index cbdcb86d6d..d3f23f4663 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -53,6 +53,29 @@ class Time
end
alias_method :at_without_coercion, :at
alias_method :at, :at_with_coercion
+
+ # Creates a +Time+ instance from an RFC 3339 string.
+ #
+ # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000
+ #
+ # If the time or offset components are missing then an +ArgumentError+ will be raised.
+ #
+ # Time.rfc3339('1999-12-31') # => ArgumentError: invalid date
+ def rfc3339(str)
+ parts = Date._rfc3339(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour),
+ parts.fetch(:min),
+ parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset)
+ )
+ end
end
# Returns the number of seconds since 00:00:00.
@@ -84,21 +107,22 @@ class Time
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
# the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour
- # and minute is passed, then sec, usec and nsec is set to 0. The +options+
- # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
- # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>
- # <tt>:nsec</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
+ # and minute is passed, then sec, usec and nsec is set to 0. The +options+ parameter
+ # takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>,
+ # <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>,
+ # <tt>:offset</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
#
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
def change(options)
- new_year = options.fetch(:year, year)
- new_month = options.fetch(:month, month)
- new_day = options.fetch(:day, day)
- new_hour = options.fetch(:hour, hour)
- new_min = options.fetch(:min, options[:hour] ? 0 : min)
- new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_year = options.fetch(:year, year)
+ new_month = options.fetch(:month, month)
+ new_day = options.fetch(:day, day)
+ new_hour = options.fetch(:hour, hour)
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_offset = options.fetch(:offset, nil)
if new_nsec = options[:nsec]
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
@@ -107,13 +131,18 @@ class Time
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
end
- if utc?
- ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ raise ArgumentError, "argument out of range" if new_usec >= 1000000
+
+ new_sec += Rational(new_usec, 1000000)
+
+ if new_offset
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset)
+ elsif utc?
+ ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
elsif zone
- ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec)
else
- raise ArgumentError, "argument out of range" if new_usec >= 1000000
- ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
index ca4b9574d5..45e86b77ce 100644
--- a/activesupport/lib/active_support/core_ext/time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -1,5 +1,14 @@
require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/remove_method"
class Time
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ remove_possible_method :to_time
+
+ # Either return +self+ or the time in the local system timezone depending
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? self : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index f2bbe55aa6..595bda6b4f 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -64,4 +64,7 @@ class Time
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
+
+ # Aliased to +xmlschema+ for compatibility with +DateTime+
+ alias_method :rfc3339, :xmlschema
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 191e582de8..72c74e966a 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -15,6 +15,7 @@ module ActiveSupport
require "active_support/deprecation/instance_delegator"
require "active_support/deprecation/behaviors"
require "active_support/deprecation/reporting"
+ require "active_support/deprecation/constant_accessor"
require "active_support/deprecation/method_wrappers"
require "active_support/deprecation/proxy_wrappers"
require "active_support/core_ext/module/deprecation"
@@ -32,7 +33,7 @@ module ActiveSupport
# and the second is a library name
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = "5.2", gem_name = "Rails")
+ def initialize(deprecation_horizon = "5.3", 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/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb
new file mode 100644
index 0000000000..2b19de365f
--- /dev/null
+++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb
@@ -0,0 +1,50 @@
+require "active_support/inflector/methods"
+
+module ActiveSupport
+ class Deprecation
+ # DeprecatedConstantAccessor transforms a constant into a deprecated one by
+ # hooking +const_missing+.
+ #
+ # It takes the names of an old (deprecated) constant and of a new constant
+ # (both in string form) and optionally a deprecator. The deprecator defaults
+ # to +ActiveSupport::Deprecator+ if none is specified.
+ #
+ # The deprecated constant now returns the same object as the new one rather
+ # than a proxy object, so it can be used transparently in +rescue+ blocks
+ # etc.
+ #
+ # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
+ #
+ # (In a later update, the original implementation of `PLANETS` has been removed.)
+ #
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # include ActiveSupport::Deprecation::DeprecatedConstantAccessor
+ # deprecate_constant 'PLANETS', 'PLANETS_POST_2006'
+ #
+ # PLANETS.map { |planet| planet.capitalize }
+ # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
+ # (Backtrace information…)
+ # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
+ module DeprecatedConstantAccessor
+ def self.included(base)
+ extension = Module.new do
+ def const_missing(missing_const_name)
+ if class_variable_defined?(:@@_deprecated_constants)
+ if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s])
+ replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations)
+ return ActiveSupport::Inflector.constantize(replacement[:new].to_s)
+ end
+ end
+ super
+ end
+
+ def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance)
+ class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
+ class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
+ end
+ end
+ base.singleton_class.prepend extension
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 003f6203ef..99080e34a1 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,5 +1,8 @@
require "active_support/core_ext/array/conversions"
+require "active_support/core_ext/module/delegation"
require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/string/filters"
+require "active_support/deprecation"
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
@@ -7,6 +10,66 @@ module ActiveSupport
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
class Duration
+ class Scalar < Numeric #:nodoc:
+ attr_reader :value
+ delegate :to_i, :to_f, :to_s, to: :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def coerce(other)
+ [Scalar.new(other), self]
+ end
+
+ def -@
+ Scalar.new(-value)
+ end
+
+ def <=>(other)
+ if Scalar === other || Duration === other
+ value <=> other.value
+ elsif Numeric === other
+ value <=> other
+ else
+ nil
+ end
+ end
+
+ def +(other)
+ calculate(:+, other)
+ end
+
+ def -(other)
+ calculate(:-, other)
+ end
+
+ def *(other)
+ calculate(:*, other)
+ end
+
+ def /(other)
+ calculate(:/, other)
+ end
+
+ private
+ def calculate(op, other)
+ if Scalar === other
+ Scalar.new(value.public_send(op, other.value))
+ elsif Duration === other
+ Duration.seconds(value).public_send(op, other)
+ elsif Numeric === other
+ Scalar.new(value.public_send(op, other))
+ else
+ raise_type_error(other)
+ end
+ end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
+ end
+ end
+
SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = 3600
SECONDS_PER_DAY = 86400
@@ -88,6 +151,24 @@ module ActiveSupport
@parts.default = 0
end
+ def coerce(other) #:nodoc:
+ if Scalar === other
+ [other, self]
+ else
+ [Scalar.new(other), self]
+ end
+ end
+
+ # Compares one Duration with another or a Numeric to this Duration.
+ # Numeric values are treated as seconds.
+ def <=>(other)
+ if Duration === other
+ value <=> other.value
+ elsif Numeric === other
+ value <=> other
+ end
+ end
+
# Adds another Duration or a Numeric to this Duration. Numeric values
# are treated as seconds.
def +(other)
@@ -109,6 +190,28 @@ module ActiveSupport
self + (-other)
end
+ # Multiplies this Duration by a Numeric and returns a new Duration.
+ def *(other)
+ if Scalar === other || Duration === other
+ Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
+ elsif Numeric === other
+ Duration.new(value * other, parts.map { |type, number| [type, number * other] })
+ else
+ raise_type_error(other)
+ end
+ end
+
+ # Divides this Duration by a Numeric and returns a new Duration.
+ def /(other)
+ if Scalar === other || Duration === other
+ Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
+ elsif Numeric === other
+ Duration.new(value / other, parts.map { |type, number| [type, number / other] })
+ else
+ raise_type_error(other)
+ end
+ end
+
def -@ #:nodoc:
Duration.new(-value, parts.map { |type, number| [type, -number] })
end
@@ -180,6 +283,7 @@ module ActiveSupport
sum(1, time)
end
alias :from_now :since
+ alias :after :since
# Calculates a new Time or Date that is as far in the past
# as this Duration represents.
@@ -187,6 +291,7 @@ module ActiveSupport
sum(-1, time)
end
alias :until :ago
+ alias :before :ago
def inspect #:nodoc:
parts.
@@ -210,8 +315,6 @@ module ActiveSupport
ISO8601Serializer.new(self, precision: precision).serialize
end
- delegate :<=>, to: :value
-
private
def sum(sign, time = ::Time.current)
@@ -235,5 +338,9 @@ module ActiveSupport
def method_missing(method, *args, &block)
value.send(method, *args, &block)
end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
+ end
end
end
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 8e0dc71dca..f59f5d17dc 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -125,6 +125,11 @@ module ActiveSupport
dtw.compact!
dtw.uniq!
+ normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
+ dtw = dtw.reject do |path|
+ normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
+ end
+
@ph.filter_out_descendants(dtw)
end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index a641b96c57..371a39a5e6 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
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 1927cddf34..3b185dd86b 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -195,6 +195,19 @@ module ActiveSupport
indices.collect { |key| self[convert_key(key)] }
end
+ # Returns an array of the values at the specified indices, but also
+ # raises an exception when one of the keys can't be found.
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash[:a] = 'x'
+ # hash[:b] = 'y'
+ # hash.fetch_values('a', 'b') # => ["x", "y"]
+ # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
+ # hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
+ def fetch_values(*indices, &block)
+ indices.collect { |key| fetch(key, &block) }
+ end if Hash.method_defined?(:fetch_values)
+
# Returns a shallow copy of the hash.
#
# hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
@@ -225,11 +238,13 @@ module ActiveSupport
def reverse_merge(other_hash)
super(self.class.new(other_hash))
end
+ alias_method :with_defaults, :reverse_merge
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash)
replace(reverse_merge(other_hash))
end
+ alias_method :with_defaults!, :reverse_merge!
# Replaces the contents of this hash with other_hash.
#
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 8ccb735c6d..1b089a7538 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -108,33 +108,38 @@ module ActiveSupport
# * Replaces underscores with spaces, if any.
# * Downcases all words except acronyms.
# * Capitalizes the first word.
- #
# The capitalization of the first word can be turned off by setting the
# +:capitalize+ option to false (default is true).
#
- # humanize('employee_salary') # => "Employee salary"
- # humanize('author_id') # => "Author"
- # humanize('author_id', capitalize: false) # => "author"
- # humanize('_id') # => "Id"
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true (default is false).
+ #
+ # humanize('employee_salary') # => "Employee salary"
+ # humanize('author_id') # => "Author"
+ # humanize('author_id', capitalize: false) # => "author"
+ # humanize('_id') # => "Id"
+ # humanize('author_id', keep_id_suffix: true) # => "Author Id"
#
# If "SSL" was defined to be an acronym:
#
# humanize('ssl_error') # => "SSL error"
#
- def humanize(lower_case_and_underscored_word, options = {})
+ def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
result = lower_case_and_underscored_word.to_s.dup
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result.sub!(/\A_+/, "".freeze)
- result.sub!(/_id\z/, "".freeze)
+ unless keep_id_suffix
+ result.sub!(/_id\z/, "".freeze)
+ end
result.tr!("_".freeze, " ".freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
end
- if options.fetch(:capitalize, true)
+ if capitalize
result.sub!(/\A\w/) { |match| match.upcase }
end
@@ -154,14 +159,21 @@ module ActiveSupport
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # titleize('man from the boondocks') # => "Man From The Boondocks"
- # titleize('x-men: the last stand') # => "X Men: The Last Stand"
- # titleize('TheManWithoutAPast') # => "The Man Without A Past"
- # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
- def titleize(word)
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
+ # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(word, keep_id_suffix: false)
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`])[a-z]/) do |match|
+ match.capitalize
+ end
end
# Creates the name of a table like Rails does for models to table names.
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 0671469788..24053b4fe5 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -50,6 +50,11 @@ module ActiveSupport
# key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
# derivation function.
#
+ # First additional parameter is used as the signature key for +MessageVerifier+.
+ # This allows you to specify keys to encrypt and sign data.
+ #
+ # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
+ #
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
@@ -61,7 +66,7 @@ module ActiveSupport
sign_secret = signature_key_or_options.first
@secret = secret
@sign_secret = sign_secret
- @cipher = options[:cipher] || "aes-256-cbc"
+ @cipher = options[:cipher] || DEFAULT_CIPHER
@digest = options[:digest] || "SHA1" unless aead_mode?
@verifier = resolve_verifier
@serializer = options[:serializer] || Marshal
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 07c9be0604..3d9ff99729 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,4 +1,5 @@
require "active_support/core_ext/string/strip" # for strip_heredoc
+require "active_support/core_ext/time/calculations"
require "concurrent/map"
module ActiveSupport
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 857cc1a664..ecb9fb6785 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -148,6 +148,7 @@ module ActiveSupport
"#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}"
end
alias_method :iso8601, :xmlschema
+ alias_method :rfc3339, :xmlschema
# Coerces time to a string for JSON encoding. The default format is ISO 8601.
# You can get %Y/%m/%d %H:%M:%S +offset style by setting
@@ -329,6 +330,42 @@ module ActiveSupport
since(-other)
end
+ # Returns a new +ActiveSupport::TimeWithZone+ where one or more of the elements have
+ # been changed according to the +options+ parameter. The time options (<tt>:hour</tt>,
+ # <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly,
+ # so if only the hour is passed, then minute, sec, usec and nsec is set to 0. If the
+ # hour and minute is passed, then sec, usec and nsec is set to 0. The +options+
+ # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
+ # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>,
+ # <tt>:nsec</tt>, <tt>:offset</tt>, <tt>:zone</tt>. Pass either <tt>:usec</tt>
+ # or <tt>:nsec</tt>, not both. Similarly, pass either <tt>:zone</tt> or
+ # <tt>:offset</tt>, not both.
+ #
+ # t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15 EST -05:00
+ # t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15 EST -05:00
+ # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00 EST -05:00
+ # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00 EST -05:00
+ # t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15 HST -10:00
+ # t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15 HST -10:00
+ def change(options)
+ if options[:zone] && options[:offset]
+ raise ArgumentError, "Can't change both :offset and :zone at the same time: #{options.inspect}"
+ end
+
+ new_time = time.change(options)
+
+ if options[:zone]
+ new_zone = ::Time.find_zone(options[:zone])
+ elsif options[:offset]
+ new_zone = ::Time.find_zone(new_time.utc_offset)
+ end
+
+ new_zone ||= time_zone
+ periods = new_zone.periods_for_local(new_time)
+
+ self.class.new(nil, new_zone, new_time, periods.include?(period) ? period : nil)
+ end
+
# Uses Date to provide precise Time calculations for years, months, and days
# according to the proleptic Gregorian calendar. The result is returned as a
# new TimeWithZone object.
@@ -410,6 +447,17 @@ module ActiveSupport
@to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
+ # Returns an instance of +Time+, either with the same UTC offset
+ # as +self+ or in the local system timezone depending on the setting
+ # of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ if preserve_timezone
+ @to_time_with_instance_offset ||= getlocal(utc_offset)
+ else
+ @to_time_with_system_offset ||= getlocal
+ end
+ end
+
# So that +self+ <tt>acts_like?(:time)</tt>.
def acts_like_time?
true
@@ -428,7 +476,7 @@ module ActiveSupport
def freeze
# preload instance variables before freezing
- period; utc; time; to_datetime
+ period; utc; time; to_datetime; to_time
super
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 09cb9cbbe1..96a541a4ef 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -6,7 +6,7 @@ module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
# It allows us to do the following:
#
- # * Limit the set of zones provided by TZInfo to a meaningful subset of 146
+ # * Limit the set of zones provided by TZInfo to a meaningful subset of 134
# zones.
# * Retrieve and display zones with a friendlier name
# (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
@@ -59,6 +59,7 @@ module ActiveSupport
"Buenos Aires" => "America/Argentina/Buenos_Aires",
"Montevideo" => "America/Montevideo",
"Georgetown" => "America/Guyana",
+ "Puerto Rico" => "America/Puerto_Rico",
"Greenland" => "America/Godthab",
"Mid-Atlantic" => "Atlantic/South_Georgia",
"Azores" => "Atlantic/Azores",
@@ -250,14 +251,21 @@ module ActiveSupport
# for time zones in the country specified by its ISO 3166-1 Alpha2 code.
def country_zones(country_code)
code = country_code.to_s.upcase
- @country_zones[code] ||=
- TZInfo::Country.get(code).zone_identifiers.map do |tz_id|
- name = MAPPING.key(tz_id)
- name && self[name]
- end.compact.sort!
+ @country_zones[code] ||= load_country_zones(code)
end
private
+ def load_country_zones(code)
+ country = TZInfo::Country.get(code)
+ country.zone_identifiers.map do |tz_id|
+ if MAPPING.value?(tz_id)
+ self[MAPPING.key(tz_id)]
+ else
+ create(tz_id, nil, TZInfo::Timezone.new(tz_id))
+ end
+ end.sort!
+ end
+
def zones_map
@zones_map ||= begin
MAPPING.each_key { |place| self[place] } # load all the zones
@@ -340,6 +348,41 @@ module ActiveSupport
end
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from an ISO 8601 string.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If the time components are missing then they will be set to zero.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00
+ #
+ # If the string is invalid then an +ArgumentError+ will be raised unlike +parse+
+ # which returns +nil+ when given an invalid date string.
+ def iso8601(str)
+ parts = Date._iso8601(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+
+ if parts[:offset]
+ TimeWithZone.new(time.utc, self)
+ else
+ TimeWithZone.new(nil, self, time)
+ end
+ end
+
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
# of +self+ from parsed string.
#
# Time.zone = 'Hawaii' # => "Hawaii"
@@ -359,6 +402,36 @@ module ActiveSupport
parts_to_time(Date._parse(str, false), now)
end
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from an RFC 3339 string.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If the time or zone components are missing then an +ArgumentError+ will
+ # be raised. This is much stricter than either +parse+ or +iso8601+ which
+ # allow for missing components.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date
+ def rfc3339(str)
+ parts = Date._rfc3339(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour),
+ parts.fetch(:min),
+ parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset)
+ )
+
+ TimeWithZone.new(time.utc, self)
+ end
+
# Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone.
#
# Assumes that +str+ is a time in the time zone +self+,
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index c543122d91..c67ffe69b8 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -47,6 +47,17 @@ module ActiveSupport
assert_raises(RuntimeError) { middleware.call({}) }
assert_nil LocalCacheRegistry.cache_for(key)
end
+
+ def test_local_cache_cleared_on_throw
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ throw :warden
+ })
+ assert_throws(:warden) { middleware.call({}) }
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
end
end
end
diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
index 4c90460032..6c6205a4d2 100644
--- a/activesupport/test/core_ext/date_and_time_compatibility_test.rb
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -16,11 +16,13 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
end
end
end
@@ -28,11 +30,43 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
+ assert_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ assert_not_predicate time, :frozen?
end
end
end
@@ -40,7 +74,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -52,7 +87,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -61,17 +97,47 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
end
end
+ def test_datetime_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_datetime_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
def test_twz_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
@@ -84,19 +150,69 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_twz_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_twz_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_twz_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
end
end
end
@@ -104,7 +220,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_string_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -116,11 +233,40 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_string_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
end
end
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index e3b31c10f5..be7c14e9b4 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -4,8 +4,8 @@ require "core_ext/date_and_time_behavior"
require "time_zone_test_helpers"
class DateTimeExtCalculationsTest < ActiveSupport::TestCase
- def date_time_init(year, month, day, hour, minute, second, *args)
- DateTime.civil(year, month, day, hour, minute, second)
+ def date_time_init(year, month, day, hour, minute, second, usec = 0)
+ DateTime.civil(year, month, day, hour, minute, second + (usec / 1000000))
end
include DateAndTimeBehavior
@@ -113,7 +113,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal DateTime.civil(2005, 2, 4, 23, 59, 59), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day
+ assert_equal DateTime.civil(2005, 2, 4, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day
end
def test_beginning_of_hour
@@ -121,7 +121,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_hour
- assert_equal DateTime.civil(2005, 2, 4, 19, 59, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour
+ assert_equal DateTime.civil(2005, 2, 4, 19, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour
end
def test_beginning_of_minute
@@ -129,13 +129,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_minute
- assert_equal DateTime.civil(2005, 2, 4, 19, 30, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute
+ assert_equal DateTime.civil(2005, 2, 4, 19, 30, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute
end
def test_end_of_month
- assert_equal DateTime.civil(2005, 3, 31, 23, 59, 59), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month
- assert_equal DateTime.civil(2005, 2, 28, 23, 59, 59), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month
- assert_equal DateTime.civil(2005, 4, 30, 23, 59, 59), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 3, 31, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 2, 28, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 4, 30, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month
end
def test_last_year
@@ -162,12 +162,22 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2006, 2, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2006)
assert_equal DateTime.civil(2005, 6, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(month: 6)
assert_equal DateTime.civil(2012, 9, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9)
- assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16)
- assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
- assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45)
+ assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16)
+ assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
+ assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45)
+
+ # datetime with non-zero offset
+ assert_equal DateTime.civil(2005, 2, 22, 15, 15, 10, Rational(-5, 24)), DateTime.civil(2005, 2, 22, 15, 15, 10, 0).change(offset: Rational(-5, 24))
# datetime with fractions of a second
assert_equal DateTime.civil(2005, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1)
+ assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(usec: 8)
+ assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(nsec: 8000)
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1, nsec: 1) }
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1000000) }
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 1000000000) }
+ assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 999999) }
+ assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 999999999) }
end
def test_advance
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 6a275d1d5b..1648a9b270 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -84,6 +84,38 @@ class DurationTest < ActiveSupport::TestCase
assert_nothing_raised { Date.today - Date.today }
end
+ def test_plus
+ assert_equal 2.seconds, 1.second + 1.second
+ assert_instance_of ActiveSupport::Duration, 1.second + 1.second
+ assert_equal 2.seconds, 1.second + 1
+ assert_instance_of ActiveSupport::Duration, 1.second + 1
+ end
+
+ def test_minus
+ assert_equal 1.second, 2.seconds - 1.second
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1.second
+ assert_equal 1.second, 2.seconds - 1
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1
+ assert_equal 1.second, 2 - 1.second
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1
+ end
+
+ def test_multiply
+ assert_equal 7.days, 1.day * 7
+ assert_instance_of ActiveSupport::Duration, 1.day * 7
+ assert_equal 86400, 1.day * 1.second
+ end
+
+ def test_divide
+ assert_equal 1.day, 7.days / 7
+ assert_instance_of ActiveSupport::Duration, 7.days / 7
+ assert_equal 1, 1.day / 1.day
+ end
+
+ def test_date_added_with_multiplied_duration
+ assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 1.day * 2
+ end
+
def test_plus_with_time
assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
end
@@ -179,6 +211,19 @@ class DurationTest < ActiveSupport::TestCase
Time.zone = nil
end
+ def test_before_and_afer
+ t = Time.local(2000)
+ assert_equal t + 1, 1.second.after(t)
+ assert_equal t - 1, 1.second.before(t)
+ end
+
+ def test_before_and_after_without_argument
+ Time.stub(:now, Time.local(2000)) do
+ assert_equal Time.now - 1.second, 1.second.before
+ assert_equal Time.now + 1.second, 1.second.after
+ end
+ end
+
def test_adding_hours_across_dst_boundary
with_env_tz "CET" do
assert_equal Time.local(2009, 3, 29, 0, 0, 0) + 24.hours, Time.local(2009, 3, 30, 1, 0, 0)
@@ -237,6 +282,118 @@ class DurationTest < ActiveSupport::TestCase
assert_equal(1, (61 <=> 1.minute))
end
+ def test_implicit_coercion
+ assert_equal 2.days, 2 * 1.day
+ assert_instance_of ActiveSupport::Duration, 2 * 1.day
+ assert_equal Time.utc(2017, 1, 3), Time.utc(2017, 1, 1) + 2 * 1.day
+ assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 2 * 1.day
+ end
+
+ def test_scalar_coerce
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ end
+
+ def test_scalar_delegations
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_kind_of Float, scalar.to_f
+ assert_kind_of Integer, scalar.to_i
+ assert_kind_of String, scalar.to_s
+ end
+
+ def test_scalar_unary_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(-10, -scalar)
+ assert_instance_of ActiveSupport::Duration::Scalar, -scalar
+ end
+
+ def test_scalar_compare
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(1, scalar <=> 5)
+ assert_equal(0, scalar <=> 10)
+ assert_equal(-1, scalar <=> 15)
+ assert_equal(nil, scalar <=> "foo")
+ end
+
+ def test_scalar_plus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 20, 10 + scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_equal 20, scalar + 10
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar + 10
+ assert_equal 20, 10.seconds + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ assert_equal 20, scalar + 10.seconds
+ assert_instance_of ActiveSupport::Duration, scalar + 10.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar + "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 20 - scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 20 - scalar
+ assert_equal 5, scalar - 5
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar - 5
+ assert_equal 10, 20.seconds - scalar
+ assert_instance_of ActiveSupport::Duration, 20.seconds - scalar
+ assert_equal 5, scalar - 5.seconds
+ assert_instance_of ActiveSupport::Duration, scalar - 5.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar - "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_multiply
+ scalar = ActiveSupport::Duration::Scalar.new(5)
+
+ assert_equal 10, 2 * scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 2 * scalar
+ assert_equal 10, scalar * 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar * 2
+ assert_equal 10, 2.seconds * scalar
+ assert_instance_of ActiveSupport::Duration, 2.seconds * scalar
+ assert_equal 10, scalar * 2.seconds
+ assert_instance_of ActiveSupport::Duration, scalar * 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar * "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_divide
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 100 / scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 100 / scalar
+ assert_equal 5, scalar / 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar / 2
+ assert_equal 10, 100.seconds / scalar
+ assert_instance_of ActiveSupport::Duration, 2.seconds * scalar
+ assert_equal 5, scalar / 2.seconds
+ assert_instance_of ActiveSupport::Duration, scalar / 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar / "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
def test_twelve_months_equals_one_year
assert_equal 12.months, 1.year
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 525ea08abd..18da5fcf5f 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -8,33 +8,6 @@ require "active_support/core_ext/object/deep_dup"
require "active_support/inflections"
class HashExtTest < ActiveSupport::TestCase
- HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
-
- class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
- end
-
- class SubclassingArray < Array
- end
-
- class SubclassingHash < Hash
- end
-
- class NonIndifferentHash < Hash
- def nested_under_indifferent_access
- self
- end
- end
-
- class HashByConversion
- def initialize(hash)
- @hash = hash
- end
-
- def to_hash
- @hash
- end
- end
-
def setup
@strings = { "a" => 1, "b" => 2 }
@nested_strings = { "a" => { "b" => { "c" => 3 } } }
@@ -264,471 +237,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)
end
- def test_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
- end
-
- def test_deep_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
- end
-
- def test_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @strings.with_indifferent_access.stringify_keys
- assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
- end
-
- def test_deep_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
- end
-
- def test_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
- end
-
- def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
- end
-
- def test_nested_under_indifferent_access
- foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
-
- foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of NonIndifferentHash, foo["foo"]
-
- foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of IndifferentHash, foo["foo"]
- end
-
- def test_indifferent_assorted
- @strings = @strings.with_indifferent_access
- @symbols = @symbols.with_indifferent_access
- @mixed = @mixed.with_indifferent_access
-
- assert_equal "a", @strings.__send__(:convert_key, :a)
-
- assert_equal 1, @strings.fetch("a")
- assert_equal 1, @strings.fetch(:a.to_s)
- assert_equal 1, @strings.fetch(:a)
-
- hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
- method_map = { '[]': 1, fetch: 1, values_at: [1],
- has_key?: true, include?: true, key?: true,
- member?: true }
-
- hashes.each do |name, hash|
- method_map.sort_by(&:to_s).each do |meth, expected|
- assert_equal(expected, hash.__send__(meth, "a"),
- "Calling #{name}.#{meth} 'a'")
- assert_equal(expected, hash.__send__(meth, :a),
- "Calling #{name}.#{meth} :a")
- end
- end
-
- assert_equal [1, 2], @strings.values_at("a", "b")
- assert_equal [1, 2], @strings.values_at(:a, :b)
- assert_equal [1, 2], @symbols.values_at("a", "b")
- assert_equal [1, 2], @symbols.values_at(:a, :b)
- assert_equal [1, 2], @mixed.values_at("a", "b")
- assert_equal [1, 2], @mixed.values_at(:a, :b)
- end
-
- def test_indifferent_reading
- hash = HashWithIndifferentAccess.new
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_nil hash[:e]
- end
-
- def test_indifferent_reading_with_nonnil_default
- hash = HashWithIndifferentAccess.new(1)
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_equal 1, hash[:e]
- end
-
- def test_indifferent_writing
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 2
- hash[3] = 3
-
- assert_equal 1, hash["a"]
- assert_equal 2, hash["b"]
- assert_equal 1, hash[:a]
- assert_equal 2, hash[:b]
- assert_equal 3, hash[3]
- end
-
- def test_indifferent_update
- hash = HashWithIndifferentAccess.new
- hash[:a] = "a"
- hash["b"] = "b"
-
- updated_with_strings = hash.update(@strings)
- updated_with_symbols = hash.update(@symbols)
- updated_with_mixed = hash.update(@mixed)
-
- assert_equal 1, updated_with_strings[:a]
- assert_equal 1, updated_with_strings["a"]
- assert_equal 2, updated_with_strings["b"]
-
- assert_equal 1, updated_with_symbols[:a]
- assert_equal 2, updated_with_symbols["b"]
- assert_equal 2, updated_with_symbols[:b]
-
- assert_equal 1, updated_with_mixed[:a]
- assert_equal 2, updated_with_mixed["b"]
-
- assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
- end
-
- def test_update_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash.update HashByConversion.new(a: 1)
- assert_equal 1, hash["a"]
- end
-
- def test_indifferent_merging
- hash = HashWithIndifferentAccess.new
- hash[:a] = "failure"
- hash["b"] = "failure"
-
- other = { "a" => 1, :b => 2 }
-
- merged = hash.merge(other)
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 1, merged[:a]
- assert_equal 2, merged["b"]
-
- hash.update(other)
-
- assert_equal 1, hash[:a]
- assert_equal 2, hash["b"]
- end
-
- def test_merge_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- merged = hash.merge HashByConversion.new(a: 1)
- assert_equal 1, merged["a"]
- end
-
- def test_indifferent_replace
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(b: 12)
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_replace_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(HashByConversion.new(b: 12))
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_indifferent_merging_with_block
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 3
-
- other = { "a" => 4, :b => 2, "c" => 10 }
-
- merged = hash.merge(other) { |key, old, new| old > new ? old : new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 4, merged[:a]
- assert_equal 3, merged["b"]
- assert_equal 10, merged[:c]
-
- other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
-
- merged = hash.merge(other_indifferent) { |key, old, new| old + new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 10, merged[:a]
- assert_equal 5, merged[:b]
- end
-
- def test_indifferent_reverse_merging
- hash = HashWithIndifferentAccess.new key: :old_value
- hash.reverse_merge! key: :new_value
- assert_equal :old_value, hash[:key]
-
- hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value")
- hash.reverse_merge!(some: "noclobber", another: "clobber")
- assert_equal "value", hash[:some]
- assert_equal "clobber", hash[:another]
- end
-
- def test_indifferent_deleting
- get_hash = proc { { a: "foo" }.with_indifferent_access }
- hash = get_hash.call
- assert_equal "foo", hash.delete(:a)
- assert_nil hash.delete(:a)
- hash = get_hash.call
- assert_equal "foo", hash.delete("a")
- assert_nil hash.delete("a")
- end
-
- def test_indifferent_select
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_select_returns_enumerator
- enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
- assert_instance_of Enumerator, enum
- end
-
- def test_indifferent_select_returns_a_hash_when_unchanged
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true }
-
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_select_bang
- indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.select! { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_reject
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
-
- assert_equal({ "a" => 1 }, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_reject_returns_enumerator
- enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
- assert_instance_of Enumerator, enum
- end
-
- def test_indifferent_reject_bang
- indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.reject! { |k, v| v != 1 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_compact
- hash_contain_nil_value = @strings.merge("z" => nil)
- hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
- compacted_hash = hash.compact
-
- assert_equal(@strings, compacted_hash)
- assert_equal(hash_contain_nil_value, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
-
- empty_hash = ActiveSupport::HashWithIndifferentAccess.new
- compacted_hash = empty_hash.compact
-
- assert_equal compacted_hash, empty_hash
-
- non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar)
- compacted_hash = non_empty_hash.compact
-
- assert_equal compacted_hash, non_empty_hash
- end
-
- def test_indifferent_to_hash
- # Should convert to a Hash with String keys.
- assert_equal @strings, @mixed.with_indifferent_access.to_hash
-
- # Should preserve the default value.
- mixed_with_default = @mixed.dup
- mixed_with_default.default = "1234"
- roundtrip = mixed_with_default.with_indifferent_access.to_hash
- assert_equal @strings, roundtrip
- assert_equal "1234", roundtrip.default
-
- # Ensure nested hashes are not HashWithIndiffereneAccess
- new_to_hash = @nested_mixed.with_indifferent_access.to_hash
- assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
- end
-
- def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
- hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
- hash[:a] << 1
-
- assert_equal [1], hash[:a]
- end
-
- def test_with_indifferent_access_has_no_side_effects_on_existing_hash
- hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
- hash.with_indifferent_access
-
- assert_equal [:foo, "bar"], hash[:content].first.keys
- end
-
- def test_indifferent_hash_with_array_of_hashes
- hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
- assert_equal "1", hash[:urls][:url].first[:address]
-
- hash = hash.to_hash
- assert_not hash.instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
- end
-
- def test_should_preserve_array_subclass_when_value_is_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_should_preserve_array_class_when_hash_value_is_frozen_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.deep_stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.deep_symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_to_options_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h.to_options!
- assert_equal 1, h["first"]
- end
-
- def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
- h = HashWithIndifferentAccess.new(a: { b: "b" })
- dup = h.dup
-
- dup[:a][:c] = "c"
- assert_equal "c", h[:a][:c]
- end
-
- def test_indifferent_sub_hashes
- h = { "user" => { "id" => 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
-
- h = { user: { id: 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
- end
-
- def test_indifferent_duplication
- # Should preserve default value
- h = HashWithIndifferentAccess.new
- h.default = "1234"
- assert_equal h.default, h.dup.default
-
- # Should preserve class for subclasses
- h = IndifferentHash.new
- assert_equal h.class, h.dup.class
- end
-
- def test_nested_dig_indifferent_access
- skip if RUBY_VERSION < "2.3.0"
- data = { "this" => { "views" => 1234 } }.with_indifferent_access
- assert_equal 1234, data.dig(:this, :views)
- end
-
def test_assert_valid_keys
assert_nothing_raised do
{ failure: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny ])
@@ -761,12 +269,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message
end
- def test_assorted_keys_not_stringified
- original = { Object.new => 2, 1 => 2, [] => true }
- indiff = original.with_indifferent_access
- assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
- end
-
def test_deep_merge
hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } }
hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
@@ -797,37 +299,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, hash_1
end
- def test_deep_merge_on_indifferent_access
- hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
- hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
- hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
- expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
- assert_equal expected, hash_1.deep_merge(hash_2)
- assert_equal expected, hash_1.deep_merge(hash_3)
-
- hash_1.deep_merge!(hash_2)
- assert_equal expected, hash_1
- end
-
- def test_store_on_indifferent_access
- hash = HashWithIndifferentAccess.new
- hash.store(:test1, 1)
- hash.store("test1", 11)
- hash[:test2] = 2
- hash["test2"] = 22
- expected = { "test1" => 11, "test2" => 22 }
- assert_equal expected, hash
- end
-
- def test_constructor_on_indifferent_access
- hash = HashWithIndifferentAccess[:foo, 1]
- assert_equal 1, hash[:foo]
- assert_equal 1, hash["foo"]
- hash[:foo] = 3
- assert_equal 3, hash[:foo]
- assert_equal 3, hash["foo"]
- end
-
def test_reverse_merge
defaults = { a: "x", b: "y", c: 10 }.freeze
options = { a: 1, b: 2 }
@@ -848,6 +319,21 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, merged
end
+ def test_with_defaults_aliases_reverse_merge
+ defaults = { a: "x", b: "y", c: 10 }.freeze
+ options = { a: 1, b: 2 }
+ expected = { a: 1, b: 2, c: 10 }
+
+ # Should be an alias for reverse_merge
+ assert_equal expected, options.with_defaults(defaults)
+ assert_not_equal expected, options
+
+ # Should be an alias for reverse_merge!
+ merged = options.dup
+ assert_equal expected, merged.with_defaults!(defaults)
+ assert_equal expected, merged
+ end
+
def test_slice
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -890,38 +376,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, original.slice(*[:a, :b])
end
- def test_indifferent_slice
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { a: "x", b: "y" }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should return a new hash with only the given keys.
- assert_equal expected, original.slice(*keys), keys.inspect
- assert_not_equal expected, original
- end
- end
-
- def test_indifferent_slice_inplace
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { c: 10 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should replace the hash with only the given keys.
- copy = original.dup
- assert_equal expected, copy.slice!(*keys)
- end
- end
-
- def test_indifferent_slice_access_with_symbols
- original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
- original = original.with_indifferent_access
-
- slice = original.slice(:login, :password)
-
- assert_equal "bender", slice[:login]
- assert_equal "bender", slice["login"]
- end
-
def test_slice_bang_does_not_override_default
hash = Hash.new(0)
hash.update(a: 1, b: 2)
@@ -959,18 +413,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil extracted[:x]
end
- def test_indifferent_extract
- original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
- expected = { a: 1, b: 2 }.with_indifferent_access
- remaining = { c: 3, d: 4 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- copy = original.dup
- assert_equal expected, copy.extract!(*keys)
- assert_equal remaining, copy
- end
- end
-
def test_except
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -1042,78 +484,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil(h.compact!)
assert_equal(@symbols, h)
end
-
- def test_new_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
- assert hash.key?("a")
- assert_equal 1, hash[:a]
- end
-
- def test_dup_with_default_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, v| raise "walrus" }
- assert_nothing_raised { hash.dup }
- end
-
- def test_dup_with_default_proc_sets_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, k| k + 1 }
- new_hash = hash.dup
-
- assert_equal 3, new_hash[2]
-
- new_hash.default = 2
- assert_equal 2, new_hash[:non_existent]
- end
-
- def test_to_hash_with_raising_default_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, k| raise "walrus" }
-
- assert_nothing_raised { hash.to_hash }
- end
-
- def test_new_with_to_hash_conversion_copies_default
- normal_hash = Hash.new(3)
- normal_hash[:a] = 1
-
- hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
- assert_equal 1, hash[:a]
- assert_equal 3, hash[:b]
- end
-
- def test_new_with_to_hash_conversion_copies_default_proc
- normal_hash = Hash.new { 1 + 2 }
- normal_hash[:a] = 1
-
- hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
- assert_equal 1, hash[:a]
- assert_equal 3, hash[:b]
- end
-
- def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain
- klass = Class.new(::HashWithIndifferentAccess)
- assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1]
- end
-
- def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars
- klass = Class.new(::HashWithIndifferentAccess) do
- def initialize(*)
- @foo = "bar"
- super
- end
- end
-
- yaml_output = klass.new.to_yaml
-
- # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW)
- if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9")
- assert_includes yaml_output, "hash-with-ivars"
- assert_includes yaml_output, "@foo: bar"
- else
- assert_includes yaml_output, "hash"
- end
- end
end
class IWriteMyOwnXML
@@ -1159,8 +529,6 @@ class HashExtToParamTests < ActiveSupport::TestCase
end
class HashToXmlTest < ActiveSupport::TestCase
- HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
-
def setup
@xml_options = { root: :person, skip_instruct: true, indent: 0 }
end
@@ -1636,61 +1004,6 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
end
- def test_should_use_default_proc_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_return_nil_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_nil hash_wia.default
- end
-
- def test_should_use_default_value_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_use_default_value_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia.default
- end
-
- def test_should_nil_if_no_default_value_is_supplied
- hash_wia = HashWithIndifferentAccess.new
- assert_nil hash_wia.default
- end
-
- def test_should_return_dup_for_with_indifferent_access
- hash_wia = HashWithIndifferentAccess.new
- assert_equal hash_wia, hash_wia.with_indifferent_access
- assert_not_same hash_wia, hash_wia.with_indifferent_access
- end
-
- def test_allows_setting_frozen_array_values_with_indifferent_access
- value = [1, 2, 3].freeze
- hash = HashWithIndifferentAccess.new
- hash[:key] = value
- assert_equal hash[:key], value
- end
-
- def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
- hash = Hash.new(3)
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia.default
- end
-
- def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
- hash = Hash.new do
- 2 + 1
- end
- assert_equal 3, hash[:foo]
-
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia[:foo]
- assert_equal 3, hash_wia[:bar]
- end
-
# The XML builder seems to fail miserably when trying to tag something
# with the same name as a Kernel method (throw, test, loop, select ...)
def test_kernel_method_names_to_xml
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index a17438bf4d..085fd6592d 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -70,7 +70,23 @@ Product = Struct.new(:name) do
end
end
+module ExtraMissing
+ def method_missing(sym, *args)
+ if sym == :extra_missing
+ 42
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(sym, priv = false)
+ sym == :extra_missing || super
+ end
+end
+
DecoratedTester = Struct.new(:client) do
+ include ExtraMissing
+
delegate_missing_to :client
end
@@ -356,6 +372,22 @@ class ModuleTest < ActiveSupport::TestCase
assert_match(/undefined method `my_fake_method' for/, e.message)
end
+ def test_delegate_to_missing_affects_respond_to
+ assert DecoratedTester.new(@david).respond_to?(:name)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method)
+
+ assert DecoratedTester.new(@david).respond_to?(:name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method, true)
+ end
+
+ def test_delegate_to_missing_respects_superclass_missing
+ assert_equal 42, DecoratedTester.new(@david).extra_missing
+
+ assert_respond_to DecoratedTester.new(@david), :extra_missing
+ end
+
def test_delegate_with_case
event = Event.new(Tester.new)
assert_equal 1, event.foo
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index e6f3c8aef2..68b0129980 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -4,7 +4,10 @@ require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/numeric/time"
class DuplicableTest < ActiveSupport::TestCase
- if RUBY_VERSION >= "2.4.1"
+ if RUBY_VERSION >= "2.5.0"
+ RAISE_DUP = [method(:puts)]
+ ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)]
+ elsif RUBY_VERSION >= "2.4.1"
RAISE_DUP = [method(:puts), Complex(1), Rational(1)]
ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]
elsif RUBY_VERSION >= "2.4.0" # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support...
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 41cc9888c6..a98951e889 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -78,6 +78,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_titleize_with_keep_id_suffix
+ MixtureToTitleCaseWithKeepIdSuffix.each do |before, titleized|
+ assert_equal(titleized, before.titleize(keep_id_suffix: true))
+ end
+ end
+
def test_upcase_first
assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first
end
@@ -199,6 +205,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, underscore.humanize(keep_id_suffix: true))
+ end
+ end
+
def test_humanize_with_html_escape
assert_equal "Hello", ERB::Util.html_escape("hello").humanize
end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index a399e36dc9..625a5bffb8 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -433,6 +433,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(nsec: 1000000000) }
end
+ def test_change_offset
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, "-08:00"), Time.new(2006, 2, 22, 15, 15, 10, "+01:00").change(offset: "-08:00")
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, -28800), Time.new(2006, 2, 22, 15, 15, 10, 3600).change(offset: -28800)
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(usec: 1000000, offset: "-08:00") }
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(nsec: 1000000000, offset: -28800) }
+ end
+
def test_advance
assert_equal Time.local(2006, 2, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 1)
assert_equal Time.local(2005, 6, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(months: 4)
@@ -569,6 +576,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
Time::DATE_FORMATS.delete(:custom)
end
+ def test_rfc3339_with_fractional_seconds
+ time = Time.new(1999, 12, 31, 19, 0, Rational(1, 8), -18000)
+ assert_equal "1999-12-31T19:00:00.125-05:00", time.rfc3339(3)
+ end
+
def test_to_date
assert_equal Date.new(2005, 2, 21), Time.local(2005, 2, 21, 17, 44, 30).to_date
end
@@ -910,6 +922,37 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_all_year
assert_equal Time.local(2011, 1, 1, 0, 0, 0)..Time.local(2011, 12, 31, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_year
end
+
+ def test_rfc3339_parse
+ time = Time.rfc3339("1999-12-31T19:00:00.125-05:00")
+
+ assert_equal 1999, time.year
+ assert_equal 12, time.month
+ assert_equal 31, time.day
+ assert_equal 19, time.hour
+ assert_equal 0, time.min
+ assert_equal 0, time.sec
+ assert_equal 125000, time.usec
+ assert_equal(-18000, time.utc_offset)
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("1999-12-31")
+ end
+
+ assert_equal "invalid date", exception.message
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("1999-12-31T19:00:00")
+ end
+
+ assert_equal "invalid date", exception.message
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
end
class TimeExtMarshalingTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 1534daacb9..70ae793cda 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -144,6 +144,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil)
end
+ def test_iso8601_with_fractional_seconds
+ @twz += Rational(1, 8)
+ assert_equal "1999-12-31T19:00:00.125-05:00", @twz.iso8601(3)
+ end
+
+ def test_rfc3339_with_fractional_seconds
+ @twz += Rational(1, 8)
+ assert_equal "1999-12-31T19:00:00.125-05:00", @twz.rfc3339(3)
+ end
+
def test_to_yaml
yaml = <<-EOF.strip_heredoc
--- !ruby/object:ActiveSupport::TimeWithZone
@@ -421,11 +431,29 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal time, Time.at(time)
end
- def test_to_time
- with_env_tz "US/Eastern" do
- assert_equal Time, @twz.to_time.class
- assert_equal Time.local(1999, 12, 31, 19), @twz.to_time
- assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset
+ def test_to_time_with_preserve_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_to_time_without_preserve_timezone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
end
end
@@ -508,6 +536,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
@twz.period
@twz.time
@twz.to_datetime
+ @twz.to_time
end
end
@@ -596,6 +625,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 06:00:00 EST -05:00", @twz.change(hour: 6).inspect
assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(min: 15).inspect
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(sec: 30).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: "-10:00").inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: -36000).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Hawaii").inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -10).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -36000).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Pacific/Honolulu").inspect
end
def test_change_at_dst_boundary
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 5f72fbf662..36d1ef0849 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -35,6 +35,18 @@ class Deprecatee
A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::A", "Deprecatee::B::C")
end
+class DeprecateeWithAccessor
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
+
+ module B
+ C = 1
+ end
+ deprecate_constant "A", "DeprecateeWithAccessor::B::C"
+
+ class NewException < StandardError; end
+ deprecate_constant "OldException", "DeprecateeWithAccessor::NewException"
+end
+
class DeprecationTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Stream
@@ -162,6 +174,17 @@ class DeprecationTest < ActiveSupport::TestCase
assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class }
end
+ def test_deprecated_constant_accessor
+ assert_not_deprecated { DeprecateeWithAccessor::B::C }
+ assert_deprecated("DeprecateeWithAccessor::A") { assert_equal DeprecateeWithAccessor::B::C, DeprecateeWithAccessor::A }
+ end
+
+ def test_deprecated_constant_accessor_exception
+ raise DeprecateeWithAccessor::NewException.new("Test")
+ rescue DeprecateeWithAccessor::OldException => e
+ assert_kind_of DeprecateeWithAccessor::NewException, e
+ end
+
def test_assert_deprecated_raises_when_method_not_deprecated
assert_raises(Minitest::Assertion) { assert_deprecated { @dtc.not } }
end
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
new file mode 100644
index 0000000000..d68add46cd
--- /dev/null
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -0,0 +1,745 @@
+require "abstract_unit"
+require "active_support/core_ext/hash"
+require "bigdecimal"
+require "active_support/core_ext/string/access"
+require "active_support/ordered_hash"
+require "active_support/core_ext/object/conversions"
+require "active_support/core_ext/object/deep_dup"
+require "active_support/inflections"
+
+class HashWithIndifferentAccessTest < ActiveSupport::TestCase
+ HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
+
+ class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
+ end
+
+ class SubclassingArray < Array
+ end
+
+ class SubclassingHash < Hash
+ end
+
+ class NonIndifferentHash < Hash
+ def nested_under_indifferent_access
+ self
+ end
+ end
+
+ class HashByConversion
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def to_hash
+ @hash
+ end
+ end
+
+ def setup
+ @strings = { "a" => 1, "b" => 2 }
+ @nested_strings = { "a" => { "b" => { "c" => 3 } } }
+ @symbols = { a: 1, b: 2 }
+ @nested_symbols = { a: { b: { c: 3 } } }
+ @mixed = { :a => 1, "b" => 2 }
+ @nested_mixed = { "a" => { b: { "c" => 3 } } }
+ @integers = { 0 => 1, 1 => 2 }
+ @nested_integers = { 0 => { 1 => { 2 => 3 } } }
+ @illegal_symbols = { [] => 3 }
+ @nested_illegal_symbols = { [] => { [] => 3 } }
+ end
+
+ def test_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
+ end
+
+ def test_deep_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
+ end
+
+ def test_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @strings.with_indifferent_access.stringify_keys
+ assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
+ end
+
+ def test_deep_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
+ end
+
+ def test_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
+ end
+
+ def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
+ end
+
+ def test_nested_under_indifferent_access
+ foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
+
+ foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of NonIndifferentHash, foo["foo"]
+
+ foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of IndifferentHash, foo["foo"]
+ end
+
+ def test_indifferent_assorted
+ @strings = @strings.with_indifferent_access
+ @symbols = @symbols.with_indifferent_access
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal "a", @strings.__send__(:convert_key, :a)
+
+ assert_equal 1, @strings.fetch("a")
+ assert_equal 1, @strings.fetch(:a.to_s)
+ assert_equal 1, @strings.fetch(:a)
+
+ hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
+ method_map = { '[]': 1, fetch: 1, values_at: [1],
+ has_key?: true, include?: true, key?: true,
+ member?: true }
+
+ hashes.each do |name, hash|
+ method_map.sort_by(&:to_s).each do |meth, expected|
+ assert_equal(expected, hash.__send__(meth, "a"),
+ "Calling #{name}.#{meth} 'a'")
+ assert_equal(expected, hash.__send__(meth, :a),
+ "Calling #{name}.#{meth} :a")
+ end
+ end
+
+ assert_equal [1, 2], @strings.values_at("a", "b")
+ assert_equal [1, 2], @strings.values_at(:a, :b)
+ assert_equal [1, 2], @symbols.values_at("a", "b")
+ assert_equal [1, 2], @symbols.values_at(:a, :b)
+ assert_equal [1, 2], @mixed.values_at("a", "b")
+ assert_equal [1, 2], @mixed.values_at(:a, :b)
+ end
+
+ def test_indifferent_fetch_values
+ skip unless Hash.method_defined?(:fetch_values)
+
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal [1, 2], @mixed.fetch_values("a", "b")
+ assert_equal [1, 2], @mixed.fetch_values(:a, :b)
+ assert_equal [1, 2], @mixed.fetch_values(:a, "b")
+ assert_equal [1, "c"], @mixed.fetch_values(:a, :c) { |key| key }
+ assert_raise(KeyError) { @mixed.fetch_values(:a, :c) }
+ end
+
+ def test_indifferent_reading
+ hash = HashWithIndifferentAccess.new
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_nil hash[:e]
+ end
+
+ def test_indifferent_reading_with_nonnil_default
+ hash = HashWithIndifferentAccess.new(1)
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_equal 1, hash[:e]
+ end
+
+ def test_indifferent_writing
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 2
+ hash[3] = 3
+
+ assert_equal 1, hash["a"]
+ assert_equal 2, hash["b"]
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash[:b]
+ assert_equal 3, hash[3]
+ end
+
+ def test_indifferent_update
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "a"
+ hash["b"] = "b"
+
+ updated_with_strings = hash.update(@strings)
+ updated_with_symbols = hash.update(@symbols)
+ updated_with_mixed = hash.update(@mixed)
+
+ assert_equal 1, updated_with_strings[:a]
+ assert_equal 1, updated_with_strings["a"]
+ assert_equal 2, updated_with_strings["b"]
+
+ assert_equal 1, updated_with_symbols[:a]
+ assert_equal 2, updated_with_symbols["b"]
+ assert_equal 2, updated_with_symbols[:b]
+
+ assert_equal 1, updated_with_mixed[:a]
+ assert_equal 2, updated_with_mixed["b"]
+
+ assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
+ end
+
+ def test_update_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash.update HashByConversion.new(a: 1)
+ assert_equal 1, hash["a"]
+ end
+
+ def test_indifferent_merging
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "failure"
+ hash["b"] = "failure"
+
+ other = { "a" => 1, :b => 2 }
+
+ merged = hash.merge(other)
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 1, merged[:a]
+ assert_equal 2, merged["b"]
+
+ hash.update(other)
+
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash["b"]
+ end
+
+ def test_merge_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ merged = hash.merge HashByConversion.new(a: 1)
+ assert_equal 1, merged["a"]
+ end
+
+ def test_indifferent_replace
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(b: 12)
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_replace_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(HashByConversion.new(b: 12))
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_indifferent_merging_with_block
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 3
+
+ other = { "a" => 4, :b => 2, "c" => 10 }
+
+ merged = hash.merge(other) { |key, old, new| old > new ? old : new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 4, merged[:a]
+ assert_equal 3, merged["b"]
+ assert_equal 10, merged[:c]
+
+ other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
+
+ merged = hash.merge(other_indifferent) { |key, old, new| old + new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 10, merged[:a]
+ assert_equal 5, merged[:b]
+ end
+
+ def test_indifferent_reverse_merging
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.reverse_merge! key: :new_value
+ assert_equal :old_value, hash[:key]
+
+ hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value")
+ hash.reverse_merge!(some: "noclobber", another: "clobber")
+ assert_equal "value", hash[:some]
+ assert_equal "clobber", hash[:another]
+ end
+
+ def test_indifferent_with_defaults_aliases_reverse_merge
+ hash = HashWithIndifferentAccess.new key: :old_value
+ actual = hash.with_defaults key: :new_value
+ assert_equal :old_value, actual[:key]
+
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.with_defaults! key: :new_value
+ assert_equal :old_value, hash[:key]
+ end
+
+ def test_indifferent_deleting
+ get_hash = proc { { a: "foo" }.with_indifferent_access }
+ hash = get_hash.call
+ assert_equal "foo", hash.delete(:a)
+ assert_nil hash.delete(:a)
+ hash = get_hash.call
+ assert_equal "foo", hash.delete("a")
+ assert_nil hash.delete("a")
+ end
+
+ def test_indifferent_select
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_select_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
+ assert_instance_of Enumerator, enum
+ end
+
+ def test_indifferent_select_returns_a_hash_when_unchanged
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true }
+
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_select_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.select! { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_reject
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_reject_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
+ assert_instance_of Enumerator, enum
+ end
+
+ def test_indifferent_reject_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.reject! { |k, v| v != 1 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_compact
+ hash_contain_nil_value = @strings.merge("z" => nil)
+ hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
+ compacted_hash = hash.compact
+
+ assert_equal(@strings, compacted_hash)
+ assert_equal(hash_contain_nil_value, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
+
+ empty_hash = ActiveSupport::HashWithIndifferentAccess.new
+ compacted_hash = empty_hash.compact
+
+ assert_equal compacted_hash, empty_hash
+
+ non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar)
+ compacted_hash = non_empty_hash.compact
+
+ assert_equal compacted_hash, non_empty_hash
+ end
+
+ def test_indifferent_to_hash
+ # Should convert to a Hash with String keys.
+ assert_equal @strings, @mixed.with_indifferent_access.to_hash
+
+ # Should preserve the default value.
+ mixed_with_default = @mixed.dup
+ mixed_with_default.default = "1234"
+ roundtrip = mixed_with_default.with_indifferent_access.to_hash
+ assert_equal @strings, roundtrip
+ assert_equal "1234", roundtrip.default
+
+ # Ensure nested hashes are not HashWithIndiffereneAccess
+ new_to_hash = @nested_mixed.with_indifferent_access.to_hash
+ assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
+ hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
+ hash[:a] << 1
+
+ assert_equal [1], hash[:a]
+ end
+
+ def test_with_indifferent_access_has_no_side_effects_on_existing_hash
+ hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
+ hash.with_indifferent_access
+
+ assert_equal [:foo, "bar"], hash[:content].first.keys
+ end
+
+ def test_indifferent_hash_with_array_of_hashes
+ hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
+ assert_equal "1", hash[:urls][:url].first[:address]
+
+ hash = hash.to_hash
+ assert_not hash.instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_should_preserve_array_subclass_when_value_is_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_should_preserve_array_class_when_hash_value_is_frozen_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.deep_stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.deep_symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_to_options_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h.to_options!
+ assert_equal 1, h["first"]
+ end
+
+ def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
+ h = HashWithIndifferentAccess.new(a: { b: "b" })
+ dup = h.dup
+
+ dup[:a][:c] = "c"
+ assert_equal "c", h[:a][:c]
+ end
+
+ def test_indifferent_sub_hashes
+ h = { "user" => { "id" => 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+
+ h = { user: { id: 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+ end
+
+ def test_indifferent_duplication
+ # Should preserve default value
+ h = HashWithIndifferentAccess.new
+ h.default = "1234"
+ assert_equal h.default, h.dup.default
+
+ # Should preserve class for subclasses
+ h = IndifferentHash.new
+ assert_equal h.class, h.dup.class
+ end
+
+ def test_nested_dig_indifferent_access
+ skip if RUBY_VERSION < "2.3.0"
+ data = { "this" => { "views" => 1234 } }.with_indifferent_access
+ assert_equal 1234, data.dig(:this, :views)
+ end
+
+ def test_assorted_keys_not_stringified
+ original = { Object.new => 2, 1 => 2, [] => true }
+ indiff = original.with_indifferent_access
+ assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
+ end
+
+ def test_deep_merge_on_indifferent_access
+ hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
+ hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
+ hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
+ expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
+ assert_equal expected, hash_1.deep_merge(hash_2)
+ assert_equal expected, hash_1.deep_merge(hash_3)
+
+ hash_1.deep_merge!(hash_2)
+ assert_equal expected, hash_1
+ end
+
+ def test_store_on_indifferent_access
+ hash = HashWithIndifferentAccess.new
+ hash.store(:test1, 1)
+ hash.store("test1", 11)
+ hash[:test2] = 2
+ hash["test2"] = 22
+ expected = { "test1" => 11, "test2" => 22 }
+ assert_equal expected, hash
+ end
+
+ def test_constructor_on_indifferent_access
+ hash = HashWithIndifferentAccess[:foo, 1]
+ assert_equal 1, hash[:foo]
+ assert_equal 1, hash["foo"]
+ hash[:foo] = 3
+ assert_equal 3, hash[:foo]
+ assert_equal 3, hash["foo"]
+ end
+
+ def test_indifferent_slice
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { a: "x", b: "y" }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should return a new hash with only the given keys.
+ assert_equal expected, original.slice(*keys), keys.inspect
+ assert_not_equal expected, original
+ end
+ end
+
+ def test_indifferent_slice_inplace
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { c: 10 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should replace the hash with only the given keys.
+ copy = original.dup
+ assert_equal expected, copy.slice!(*keys)
+ end
+ end
+
+ def test_indifferent_slice_access_with_symbols
+ original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
+ original = original.with_indifferent_access
+
+ slice = original.slice(:login, :password)
+
+ assert_equal "bender", slice[:login]
+ assert_equal "bender", slice["login"]
+ end
+
+ def test_indifferent_extract
+ original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
+ expected = { a: 1, b: 2 }.with_indifferent_access
+ remaining = { c: 3, d: 4 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ copy = original.dup
+ assert_equal expected, copy.extract!(*keys)
+ assert_equal remaining, copy
+ end
+ end
+
+ def test_new_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
+ assert hash.key?("a")
+ assert_equal 1, hash[:a]
+ end
+
+ def test_dup_with_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, v| raise "walrus" }
+ assert_nothing_raised { hash.dup }
+ end
+
+ def test_dup_with_default_proc_sets_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| k + 1 }
+ new_hash = hash.dup
+
+ assert_equal 3, new_hash[2]
+
+ new_hash.default = 2
+ assert_equal 2, new_hash[:non_existent]
+ end
+
+ def test_to_hash_with_raising_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ assert_nothing_raised { hash.to_hash }
+ end
+
+ def test_new_with_to_hash_conversion_copies_default
+ normal_hash = Hash.new(3)
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
+
+ def test_new_with_to_hash_conversion_copies_default_proc
+ normal_hash = Hash.new { 1 + 2 }
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
+
+ def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain
+ klass = Class.new(::HashWithIndifferentAccess)
+ assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1]
+ end
+
+ def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars
+ klass = Class.new(::HashWithIndifferentAccess) do
+ def initialize(*)
+ @foo = "bar"
+ super
+ end
+ end
+
+ yaml_output = klass.new.to_yaml
+
+ # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW)
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9")
+ assert_includes yaml_output, "hash-with-ivars"
+ assert_includes yaml_output, "@foo: bar"
+ else
+ assert_includes yaml_output, "hash"
+ end
+ end
+
+ def test_should_use_default_proc_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_return_nil_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_nil hash_wia.default
+ end
+
+ def test_should_use_default_value_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_use_default_value_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_nil_if_no_default_value_is_supplied
+ hash_wia = HashWithIndifferentAccess.new
+ assert_nil hash_wia.default
+ end
+
+ def test_should_return_dup_for_with_indifferent_access
+ hash_wia = HashWithIndifferentAccess.new
+ assert_equal hash_wia, hash_wia.with_indifferent_access
+ assert_not_same hash_wia, hash_wia.with_indifferent_access
+ end
+
+ def test_allows_setting_frozen_array_values_with_indifferent_access
+ value = [1, 2, 3].freeze
+ hash = HashWithIndifferentAccess.new
+ hash[:key] = value
+ assert_equal hash[:key], value
+ end
+
+ def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new(3)
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new do
+ 2 + 1
+ end
+ assert_equal 3, hash[:foo]
+
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia[:foo]
+ assert_equal 3, hash_wia[:bar]
+ end
+end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 03d7b3fe94..14bc10513b 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -119,6 +119,13 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ MixtureToTitleCaseWithKeepIdSuffix.each_with_index do |(before, titleized), index|
+ define_method "test_titleize_with_keep_id_suffix_mixture_to_title_case_#{index}" do
+ assert_equal(titleized, ActiveSupport::Inflector.titleize(before, keep_id_suffix: true),
+ "mixture to TitleCase with keep_id_suffix failed for #{before}")
+ end
+ end
+
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, ActiveSupport::Inflector.camelize(underscore))
@@ -324,6 +331,12 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, ActiveSupport::Inflector.humanize(underscore, keep_id_suffix: true))
+ end
+ end
+
def test_humanize_by_rule
ActiveSupport::Inflector.inflections do |inflect|
inflect.human(/_cnt$/i, '\1_count')
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index b660987d92..d61ca3fc18 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -248,12 +248,27 @@ module InflectorTestCases
"_external_id" => "External"
}
+ UnderscoreToHumanWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This is a string ending with id",
+ "employee_id" => "Employee id",
+ "employee_id_something_else" => "Employee id something else",
+ "underground" => "Underground",
+ "_id" => "Id",
+ "_external_id" => "External id"
+ }
+
UnderscoreToHumanWithoutCapitalize = {
"employee_salary" => "employee salary",
"employee_id" => "employee",
"underground" => "underground"
}
+ MixtureToTitleCaseWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This Is A String Ending With Id",
+ "EmployeeId" => "Employee Id",
+ "Author Id" => "Author Id"
+ }
+
MixtureToTitleCase = {
"active_record" => "Active Record",
"ActiveRecord" => "Active Record",
@@ -271,6 +286,7 @@ module InflectorTestCases
"¿por qué?" => "¿Por Qué?",
"Fred’s" => "Fred’s",
"Fred`s" => "Fred`s",
+ "this was 'fake news'" => "This Was 'Fake News'",
ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num"
}
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 4794b55742..de111cc40e 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -215,6 +215,95 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal secs, twz.to_f
end
+ def test_iso8601
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T19:00:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_fractional_seconds
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T19:00:00.750")
+ assert_equal 750000, twz.time.usec
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_zone
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T14:00:00-10:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_invalid_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.iso8601("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_iso8601_with_missing_time_components
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31")
+ assert_equal Time.utc(1999, 12, 31, 0, 0, 0), twz.time
+ assert_equal Time.utc(1999, 12, 31, 5, 0, 0), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_old_date
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1883-12-31T19:00:00")
+ assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_far_future_date_with_time_zone_offset_in_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC
+ assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_should_not_black_out_system_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.iso8601("2012-03-25T03:29:00")
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_iso8601_should_black_out_app_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.iso8601("2012-03-11T02:29:00")
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_iso8601_doesnt_use_local_dst
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["UTC"]
+ twz = zone.iso8601("2013-03-10T02:00:00")
+ assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
+ end
+ end
+
+ def test_iso8601_handles_dst_jump
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2013-03-10T02:00:00")
+ assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
+ end
+ end
+
def test_parse
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("1999-12-31 19:00:00")
@@ -314,6 +403,99 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_rfc3339
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("1999-12-31T14:00:00-10:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_with_fractional_seconds
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T14:00:00.750-10:00")
+ assert_equal 750000, twz.time.usec
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_with_missing_time
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("1999-12-31")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_missing_offset
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("1999-12-31T19:00:00")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_invalid_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_old_date
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("1883-12-31T19:00:00-05:00")
+ assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_far_future_date_with_time_zone_offset_in_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC
+ assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_should_not_black_out_system_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.rfc3339("2012-03-25T03:29:00-07:00")
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_rfc3339_should_black_out_app_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.rfc3339("2012-03-11T02:29:00-08:00")
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_rfc3339_doesnt_use_local_dst
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["UTC"]
+ twz = zone.rfc3339("2013-03-10T02:00:00Z")
+ assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
+ end
+ end
+
+ def test_rfc3339_handles_dst_jump
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2013-03-10T02:00:00-05:00")
+ assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
+ end
+ end
+
def test_strptime
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S")
@@ -532,6 +714,10 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"]
end
+ def test_country_zones_without_mappings
+ assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"]
+ end
+
def test_to_yaml
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml)
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml)
diff --git a/ci/travis.rb b/ci/travis.rb
index eb2890ca70..bb87c8f4ad 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -152,6 +152,7 @@ results = {}
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 RUBY_VERSION < "2.4" && isolated
next if gem == "railties" && isolated
next if gem == "ac" && isolated
next if gem == "ac:integration" && isolated
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 3a602efb3d..d8b122d264 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,6 +1 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* No changes.
-
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/guides/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/Rakefile b/guides/Rakefile
index ccb42f3273..0a591558e1 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -2,15 +2,28 @@ namespace :guides do
desc 'Generate guides (for authors), use ONLY=foo to process just "foo.md"'
task generate: "generate:html"
+ # Guides are written in UTF-8, but the environment may be configured for some
+ # other locale, these tasks are responsible for ensuring the default external
+ # encoding is UTF-8.
+ #
+ # Real use cases: Generation was reported to fail on a machine configured with
+ # GBK (Chinese). The docs server once got misconfigured somehow and had "C",
+ # which broke generation too.
+ task :encoding do
+ %w(LANG LANGUAGE LC_ALL).each do |env_var|
+ ENV[env_var] = "en_US.UTF-8"
+ end
+ end
+
namespace :generate do
desc "Generate HTML guides"
- task :html do
+ task :html => :encoding do
ENV["WARNINGS"] = "1" # authors can't disable this
ruby "rails_guides.rb"
end
desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/gp/feature.html?docId=1000765211"
- task :kindle do
+ task :kindle => :encoding do
require "kindlerb"
unless Kindlerb.kindlegen_available?
abort "Please run `setupkindlerb` to install kindlegen"
@@ -25,7 +38,7 @@ namespace :guides do
# Validate guides -------------------------------------------------------------------------
desc 'Validate guides, use ONLY=foo to process just "foo.html"'
- task :validate do
+ task :validate => :encoding do
ruby "w3c_validator.rb"
end
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index b56699a0d0..b27776745a 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -294,6 +294,10 @@ a, a:link, a:visited {
#mainCol a, #subCol a, #feature a {color: #980905;}
#mainCol a code, #subCol a code, #feature a code {color: #980905;}
+#mainCol a.anchorlink, #mainCol a.anchorlink code {color: #333;}
+#mainCol a.anchorlink { text-decoration: none; }
+#mainCol a.anchorlink:hover { text-decoration: underline; }
+
/* Navigation
--------------------------------------- */
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 85d698f81b..46fabca3e8 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "rails", "5.0.1"
+ gem "rails", "5.1.0.rc1"
end
require "rack/test"
diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
index f715caec95..71fe356ea0 100644
--- a/guides/bug_report_templates/active_job_gem.rb
+++ b/guides/bug_report_templates/active_job_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activejob", "5.0.1"
+ gem "activejob", "5.1.0.rc1"
end
require "minitest/autorun"
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
index f61518713f..7591470440 100644
--- a/guides/bug_report_templates/active_job_master.rb
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -8,6 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
end
require "active_job"
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 98edcb76b1..a685c257ea 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activerecord", "5.0.1"
+ gem "activerecord", "5.1.0.rc1"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 7265a671b0..8bbc1ef19e 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -8,6 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb
index ece6ae4f12..b4e822dfe0 100644
--- a/guides/bug_report_templates/active_record_migrations_gem.rb
+++ b/guides/bug_report_templates/active_record_migrations_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activerecord", "5.0.1"
+ gem "activerecord", "5.1.0.rc1"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
index 13a375d1ba..84a4b71909 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -8,6 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index fa9f94eea0..e1b705bea4 100644
--- a/guides/bug_report_templates/generic_gem.rb
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activesupport", "5.0.1"
+ gem "activesupport", "5.1.0.rc1"
end
require "active_support/core_ext/object/blank"
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index d3a7ae4ac4..ed45726e92 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -8,6 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
end
require "active_support"
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index bf2cc82c7c..16aaa7d1eb 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -105,6 +105,10 @@ module RailsGuides
node.inner_html = "#{node_index(hierarchy)} #{node.inner_html}"
end
end
+
+ doc.css('h3, h4, h5, h6').each do |node|
+ node.inner_html = "<a class='anchorlink' href='##{node[:id]}'>#{node.inner_html}</a>"
+ end
end.to_html
end
end
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
new file mode 100644
index 0000000000..43b5a0fdaf
--- /dev/null
+++ b/guides/source/5_1_release_notes.md
@@ -0,0 +1,382 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+Ruby on Rails 5.1 Release Notes
+===============================
+
+Highlights in Rails 5.1:
+
+* Yarn Support
+* Optional Webpack support
+* jQuery no longer a default dependency
+* System tests
+* Encrypted secrets
+* Parameterized mailers
+* Direct & resolved routes
+* Unification of form_for and form_tag into form_with
+
+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-1-stable) in the main Rails
+repository on GitHub.
+
+--------------------------------------------------------------------------------
+
+Upgrading to Rails 5.1
+----------------------
+
+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 5.0 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 5.1. 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-5-0-to-rails-5-1)
+guide.
+
+
+Major Features
+--------------
+
+### Yarn Support
+
+[Pull Request](https://github.com/rails/rails/pull/26836)
+
+Rails 5.1 will allow managing JavaScript dependencies
+from NPM via Yarn. This will make it easy to use libraries like React, VueJS
+or any other library from NPM world. The Yarn support is integrated with
+the asset pipeline so that all dependencies will work seamlessly with the
+Rails 5.1 app.
+
+### Optional Webpack support
+
+[Pull Request](https://github.com/rails/rails/pull/27288)
+
+Rails apps can integrate with [Webpack](https://webpack.js.org/), a JavaScript
+asset bundler, more easily using the new [Webpacker](https://github.com/rails/webpacker)
+gem. Use the `--webpack` flag when generating new applications to enable Webpack
+integration.
+
+This is fully compatible with the asset pipeline, which you can continue to use for
+images, fonts, sounds, and other assets. You can even have some JavaScript code
+managed by the asset pipeline, and other code processed via Webpack. All of this is managed
+by Yarn, which is enabled by default.
+
+### jQuery no longer a default dependency
+
+[Pull Request](https://github.com/rails/rails/pull/27113)
+
+jQuery was required by default in earlier versions of Rails to provide features
+like `data-remote`, `data-confirm` and other parts of Rails' Unobtrusive JavaScript
+offerings. It is no longer required, as the UJS has been rewritten to use plain,
+vanilla JavaScript. This code now ships inside of Action View as
+`rails-ujs`.
+
+You can still use the jQuery version if needed, but it is no longer required by default.
+
+### System tests
+
+[Pull Request](https://github.com/rails/rails/pull/26703)
+
+Rails 5.1 has baked-in support for writing Capybara tests, in the form of
+System tests. You need no longer worry about configuring Capybara and
+database cleaning strategies for such tests. Rails 5.1 provides a wrapper
+for running tests in Chrome with additional features such as failure
+screenshots.
+
+### Encrypted secrets
+
+[Pull Request](https://github.com/rails/rails/pull/28038)
+
+Rails will now allow management of application secrets in a secure way,
+building on top of the [sekrets](https://github.com/ahoward/sekrets) gem.
+
+Run `bin/rails secrets:setup` to setup a new encrypted secrets file. This will
+also generate a master key, which must be stored outside of the repository. The
+secrets themselves can then be safely checked into the revision control system,
+in an encrypted form.
+
+Secrets will be decrypted in production, using a key stored either in the
+`RAILS_MASTER_KEY` environment variable, or in a key file.
+
+### Parameterized mailers
+
+[Pull Request](https://github.com/rails/rails/pull/27825)
+
+Allows specifying common params used for all methods in a mailer class
+to share instance variables, headers and other common setup.
+
+``` ruby
+class InvitationsMailer < ApplicationMailer
+
+ before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+ before_action { @account = params[:inviter].account }
+
+ def account_invitation
+ mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ end
+
+ def project_invitation
+ @project = params[:project]
+ @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+
+ mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ end
+end
+
+InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+```
+
+### Direct & resolved routes
+
+[Pull Request](https://github.com/rails/rails/pull/23138)
+
+Rails 5.1 has added two new methods, `resolve` and `direct`, to the routing
+DSL.
+
+The `resolve` method allows customizing polymorphic mapping of models.
+
+``` ruby
+resource :basket
+
+resolve("Basket") { [:basket] }
+```
+
+``` erb
+<%= form_for @basket do |form| %>
+ <!-- basket form -->
+<% end %>
+```
+
+This will generate the singular URL `/basket` instead of the usual `/baskets/:id`.
+
+The `direct` method allows creation of custom URL helpers.
+
+``` ruby
+direct(:homepage) { "http://www.rubyonrails.org" }
+
+>> homepage_url
+=> "http://www.rubyonrails.org"
+```
+
+The return value of the block must be a valid argument for the `url_for`
+method. So, you can pass a valid string URL, Hash, Array, an
+Active Model instance, or an Active Model class.
+
+``` ruby
+direct :commentable do |model|
+ [ model, anchor: model.dom_id ]
+end
+
+direct :main do
+ { controller: 'pages', action: 'index', subdomain: 'www' }
+end
+```
+
+### Unification of form_for and form_tag into form_with
+
+[Pull Request](https://github.com/rails/rails/pull/26976)
+
+Before Rails 5.1, there were two interfaces for handling HTML forms:
+`form_for` for model instances and `form_tag` for custom URLs.
+
+Rails 5.1 combines both of these interfaces with `form_with`, and
+can generate form tags based on URLs, scopes or models.
+
+``` erb
+# Using just a URL:
+
+<%= form_with url: posts_path do |form| %>
+ <%= form.text_field :title %>
+<% end %>
+
+# =>
+<form action="/posts" method="post" data-remote="true">
+ <input type="text" name="title">
+</form>
+
+# Adding a scope prefixes the input field names:
+
+<%= form_with scope: :post, url: posts_path do |form| %>
+ <%= form.text_field :title %>
+<% end %>
+# =>
+<form action="/posts" method="post" data-remote="true">
+ <input type="text" name="post[title]">
+</form>
+
+# Using a model infers both the URL and scope:
+
+<%= form_with model: Post.new do |form| %>
+ <%= form.text_field :title %>
+<% end %>
+# =>
+<form action="/posts" method="post" data-remote="true">
+ <input type="text" name="post[title]">
+</form>
+
+# An existing model makes an update form and fills out field values:
+
+<%= form_with model: Post.first do |form| %>
+ <%= form.text_field :title %>
+<% end %>
+# =>
+<form action="/posts/1" method="post" data-remote="true">
+ <input type="hidden" name="_method" value="patch">
+ <input type="text" name="post[title]" value="<the title of the post>">
+</form>
+```
+
+Railties
+--------
+
+Please refer to the [Changelog][railties] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Action Cable
+-----------
+
+Please refer to the [Changelog][action-cable] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+* Added support for `channel_prefix` to Redis and evented Redis adapters
+ in `cable.yml` to avoid name collisions when using the same Redis server
+ with multiple applications.
+ ([Pull Request](https://github.com/rails/rails/pull/27425))
+
+Action Pack
+-----------
+
+Please refer to the [Changelog][action-pack] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Action View
+-------------
+
+Please refer to the [Changelog][action-view] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Action Mailer
+-------------
+
+Please refer to the [Changelog][action-mailer] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+* Allowed setting custom content type when attachments are included
+ and body is set inline.
+ ([Pull Request](https://github.com/rails/rails/pull/27227))
+
+* Allowed passing lambdas as values to the `default` method.
+ ([Commit](https://github.com/rails/rails/commit/1cec84ad2ddd843484ed40b1eb7492063ce71baf))
+
+* Added support for parameterized invocation of mailers to share before filters and defaults
+ between different mailer actions.
+ ([Commit](https://github.com/rails/rails/commit/1cec84ad2ddd843484ed40b1eb7492063ce71baf))
+
+* Passed the incoming arguments to the mailer action to `process.action_mailer` event under
+ an `args` key.
+ ([Pull Request](https://github.com/rails/rails/pull/27900))
+
+Active Record
+-------------
+
+Please refer to the [Changelog][active-record] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+* Skipped comments in the output of `mysqldump` command by default.
+ ([Pull Request](https://github.com/rails/rails/pull/23301))
+
+* Fixed `ActiveRecord::Relation#count` to use Ruby's `Enumerable#count` for counting
+ records when a block is passed as argument instead of silently ignoring the
+ passed block.
+ ([Pull Request](https://github.com/rails/rails/pull/24203))
+
+* Pass `"-v ON_ERROR_STOP=1"` flag with `psql` command to not suppress SQL errors.
+ ([Pull Request](https://github.com/rails/rails/pull/24773))
+
+Active Model
+------------
+
+Please refer to the [Changelog][active-model] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Active Job
+-----------
+
+Please refer to the [Changelog][active-job] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+Active Support
+--------------
+
+Please refer to the [Changelog][active-support] for detailed changes.
+
+### Removals
+
+### Deprecations
+
+### Notable changes
+
+* Added `Module#delegate_missing_to` to delegate method calls not
+ defined for the current object to a proxy object.
+ ([Pull Request](https://github.com/rails/rails/pull/23930))
+
+* Added `Date#all_day` which returns a range representing the whole day
+ of the current date & time.
+ ([Pull Request](https://github.com/rails/rails/pull/24930))
+
+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-1-stable/railties/CHANGELOG.md
+[action-pack]: https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md
+[action-view]: https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md
+[action-mailer]: https://github.com/rails/rails/blob/5-1-stable/actionmailer/CHANGELOG.md
+[action-cable]: https://github.com/rails/rails/blob/5-1-stable/actioncable/CHANGELOG.md
+[active-record]: https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md
+[active-model]: https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md
+[active-support]: https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md
+[active-job]: https://github.com/rails/rails/blob/5-1-stable/activejob/CHANGELOG.md
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 5bd1ea4d22..8afec00018 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -10,12 +10,13 @@
</p>
<% else %>
<p>
- These are the new guides for Rails 5.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These are the new guides for Rails 5.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
</p>
<% end %>
<p>
The guides for earlier releases:
+<a href="http://guides.rubyonrails.org/v5.0/">Rails 5.0</a>,
<a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>,
<a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>,
<a href="http://guides.rubyonrails.org/v4.0/">Rails 4.0</a>,
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index e929945dd0..50a28571b4 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -6,7 +6,7 @@ incorporate real-time features into your Rails application.
After reading this guide, you will know:
-* What Action Cable is and its integration on backend and frontend
+* What Action Cable is and its integration on backend and frontend
* How to setup Action Cable
* How to setup channels
* Deployment and Architecture setup for running Action Cable
@@ -64,8 +64,8 @@ module ApplicationCable
private
def find_verified_user
- if current_user = User.find_by(id: cookies.signed[:user_id])
- current_user
+ if verified_user = User.find_by(id: cookies.signed[:user_id])
+ verified_user
else
reject_unauthorized_connection
end
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 40eb838d32..5d987264f5 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -61,7 +61,7 @@ end
The [Layouts & Rendering Guide](layouts_and_rendering.html) explains this in more detail.
-`ApplicationController` inherits from `ActionController::Base`, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the API documentation or in the source itself.
+`ApplicationController` inherits from `ActionController::Base`, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the [API documentation](http://api.rubyonrails.org/classes/ActionController.html) or in the source itself.
Only public methods are callable as actions. It is a best practice to lower the visibility of methods (with `private` or `protected`) which are not intended to be actions, like auxiliary methods or filters.
@@ -719,7 +719,7 @@ Now, the `LoginsController`'s `new` and `create` actions will work as before wit
In addition to "before" filters, you can also run filters after an action has been executed, or both before and after.
-"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running.
+"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running. Please note that "after" filters are executed only after a successful action, but not when an exception is raised in the request cycle.
"around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work.
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 380fdac658..65146ee7da 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -550,8 +550,9 @@ url helper.
<%= user_url(@user, host: 'example.com') %>
```
-NOTE: non-`GET` links require [jQuery UJS](https://github.com/rails/jquery-ujs)
-and won't work in mailer templates. They will result in normal `GET` requests.
+NOTE: non-`GET` links require [rails-ujs](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts) or
+[jQuery UJS](https://github.com/rails/jquery-ujs), and won't work in mailer templates.
+They will result in normal `GET` requests.
### Adding images in Action Mailer Views
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index c65d1e6de5..b58ca61848 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -114,7 +114,7 @@ For enqueuing and executing jobs in production you need to set up a queuing back
that is to say you need to decide for a 3rd-party queuing library that Rails should use.
Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM.
If the process crashes or the machine is reset, then all outstanding jobs are lost with the
-default async back-end. This may be fine for smaller apps or non-critical jobs, but most
+default async backend. This may be fine for smaller apps or non-critical jobs, but most
production apps will need to pick a persistent backend.
### Backends
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 732e553c62..e26805d22c 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -87,7 +87,7 @@ end
### Conversion
If a class defines `persisted?` and `id` methods, then you can include the
-`ActiveModel::Conversion` module in that class and call the Rails conversion
+`ActiveModel::Conversion` module in that class, and call the Rails conversion
methods on objects of that class.
```ruby
@@ -156,16 +156,17 @@ person.changed? # => false
person.first_name = "First Name"
person.first_name # => "First Name"
-# returns true if any of the attributes have unsaved changes, false otherwise.
+# returns true if any of the attributes have unsaved changes.
person.changed? # => true
# returns a list of attributes that have changed before saving.
person.changed # => ["first_name"]
-# returns a hash of the attributes that have changed with their original values.
+# returns a Hash of the attributes that have changed with their original values.
person.changed_attributes # => {"first_name"=>nil}
-# returns a hash of changes, with the attribute names as the keys, and the values will be an array of the old and new value for that field.
+# returns a Hash of changes, with the attribute names as the keys, and the
+# values as an array of the old and new values for that field.
person.changes # => {"first_name"=>[nil, "First Name"]}
```
@@ -179,7 +180,7 @@ person.first_name # => "First Name"
person.first_name_changed? # => true
```
-Track what was the previous value of the attribute.
+Track the previous value of the attribute.
```ruby
# attr_name_was accessor
@@ -187,7 +188,7 @@ person.first_name_was # => nil
```
Track both previous and current value of the changed attribute. Returns an array
-if changed, else returns nil.
+if changed, otherwise returns nil.
```ruby
# attr_name_change
@@ -197,7 +198,7 @@ person.last_name_change # => nil
### Validations
-The `ActiveModel::Validations` module adds the ability to validate class objects
+The `ActiveModel::Validations` module adds the ability to validate objects
like in Active Record.
```ruby
@@ -225,7 +226,7 @@ person.valid? # => raises ActiveModel::StrictValidationFa
### Naming
-`ActiveModel::Naming` adds a number of class methods which make the naming and routing
+`ActiveModel::Naming` adds a number of class methods which make naming and routing
easier to manage. The module defines the `model_name` class method which
will define a number of accessors using some `ActiveSupport::Inflector` methods.
@@ -248,7 +249,7 @@ Person.model_name.singular_route_key # => "person"
### Model
-`ActiveModel::Model` adds the ability to a class to work with Action Pack and
+`ActiveModel::Model` adds the ability for a class to work with Action Pack and
Action View right out of the box.
```ruby
@@ -293,7 +294,7 @@ objects.
### Serialization
`ActiveModel::Serialization` provides basic serialization for your object.
-You need to declare an attributes hash which contains the attributes you want to
+You need to declare an attributes Hash which contains the attributes you want to
serialize. Attributes must be strings, not symbols.
```ruby
@@ -308,7 +309,7 @@ class Person
end
```
-Now you can access a serialized hash of your object using the `serializable_hash`.
+Now you can access a serialized Hash of your object using the `serializable_hash` method.
```ruby
person = Person.new
@@ -319,13 +320,14 @@ person.serializable_hash # => {"name"=>"Bob"}
#### ActiveModel::Serializers
-Rails provides an `ActiveModel::Serializers::JSON` serializer.
-This module automatically include the `ActiveModel::Serialization`.
+Active Model also provides the `ActiveModel::Serializers::JSON` module
+for JSON serializing / deserializing. This module automatically includes the
+previously discussed `ActiveModel::Serialization` module.
##### ActiveModel::Serializers::JSON
-To use the `ActiveModel::Serializers::JSON` you only need to change from
-`ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`.
+To use `ActiveModel::Serializers::JSON` you only need to change the
+module you are including from `ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`.
```ruby
class Person
@@ -339,7 +341,8 @@ class Person
end
```
-With the `as_json` method you have a hash representing the model.
+The `as_json` method, similar to `serializable_hash`, provides a Hash representing
+the model.
```ruby
person = Person.new
@@ -348,8 +351,8 @@ person.name = "Bob"
person.as_json # => {"name"=>"Bob"}
```
-From a JSON string you define the attributes of the model.
-You need to have the `attributes=` method defined on your class:
+You can also define the attributes for a model from a JSON string.
+However, you need to define the `attributes=` method on your class:
```ruby
class Person
@@ -369,7 +372,7 @@ class Person
end
```
-Now it is possible to create an instance of person and set the attributes using `from_json`.
+Now it is possible to create an instance of `Person` and set attributes using `from_json`.
```ruby
json = { name: 'Bob' }.to_json
@@ -389,8 +392,8 @@ class Person
end
```
-With the `human_attribute_name` you can transform attribute names into a more
-human format. The human format is defined in your locale file.
+With the `human_attribute_name` method, you can transform attribute names into a
+more human-readable format. The human-readable format is defined in your locale file(s).
* config/locales/app.pt-BR.yml
@@ -411,7 +414,7 @@ Person.human_attribute_name('name') # => "Nome"
`ActiveModel::Lint::Tests` allows you to test whether an object is compliant with
the Active Model API.
-* app/models/person.rb
+* `app/models/person.rb`
```ruby
class Person
@@ -419,7 +422,7 @@ the Active Model API.
end
```
-* test/models/person_test.rb
+* `test/models/person_test.rb`
```ruby
require 'test_helper'
@@ -454,9 +457,9 @@ features out of the box.
### SecurePassword
`ActiveModel::SecurePassword` provides a way to securely store any
-password in an encrypted form. On including this module, a
+password in an encrypted form. When you include this module, a
`has_secure_password` class method is provided which defines
-an accessor named `password` with certain validations on it.
+a `password` accessor with certain validations on it.
#### Requirements
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 77bd3c97e8..b1705855d0 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -117,6 +117,10 @@ Here is a list with all the available Active Record callbacks, listed in the sam
WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
+NOTE: `before_destroy` callbacks should be placed before `dependent: :destroy`
+associations (or use the `prepend: true` option), to ensure they execute before
+the records are deleted by `dependent: :destroy`.
+
### `after_initialize` and `after_find`
The `after_initialize` callback will be called whenever an Active Record object is instantiated, either by directly using `new` or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record `initialize` method.
@@ -254,7 +258,11 @@ Halting Execution
As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
-The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception.
+The whole callback chain is wrapped in a transaction. If any callback raises an exception, the execution chain gets halted and a ROLLBACK is issued. To intentionally stop a chain use:
+
+```ruby
+throw :abort
+```
WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 31220f9be2..26d01d4ede 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -953,9 +953,6 @@ class Client < ApplicationRecord
end
```
-NOTE: Please note that the optimistic locking will be ignored if you update the
-locking column's value.
-
### Pessimistic Locking
Pessimistic locking uses a locking mechanism provided by the underlying database. Using `lock` when building a relation obtains an exclusive lock on the selected rows. Relations using `lock` are usually wrapped inside a transaction for preventing deadlock conditions.
@@ -1384,8 +1381,9 @@ class Client < ApplicationRecord
end
```
-NOTE: The `default_scope` is also applied while creating/building a record.
-It is not applied while updating a record. E.g.:
+NOTE: The `default_scope` is also applied while creating/building a record
+when the scope arguments are given as a `Hash`. It is not applied while
+updating a record. E.g.:
```ruby
class Client < ApplicationRecord
@@ -1396,6 +1394,17 @@ Client.new # => #<Client id: nil, active: true>
Client.unscoped.new # => #<Client id: nil, active: nil>
```
+Be aware that, when given in the `Array` format, `default_scope` query arguments
+cannot be converted to a `Hash` for default attribute assignment. E.g.:
+
+```ruby
+class Client < ApplicationRecord
+ default_scope { where("active = ?", true) }
+end
+
+Client.new # => #<Client id: nil, active: nil>
+```
+
### Merging of scopes
Just like `where` clauses scopes are merged using `AND` conditions.
@@ -1547,7 +1556,7 @@ SELECT people.id, people.name, comments.text
FROM people
INNER JOIN comments
ON comments.person_id = people.id
-WHERE comments.created_at = '2015-01-01'
+WHERE comments.created_at > '2015-01-01'
```
### Retrieving specific data from multiple tables
@@ -1871,7 +1880,7 @@ Which will execute:
```sql
SELECT count(DISTINCT clients.id) AS count_all FROM clients
- LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE
+ LEFT OUTER JOIN orders ON orders.client_id = clients.id WHERE
(clients.first_name = 'Ryan' AND orders.status = 'received')
```
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 32b38cde5e..5313361dfd 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -490,9 +490,6 @@ If you set `:only_integer` to `true`, then it will use the
regular expression to validate the attribute's value. Otherwise, it will try to
convert the value to a number using `Float`.
-WARNING. Note that the regular expression above allows a trailing newline
-character.
-
```ruby
class Player < ApplicationRecord
validates :points, numericality: true
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 34b9c0d2ca..3c61754982 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -333,10 +333,6 @@ As a contributor, it's important to think about whether this API is meant for en
A class or module is marked with `:nodoc:` to indicate that all methods are internal API and should never be used directly.
-If you come across an existing `:nodoc:` you should tread lightly. Consider asking someone from the core team or author of the code before removing it. This should almost always happen through a pull request instead of the docrails project.
-
-A `:nodoc:` should never be added simply because a method or class is missing documentation. There may be an instance where an internal public method wasn't given a `:nodoc:` by mistake, for example when switching a method from private to public visibility. When this happens it should be discussed over a PR on a case-by-case basis and never committed directly to docrails.
-
To summarize, the Rails team uses `:nodoc:` to mark publicly visible methods and classes for internal use; changes to the visibility of API should be considered carefully and discussed over a pull request first.
Regarding the Rails Stack
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 68dde4482f..61b7112247 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -335,7 +335,7 @@ an asset has been updated and if so loads it into the page:
<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %>
```
-In regular views you can access images in the `public/assets/images` directory
+In regular views you can access images in the `app/assets/images` directory
like this:
```erb
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 6e68935f9b..5794bfa666 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -154,7 +154,7 @@ case, the column definition might look like this:
```ruby
create_table :accounts do |t|
- t.belongs_to :supplier, index: true, unique: true, foreign_key: true
+ t.belongs_to :supplier, index: { unique: true }, foreign_key: true
# ...
end
```
@@ -582,14 +582,30 @@ class CreateBooks < ActiveRecord::Migration[5.0]
t.string :book_number
t.integer :author_id
end
-
- add_index :books, :author_id
end
end
```
If you create an association some time after you build the underlying model, you need to remember to create an `add_column` migration to provide the necessary foreign key.
+It's a good practice to add an index on the foreign key to improve queries
+performance and a foreign key constraint to ensure referential data integrity:
+
+```ruby
+class CreateBooks < ActiveRecord::Migration[5.0]
+ def change
+ create_table :books do |t|
+ t.datetime :published_at
+ t.string :book_number
+ t.integer :author_id
+ end
+
+ add_index :books, :author_id
+ add_foreign_key :books, :authors
+ end
+end
+```
+
#### Creating Join Tables for `has_and_belongs_to_many` Associations
If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical book of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering.
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index fd7626250c..6cdce5c2f4 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -387,6 +387,11 @@ store is not appropriate for large application deployments. However, it can
work well for small, low traffic sites with only a couple of server processes,
as well as development and test environments.
+New Rails projects are configured to use this implementation in development environment by default.
+
+NOTE: Since processes will not share cache data when using `:memory_store`,
+it will not be possible to manually read, write or expire the cache via the Rails console.
+
### ActiveSupport::Cache::FileStore
This cache store uses the file system to store entries. The path to the directory where the store files will be stored must be specified when initializing the cache.
@@ -396,14 +401,15 @@ config.cache_store = :file_store, "/path/to/cache/directory"
```
With this cache store, multiple server processes on the same host can share a
-cache. The cache store is appropriate for low to medium traffic sites that are
+cache. This cache store is appropriate for low to medium traffic sites that are
served off one or two hosts. Server processes running on different hosts could
share a cache by using a shared file system, but that setup is not recommended.
As the cache will grow until the disk is full, it is recommended to
periodically clear out old entries.
-This is the default cache store implementation.
+This is the default cache store implementation (at `"#{root}/tmp/cache/"`) if
+no explicit `config.cache_store` is supplied.
### ActiveSupport::Cache::MemCacheStore
@@ -570,6 +576,20 @@ You can also set the strong ETag directly on the response.
response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
```
+Caching in Development
+----------------------
+
+It's common to want to test the caching strategy of your application
+in development mode. Rails provides the rake task `dev:cache` to
+easily toggle caching on/off.
+
+```bash
+$ bin/rails dev:cache
+Development mode is now being cached.
+$ bin/rails dev:cache
+Development mode is no longer being cached.
+```
+
References
----------
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index c8d559745e..3360496c08 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -63,7 +63,7 @@ With no further work, `rails server` will run our new shiny Rails app:
$ cd commandsapp
$ bin/rails server
=> Booting Puma
-=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
+=> Rails 5.1.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.0.2 (ruby 2.3.0-p0), codename: Plethora of Penguin Pinatas
@@ -294,7 +294,7 @@ If you wish to test out some code without changing any data, you can do that by
```bash
$ bin/rails console --sandbox
-Loading development environment in sandbox (Rails 5.0.0)
+Loading development environment in sandbox (Rails 5.1.0)
Any modifications you make will be rolled back on exit
irb(main):001:0>
```
@@ -428,12 +428,12 @@ INFO: You can also use `bin/rails -T` to get the list of tasks.
```bash
$ bin/rails about
About your application's environment
-Rails version 5.0.0
+Rails version 5.1.0
Ruby version 2.2.2 (x86_64-linux)
RubyGems version 2.4.6
-Rack version 1.6
+Rack version 2.0.1
JavaScript Runtime Node.js (V8)
-Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag
+Middleware: Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, ActiveSupport::Cache::Strategy::LocalCache::Middleware, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, ActionDispatch::RemoteIp, Sprockets::Rails::QuietAssets, Rails::Rack::Logger, ActionDispatch::ShowExceptions, WebConsole::Middleware, ActionDispatch::DebugExceptions, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag
Application root /home/foobar/commandsapp
Environment development
Database adapter sqlite3
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index de921e2705..ae70b06996 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -32,7 +32,7 @@ Configuring Rails Components
In general, the work of configuring Rails means configuring the components of Rails, as well as configuring Rails itself. The configuration file `config/application.rb` and environment-specific configuration files (such as `config/environments/production.rb`) allow you to specify the various settings that you want to pass down to all of the components.
-For example, the `config/application.rb` file includes this setting:
+For example, you could add this setting to `config/application.rb` file:
```ruby
config.time_zone = 'Central Time (US & Canada)'
@@ -350,9 +350,9 @@ All these configuration options are delegated to the `I18n` library.
`config/environments/production.rb` which is generated by Rails. The
default value is `true` if this configuration is not set.
-* `config.active_record.dump_schemas` controls which database schemas will be dumped when calling db:structure:dump.
- The options are `:schema_search_path` (the default) which dumps any schemas listed in schema_search_path,
- `:all` which always dumps all schemas regardless of the schema_search_path,
+* `config.active_record.dump_schemas` controls which database schemas will be dumped when calling `db:structure:dump`.
+ The options are `:schema_search_path` (the default) which dumps any schemas listed in `schema_search_path`,
+ `:all` which always dumps all schemas regardless of the `schema_search_path`,
or a string of comma separated schemas.
* `config.active_record.belongs_to_required_by_default` is a boolean value and
@@ -362,10 +362,10 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.warn_on_records_fetched_greater_than` allows setting a
warning threshold for query result size. If the number of records returned
by a query exceeds the threshold, a warning is logged. This can be used to
- identify queries which might be causing memory bloat.
+ identify queries which might be causing a memory bloat.
* `config.active_record.index_nested_attribute_errors` allows errors for nested
- has_many relationships to be displayed with an index as well as the error.
+ `has_many` relationships to be displayed with an index as well as the error.
Defaults to `false`.
* `config.active_record.use_schema_cache_dump` enables users to get schema cache information
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index fe5437ae5d..39f4272b3c 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -15,7 +15,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
+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](http://rubyonrails.org/conduct/).
--------------------------------------------------------------------------------
@@ -98,13 +98,13 @@ Anything you can do to make bug reports more succinct or easier to reproduce hel
### Testing Patches
-You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. To apply someone's changes you need first to create a dedicated branch:
+You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. In order to apply someone's changes, you need to first create a dedicated branch:
```bash
$ git checkout -b testing_branch
```
-Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails.
+Then, you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails.
```bash
$ git remote add JohnSmith https://github.com/JohnSmith/rails.git
@@ -132,24 +132,12 @@ learn about Ruby on Rails, and the API, which serves as a reference.
You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails.
-You can either open a pull request to [Rails](https://github.com/rails/rails) or
-ask the [Rails core team](http://rubyonrails.org/community/#core) for commit access on
-docrails if you contribute regularly.
-Please do not open pull requests in docrails, if you'd like to get feedback on your
-change, ask for it in [Rails](https://github.com/rails/rails) instead.
-
-Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
-
-If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub.
+To do so, open a pull request to [Rails](https://github.com/rails/rails) on GitHub.
When working with documentation, please take into account the [API Documentation Guidelines](api_documentation_guidelines.html) and the [Ruby on Rails Guides Guidelines](ruby_on_rails_guides_guidelines.html).
-NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements.
-
NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes.
-WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails.
-
Translating Rails Guides
------------------------
@@ -418,16 +406,6 @@ examples or multiple paragraphs. Otherwise, it's best to make a new paragraph.
Some changes require the dependencies to be upgraded. In these cases make sure you run `bundle update` to get the right version of the dependency and commit the `Gemfile.lock` file within your changes.
-### Sanity Check
-
-You should not be the only person who looks at the code before you submit it.
-If you know someone else who uses Rails, try asking them if they'll check out
-your work. If you don't know anyone else using Rails, try hopping into the IRC
-room or posting about your idea to the rails-core mailing list. Doing this in
-private before you push a patch out publicly is the "smoke test" for a patch:
-if you can't convince one other developer of the beauty of your code, you’re
-unlikely to convince the core team either.
-
### Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to Git:
@@ -685,4 +663,4 @@ And then... think about your next contribution!
Rails Contributors
------------------
-All contributions, either via master or docrails, get credit in [Rails Contributors](http://contributors.rubyonrails.org).
+All contributions get credit in [Rails Contributors](http://contributors.rubyonrails.org).
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index ba0cdbf3af..58aab774b3 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -313,7 +313,7 @@ For example:
```bash
=> Booting Puma
-=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
+=> Rails 5.1.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.4.0 (ruby 2.3.1-p112), codename: Owl Bowl Brawl
@@ -445,11 +445,11 @@ then `backtrace` will supply the answer.
--> #0 ArticlesController.index
at /PathToProject/app/controllers/articles_controller.rb:8
#1 ActionController::BasicImplicitRender.send_action(method#String, *args#Array)
- at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/basic_implicit_render.rb:4
+ at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/basic_implicit_render.rb:4
#2 AbstractController::Base.process_action(action#NilClass, *args#Array)
- at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:181
+ at /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb:181
#3 ActionController::Rendering.process_action(action, *args)
- at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:30
+ at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/rendering.rb:30
...
```
@@ -461,7 +461,7 @@ context.
```
(byebug) frame 2
-[176, 185] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb
+[176, 185] in /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb
176: # is the intended way to override action dispatching.
177: #
178: # Notice that the first argument is the method to be dispatched
@@ -606,7 +606,6 @@ You can also inspect for an object method this way:
@new_record = true
@readonly = false
@transaction_state = nil
-@txn = nil
```
You can also use `display` to start watching variables. This is a good way of
@@ -677,7 +676,7 @@ Ruby instruction to be executed -- in this case, Active Support's `week` method.
```
(byebug) step
-[49, 58] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb
+[49, 58] in /PathToGems/activesupport-5.1.0/lib/active_support/core_ext/numeric/time.rb
49:
50: # Returns a Duration instance matching the number of weeks provided.
51: #
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 2925fb4b58..5fccdcccec 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -194,6 +194,11 @@
url: upgrading_ruby_on_rails.html
description: This guide helps in upgrading applications to latest Ruby on Rails versions.
-
+ name: Ruby on Rails 5.1 Release Notes
+ url: 5_1_release_notes.html
+ description: Release notes for Rails 5.1.
+ work_in_progress: true
+ -
name: Ruby on Rails 5.0 Release Notes
url: 5_0_release_notes.html
description: Release notes for Rails 5.0.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 180a786237..2276f348a1 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -14,6 +14,7 @@ After reading this guide, you will know:
* How to build features for the engine.
* How to hook the engine into an application.
* How to override engine functionality in the application.
+* Avoid loading Rails frameworks with Load and Configuration Hooks
--------------------------------------------------------------------------------
@@ -1410,3 +1411,114 @@ module MyEngine
end
end
```
+
+Active Support On Load Hooks
+----------------------------
+
+Active Support is the Ruby on Rails component responsible for providing Ruby language extensions, utilities, and other transversal utilities.
+
+Rails code can often be referenced on load of an application. Rails is responsible for the load order of these frameworks, so when you load frameworks, such as `ActiveRecord::Base`, prematurely you are violating an implicit contract your application has with Rails. Moreover, by loading code such as `ActiveRecord::Base` on boot of your application you are loading entire frameworks which may slow down your boot time and could cause conflicts with load order and boot of your application.
+
+On Load hooks are the API that allow you to hook into this initialization process without violating the load contract with Rails. This will also mitigate boot performance degradation and avoid conflicts.
+
+## What are `on_load` hooks?
+
+Since Ruby is a dynamic language, some code will cause different Rails frameworks to load. Take this snippet for instance:
+
+```ruby
+ActiveRecord::Base.include(MyActiveRecordHelper)
+```
+
+This snippet means that when this file is loaded, it will encounter `ActiveRecord::Base`. This encounter causes Ruby to look for the definition of that constant and will require it. This causes the entire Active Record framework to be loaded on boot.
+
+`ActiveSupport.on_load` is a mechanism that can be used to defer the loading of code until it is actually needed. The snippet above can be changed to:
+
+```ruby
+ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper }
+```
+
+This new snippet will only include `MyActiveRecordHelper` when `ActiveRecord::Base` is loaded.
+
+## How does it work?
+
+In the Rails framework these hooks are called when a specific library is loaded. For example, when `ActionController::Base` is loaded, the `:action_controller_base` hook is called. This means that all `ActiveSupport.on_load` calls with `:action_controller_base` hooks will be called in the context of `ActionController::Base` (that means `self` will be an `ActionController::Base`).
+
+## Modifying code to use `on_load` hooks
+
+Modifying code is generally straightforward. If you have a line of code that refers to a Rails framework such as `ActiveRecord::Base` you can wrap that code in an `on_load` hook.
+
+### Example 1
+
+```ruby
+ActiveRecord::Base.include(MyActiveRecordHelper)
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper } # self refers to ActiveRecord::Base here, so we can simply #include
+```
+
+### Example 2
+
+```ruby
+ActionController::Base.prepend(MyActionControllerHelper)
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:action_controller_base) { prepend MyActionControllerHelper } # self refers to ActionController::Base here, so we can simply #prepend
+```
+
+### Example 3
+
+```ruby
+ActiveRecord::Base.include_root_in_json = true
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:active_record) { self.include_root_in_json = true } # self refers to ActiveRecord::Base here
+```
+
+## Available Hooks
+
+These are the hooks you can use in your own code.
+
+To hook into the initialization process of one of the following classes use the available hook.
+
+| Class | Available Hooks |
+| --------------------------------- | ------------------------------------ |
+| `ActionCable` | `action_cable` |
+| `ActionController::API` | `action_controller_api` |
+| `ActionController::API` | `action_controller` |
+| `ActionController::Base` | `action_controller_base` |
+| `ActionController::Base` | `action_controller` |
+| `ActionController::TestCase` | `action_controller_test_case` |
+| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` |
+| `ActionMailer::Base` | `action_mailer` |
+| `ActionMailer::TestCase` | `action_mailer_test_case` |
+| `ActionView::Base` | `action_view` |
+| `ActionView::TestCase` | `action_view_test_case` |
+| `ActiveJob::Base` | `active_job` |
+| `ActiveJob::TestCase` | `active_job_test_case` |
+| `ActiveRecord::Base` | `active_record` |
+| `ActiveSupport::TestCase` | `active_support_test_case` |
+| `i18n` | `i18n` |
+
+## Configuration hooks
+
+These are the available configuration hooks. They do not hook into any particular framework, instead they run in context of the entire application.
+
+| Hook | Use Case |
+| ---------------------- | ------------------------------------------------------------------------------------- |
+| `before_configuration` | First configurable block to run. Called before any initializers are run. |
+| `before_initialize` | Second configurable block to run. Called before frameworks initialize. |
+| `before_eager_load` | Third configurable block to run. Does not run if `config.cache_classes` set to false. |
+| `after_initialize` | Last configurable block to run. Called after frameworks initialize. |
+
+### Example
+
+`config.before_configuration { puts 'I am called before any initializers' }`
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 8ad76ad01e..0508b0fb38 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -531,7 +531,7 @@ To leverage time zone support in Rails, you have to ask your users what time zon
<%= time_zone_select(:person, :time_zone) %>
```
-There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the API documentation to learn about the possible arguments for these two methods.
+There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-time_zone_options_for_select) to learn about the possible arguments for these two methods.
Rails _used_ to have a `country_select` helper for choosing countries, but this has been extracted to the [country_select plugin](https://github.com/stefanpenner/country_select). When using this, be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from Rails).
diff --git a/guides/source/generators.md b/guides/source/generators.md
index d0b6cef3fd..a554e08204 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -426,7 +426,7 @@ Fallbacks allow your generators to have a single responsibility, increasing code
Application Templates
---------------------
-Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred as a "template". This is a brief overview of the Templates API. For detailed documentation see the [Rails Application Templates guide](rails_application_templates.html).
+Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred to as a "template". This is a brief overview of the Templates API. For detailed documentation see the [Rails Application Templates guide](rails_application_templates.html).
```ruby
gem "rspec-rails", group: "test"
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 57b8472462..18331bb73b 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -46,7 +46,7 @@ development with Rails.
What is Rails?
--------------
-Rails is a web application development framework written in the Ruby language.
+Rails is a web application development framework written in the Ruby programming language.
It is designed to make programming web applications easier by making assumptions
about what every developer needs to get started. It allows you to write less
code while accomplishing more than many other languages and frameworks.
@@ -127,7 +127,7 @@ run the following:
$ rails --version
```
-If it says something like "Rails 5.0.0", you are ready to continue.
+If it says something like "Rails 5.1.0", you are ready to continue.
### Creating the Blog Application
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index ed8cf8a344..6c8706bc13 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -707,6 +707,7 @@ The `:count` interpolation variable has a special role in that it both is interp
```ruby
I18n.backend.store_translations :en, inbox: {
+ zero: 'no messages', # optional
one: 'one message',
other: '%{count} messages'
}
@@ -715,15 +716,20 @@ I18n.translate :inbox, count: 2
I18n.translate :inbox, count: 1
# => 'one message'
+
+I18n.translate :inbox, count: 0
+# => 'no messages'
```
The algorithm for pluralizations in `:en` is as simple as:
```ruby
-entry[count == 1 ? 0 : 1]
+lookup_key = :zero if count == 0 && entry.has_key?(:zero)
+lookup_key ||= count == 1 ? :one : :other
+entry[lookup_key]
```
-I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero).
+The translation denoted as `:one` is regarded as singular, and the `:other` is used as plural. If the count is zero, and a `:zero` entry is present, then it will be used instead of `:other`.
If the lookup for the key does not return a Hash suitable for pluralization, an `I18n::InvalidPluralizationData` exception is raised.
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 7ced3eab1c..1d6a4edb5b 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:** `5.0.Z`, `4.2.Z`.
+**Currently included series:** `5.1.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:** `5.0.Z`, `4.2.Z`.
+**Currently included series:** `5.1.Z`, `5.0.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:** `5.0.Z`, `4.2.Z`.
+**Currently included series:** `5.1.Z`, `5.0.Z`, `4.2.Z`.
Unsupported Release Series
--------------------------
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 340933c7ee..f25b185fb5 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -20,9 +20,9 @@ Introduction to Rack
Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
-* [Rack API Documentation](http://rack.github.io/)
-
-Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the [Resources](#resources) section below.
+Explaining how Rack works is not really in the scope of this guide. In case you
+are not familiar with Rack's basics, you should check out the [Resources](#resources)
+section below.
Rails on Rack
-------------
@@ -74,7 +74,7 @@ And start the server:
$ rackup config.ru
```
-To find out more about different `rackup` options:
+To find out more about different `rackup` options, you can run:
```bash
$ rackup --help
@@ -89,7 +89,8 @@ Action Dispatcher Middleware Stack
Many of Action Dispatcher's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application.
-NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements.
+NOTE: `ActionDispatch::MiddlewareStack` is Rails' equivalent of `Rack::Builder`,
+but is built for better flexibility and more features to meet Rails' requirements.
### Inspecting Middleware Stack
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 86492a9332..f7dbbc510e 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -142,16 +142,17 @@ 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 but you must also specify the `controller:` to use:
+Passing a `String` to `to:` will expect a `controller#action` format. When using a `Symbol`, the `to:` option should be replaced with `action:`. When using a `String` without a `#`, the `to:` option should be replaced with `controller:`:
```ruby
-get 'profile', to: :show, controller: 'users'
+get 'profile', action: :show, controller: 'users'
```
This resourceful route:
```ruby
resource :geocoder
+resolve('Geocoder') { [:geocoder] }
```
creates six different routes in your application, all mapping to the `Geocoders` controller:
@@ -175,14 +176,6 @@ A singular resourceful route generates these helpers:
As with plural resources, the same helpers ending in `_url` will also include the host, port and path prefix.
-WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) prevents `form_for` from working automatically with singular resources. As a workaround, specify the URL for the form directly, like so:
-
-```ruby
-form_for @geocoder, url: geocoder_path do |f|
-
-# snippet for brevity
-```
-
### Controller Namespaces and Routing
You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an `Admin::` namespace. You would place these controllers under the `app/controllers/admin` directory, and you can group them together in your router:
@@ -545,7 +538,7 @@ TIP: If you find yourself adding many extra actions to a resourceful route, it's
Non-Resourceful Routes
----------------------
-In addition to resource routing, Rails has powerful support for routing arbitrary URLs to actions. Here, you don't get groups of routes automatically generated by resourceful routing. Instead, you set up each route within your application separately.
+In addition to resource routing, Rails has powerful support for routing arbitrary URLs to actions. Here, you don't get groups of routes automatically generated by resourceful routing. Instead, you set up each route separately within your application.
While you should usually use resourceful routing, there are still many places where the simpler routing is more appropriate. There's no need to try to shoehorn every last piece of your application into a resourceful framework if that's not a good fit.
diff --git a/guides/source/security.md b/guides/source/security.md
index a81a782cf2..c305350243 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -212,7 +212,7 @@ CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less th
NOTE: _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._
-The HTTP protocol basically provides two main types of requests - GET and POST (and more, but they are not supported by most browsers). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST:
+The HTTP protocol basically provides two main types of requests - GET and POST (DELETE, PUT, and PATCH should be used like POST). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST:
**Use GET if:**
@@ -224,7 +224,7 @@ The HTTP protocol basically provides two main types of requests - GET and POST (
* The interaction _changes the state_ of the resource in a way that the user would perceive (e.g., a subscription to a service), or
* The user is _held accountable for the results_ of the interaction.
-If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Most of today's web browsers, however, do not support them - only GET and POST. Rails uses a hidden `_method` field to handle this barrier.
+If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Some legacy web browsers, however, do not support them - only GET and POST. Rails uses a hidden `_method` field to handle these cases.
_POST requests can be sent automatically, too_. In this example, the link www.harmless.com is shown as the destination in the browser's status bar. But it has actually dynamically created a new form that sends a POST request.
@@ -257,13 +257,12 @@ protect_from_forgery with: :exception
This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown.
-NOTE: By default, Rails includes jQuery and an [unobtrusive scripting adapter for
-jQuery](https://github.com/rails/jquery-ujs), which adds a header called
-`X-CSRF-Token` on every non-GET Ajax call made by jQuery with the security token.
-Without this header, non-GET Ajax requests won't be accepted by Rails. When using
-another library to make Ajax calls, it is necessary to add the security token as
-a default header for Ajax calls in your library. To get the token, have a look at
-`<meta name='csrf-token' content='THE-TOKEN'>` tag printed by
+NOTE: By default, Rails includes an [unobtrusive scripting adapter](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts),
+which adds a header called `X-CSRF-Token` with the security token on every non-GET
+Ajax call. Without this header, non-GET Ajax requests won't be accepted by Rails.
+When using another library to make Ajax calls, it is necessary to add the security
+token as a default header for Ajax calls in your library. To get the token, have
+a look at `<meta name='csrf-token' content='THE-TOKEN'>` tag printed by
`<%= csrf_meta_tags %>` in your application view.
It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
@@ -615,7 +614,7 @@ The two dashes start a comment ignoring everything after it. So the query return
Usually a web application includes access control. The user enters their login credentials and the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user.
```ruby
-User.first("login = '#{params[:name]}' AND password = '#{params[:password]}'")
+User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'")
```
If an attacker enters ' OR '1'='1 as the name, and ' OR '2'>'1 as the password, the resulting SQL query will be:
@@ -762,7 +761,7 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title))
This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags.
-As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &amp;, &quot;, &lt;, and &gt; by their uninterpreted representations in HTML (`&amp;`, `&quot;`, `&lt;`, and `&gt;`).
+As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &amp;, &quot;, &lt;, and &gt; by their uninterpreted representations in HTML (`&amp;`, `&quot;`, `&lt;`, and `&gt;`).
##### Obfuscation and Encoding Injection
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 652030a733..7741834153 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -123,7 +123,7 @@ def test_the_truth
end
```
-However only the `test` macro allows a more readable test name. You can still use regular method definitions though.
+Although you can still use regular method definitions, using the `test` macro allows for a more readable test name.
NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. This may require use of `define_method` and `send` calls to function properly, but formally there's little restriction on the name.
@@ -610,9 +610,9 @@ For creating Rails system tests, you use the `test/system` directory in your
application. Rails provides a generator to create a system test skeleton for you.
```bash
-$ bin/rails generate system_test users_create_test
+$ bin/rails generate system_test users_create
invoke test_unit
- create test/system/users_create_test.rb
+ create test/system/users_creates_test.rb
```
Here's what a freshly-generated system test looks like:
@@ -620,10 +620,12 @@ Here's what a freshly-generated system test looks like:
```ruby
require "application_system_test_case"
-class UsersCreateTest < ApplicationSystemTestCase
- visit users_url
-
- assert_selector "h1", text: "Users"
+class UsersCreatesTest < ApplicationSystemTestCase
+ # test "visiting the index" do
+ # visit users_creates_url
+ #
+ # assert_selector "h1", text: "UsersCreate"
+ # end
end
```
@@ -642,7 +644,7 @@ system tests should live.
If you want to change the default settings you can simply change what the system
tests are "driven by". Say you want to change the driver from Selenium to
-Poltergeist. First add the Poltergeist gem to your Gemfile. Then in your
+Poltergeist. First add the `poltergeist` gem to your Gemfile. Then in your
`application_system_test_case.rb` file do the following:
```ruby
@@ -656,8 +658,8 @@ end
The driver name is a required argument for `driven_by`. The optional arguments
that can be passed to `driven_by` are `:using` for the browser (this will only
-be used for non-headless drivers like Selenium), `:on` for the port Puma should
-use, and `:screen_size` to change the size of the screen for screenshots.
+be used for non-headless drivers like Selenium), and `:screen_size` to change
+the size of the screen for screenshots.
```ruby
require "test_helper"
@@ -720,7 +722,7 @@ class ArticlesTest < ApplicationSystemTestCase
end
```
-The test should see that there is an h1 on the articles index and pass.
+The test should see that there is an `h1` on the articles index page and pass.
Run the system tests.
@@ -728,6 +730,9 @@ Run the system tests.
bin/rails test:system
```
+NOTE: By default, running `bin/rails test` won't run your system tests.
+Make sure to run `bin/rails test:system` to actually run them.
+
#### Creating articles system test
Now let's test the flow for creating a new article in our blog.
@@ -758,7 +763,7 @@ text. Once the fields are filled in, "Create Article" is clicked on which will
send a POST request to create the new article in the database.
We will be redirected back to the the articles index page and there we assert
-that the text from the article title is on the articles index page.
+that the text from the new article's title is on the articles index page.
#### Taking it further
@@ -1435,6 +1440,10 @@ variable. We then ensure that it was sent (the first assert), then, in the
second batch of assertions, we ensure that the email does indeed contain what we
expect. The helper `read_fixture` is used to read in the content from this file.
+NOTE: `email.body.to_s` is present when there's only one (HTML or text) part present.
+If the mailer provides both, you can test your fixture against specific parts
+with `email.text_part.body.to_s` or `email.html_part.body.to_s`.
+
Here's the content of the `invite` fixture:
```
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 6005298127..3afc0e5309 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -80,7 +80,7 @@ It is only soft-deprecated, which means that your code will not break at the
moment and no deprecation warning will be displayed but this constant will be
removed in the future.
-Also, if you have pretty old YAML documents containg dumps of such objects,
+Also, if you have pretty old YAML documents containing dumps of such objects,
you may need to load and dump them again to make sure that they reference
the right constant and that loading them won't break in the future.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index c1dfcab6f3..2a6a87c232 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -149,7 +149,7 @@ Because of Unobtrusive JavaScript, the Rails "Ajax helpers" are actually in two
parts: the JavaScript half and the Ruby half.
Unless you have disabled the Asset Pipeline,
-[rails.js](https://github.com/rails/jquery-ujs/blob/master/src/rails.js)
+[rails-ujs](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts/rails-ujs.coffee)
provides the JavaScript half, and the regular Ruby view helpers add appropriate
tags to your DOM.
@@ -338,7 +338,7 @@ this:
end
```
-Notice the format.js in the `respond_to` block; that allows the controller to
+Notice the `format.js` in the `respond_to` block: that allows the controller to
respond to your Ajax request. You then have a corresponding
`app/views/users/create.js.erb` view file that generates the actual JavaScript
code that will be sent and executed on the client side.
@@ -355,7 +355,7 @@ which uses Ajax to speed up page rendering in most applications.
### How Turbolinks Works
-Turbolinks attaches a click handler to all `<a>` on the page. If your browser
+Turbolinks attaches a click handler to all `<a>` tags on the page. If your browser
supports
[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState%28%29_method),
Turbolinks will make an Ajax request for the page, parse the response, and
@@ -385,7 +385,7 @@ $(document).ready ->
```
However, because Turbolinks overrides the normal page loading process, the
-event that this relies on will not be fired. If you have code that looks like
+event that this relies upon will not be fired. If you have code that looks like
this, you must change your code to do this instead:
```coffeescript
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 3afadc8cba..6032d2e1a1 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,158 +1 @@
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Fix running multiple tests in one `rake` command
-
- e.g. `bin/rake test:models test:controllers`
-
- *Dominic Cleal*
-
-* Add option to configure Ruby's warning behaviour to test runner.
-
- *Yuji Yaginuma*
-
-* Initialize git repo when generating new app, if option `--skip-git`
- is not provided.
-
- *Dino Maric*
-
-* Install Byebug gem as default in Windows (mingw and x64_mingw) platform.
-
- *Junichi Ito*
-
-* Make every Rails command work within engines.
-
- *Sean Collins*, *Yuji Yaginuma*
-
-* Don't generate HTML/ERB templates for scaffold controller with `--api` flag.
-
- Fixes #27591.
-
- *Prathamesh Sonpatki*
-
-* Make `Rails.env` fall back to `development` when `RAILS_ENV` and `RACK_ENV` is an empty string.
-
- *Daniel Deng*
-
-* Remove deprecated `CONTROLLER` environment variable for `routes` task.
-
- *Rafael Mendonça França*
-
-* Remove deprecated tasks: `rails:update`, `rails:template`, `rails:template:copy`,
- `rails:update:configs` and `rails:update:bin`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated file `rails/rack/debugger`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `config.serve_static_files`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `config.static_cache_control`.
-
- *Rafael Mendonça França*
-
-* The `log:clear` task clear all environments log files by default.
-
- *Yuji Yaginuma*
-
-* Add Webpack support in new apps via the --webpack option, which will delegate to the rails/webpacker gem.
-
- To generate a new app that has Webpack dependencies configured and binstubs for webpack and webpack-watcher:
-
- `rails new myapp --webpack`
-
- To generate a new app that has Webpack + React configured and an example intalled:
-
- `rails new myapp --webpack=react`
-
- *DHH*
-
-* Add Yarn support in new apps with a yarn binstub and package.json. Skippable via --skip-yarn option.
-
- *Liceth Ovalles*, *Guillermo Iguaran*, *DHH*
-
-* Removed jquery-rails from default stack, instead rails-ujs that is shipped
- with Action View is included as default UJS adapter.
-
- *Guillermo Iguaran*
-
-* The config file `secrets.yml` is now loaded in with all keys as symbols.
- This allows secrets files to contain more complex information without all
- child keys being strings while parent keys are symbols.
-
- *Isaac Sloan*
-
-* Add `:skip_sprockets` to `Rails::PluginBuilder::PASSTHROUGH_OPTIONS`
-
- *Tsukuru Tanimichi*
-
-* Allow the use of listen's 3.1.x branch
-
- *Esteban Santana Santana*
-
-* Run `Minitest.after_run` hooks when running `rails test`.
-
- *Michael Grosser*
-
-* Run `before_configuration` callbacks as soon as application constant
- inherits from `Rails::Application`.
-
- Fixes #19880.
-
- *Yuji Yaginuma*
-
-* A generated app should not include Uglifier with `--skip-javascript` option.
-
- *Ben Pickles*
-
-* Set session store to cookie store internally and remove the initializer from
- the generated app.
-
- *Prathamesh Sonpatki*
-
-* Set the server host using the `HOST` environment variable.
-
- *mahnunchik*
-
-* Add public API to register new folders for `rake notes`:
-
- config.annotations.register_directories('spec', 'features')
-
- *John Meehan*
-
-* Display name of the class defining the initializer along with the initializer
- name in the output of `rails initializers`.
-
- Before:
- disable_dependency_loading
-
- After:
- DemoApp::Application.disable_dependency_loading
-
- *ta1kt0me*
-
-* Do not run `bundle install` when generating a new plugin.
-
- Since bundler 1.12.0, the gemspec is validated so the `bundle install`
- command will fail just after the gem is created causing confusion to the
- users. This change was a bug fix to correctly validate gemspecs.
-
- *Rafael Mendonça França*
-
-* Default `config.assets.quiet = true` in the development environment. Suppress
- logging of assets requests by default.
-
- *Kevin McPhillips*
-
-* Ensure `/rails/info` routes match in development for apps with a catch-all globbing route.
-
- *Nicholas Firth-McCoy*
-
-* Added a shared section to `config/secrets.yml` that will be loaded for all environments.
-
- *DHH*
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/railties/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc
index ef9bbf3d7e..654c7bae57 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://www.railstutorial.org/book].
+* {Ruby on \Rails Tutorial}[https://www.railstutorial.org/book].
* {Ruby on \Rails Guides}[http://guides.rubyonrails.org].
* {The API Documentation}[http://api.rubyonrails.org].
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 00add5829d..6d8bf28943 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -87,8 +87,8 @@ module Rails
# groups assets: [:development, :test]
#
# # Returns
- # # => [:default, :development, :assets] for Rails.env == "development"
- # # => [:default, :production] for Rails.env == "production"
+ # # => [:default, "development", :assets] for Rails.env == "development"
+ # # => [:default, "production"] for Rails.env == "production"
def groups(*groups)
hash = groups.extract_options!
env = Rails.env
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 89f7b5991f..f8a923141d 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -386,7 +386,9 @@ module Rails
def secrets
@secrets ||= begin
secrets = ActiveSupport::OrderedOptions.new
- secrets.merge! Rails::Secrets.parse(config.paths["config/secrets"].existent, env: Rails.env)
+ files = config.paths["config/secrets"].existent
+ files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets
+ secrets.merge! Rails::Secrets.parse(files, env: Rails.env)
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
secrets.secret_key_base ||= config.secret_key_base
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 4223c38146..dc0491035d 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -81,7 +81,6 @@ INFO
initializer :set_secrets_root, group: :all do
Rails::Secrets.root = root
- Rails::Secrets.read_encrypted_secrets = config.read_encrypted_secrets
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index b0592151b7..7c49fabba5 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -14,9 +14,8 @@ module Rails
:ssl_options, :public_file_server,
:session_options, :time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading,
- :read_encrypted_secrets
+ :read_encrypted_secrets, :log_level
- attr_writer :log_level
attr_reader :encoding, :api_only
def initialize(*)
@@ -35,7 +34,7 @@ module Rails
@session_store = nil
@time_zone = "UTC"
@beginning_of_week = :monday
- @log_level = nil
+ @log_level = :debug
@generators = app_generators
@cache_store = [ :file_store, "#{root}/tmp/cache/" ]
@railties_order = [:all]
@@ -55,6 +54,37 @@ module Rails
@read_encrypted_secrets = false
end
+ def load_defaults(target_version)
+ case target_version.to_s
+ when "5.0"
+ if respond_to?(:action_controller)
+ action_controller.per_form_csrf_tokens = true
+ action_controller.forgery_protection_origin_check = true
+ end
+
+ ActiveSupport.to_time_preserves_timezone = true
+
+ if respond_to?(:active_record)
+ active_record.belongs_to_required_by_default = true
+ end
+
+ self.ssl_options = { hsts: { subdomains: true } }
+
+ when "5.1"
+ load_defaults "5.0"
+
+ if respond_to?(:assets)
+ assets.unknown_asset_fallback = false
+ end
+
+ when "5.2"
+ load_defaults "5.1"
+
+ else
+ raise "Unknown version #{target_version.to_s.inspect}"
+ end
+ end
+
def encoding=(value)
@encoding = value
silence_warnings do
@@ -121,10 +151,6 @@ module Rails
raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace
end
- def log_level
- @log_level ||= (Rails.env.production? ? :info : :debug)
- end
-
def colorize_logging
ActiveSupport::LogSubscriber.colorize_logging
end
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 5c833e12ba..3bd18ebfb5 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -16,7 +16,7 @@ module Rails
add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests
add_gem_filters
- add_silencer { |line| line !~ APP_DIRS_PATTERN }
+ add_silencer { |line| !APP_DIRS_PATTERN.match?(line) }
end
private
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 9c4bd16aad..70dce268f1 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -7,7 +7,8 @@ class CodeStatistics #:nodoc:
"Model tests",
"Mailer tests",
"Job tests",
- "Integration tests"]
+ "Integration tests",
+ "System tests"]
HEADERS = { lines: " Lines", code_lines: " LOC", classes: "Classes", methods: "Methods" }
diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb
index db20c71861..4f074df473 100644
--- a/railties/lib/rails/command/base.rb
+++ b/railties/lib/rails/command/base.rb
@@ -64,7 +64,7 @@ module Rails
end
def printing_commands
- namespace.sub(/^rails:/, "")
+ namespaced_commands
end
def executable
@@ -135,6 +135,12 @@ module Rails
def command_root_namespace
(namespace.split(":") - %w( rails )).first
end
+
+ def namespaced_commands
+ commands.keys.map do |key|
+ key == command_root_namespace ? key : "#{command_root_namespace}:#{key}"
+ end
+ end
end
def help
diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
index 588fb06b15..5bfbe58d97 100644
--- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
+++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
@@ -140,7 +140,7 @@ module Rails
class_option :mode, enum: %w( html list line column ), type: :string,
desc: "Automatically put the sqlite3 database in the specified mode (html, list, line, column)."
- class_option :header, type: :string
+ class_option :header, type: :boolean
class_option :environment, aliases: "-e", type: :string,
desc: "Specifies the environment to run this console under (test/development/production)."
diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb
index 5b552b2070..281732a936 100644
--- a/railties/lib/rails/commands/destroy/destroy_command.rb
+++ b/railties/lib/rails/commands/destroy/destroy_command.rb
@@ -3,8 +3,13 @@ require "rails/generators"
module Rails
module Command
class DestroyCommand < Base # :nodoc:
- def help
- Rails::Generators.help self.class.command_name
+ no_commands do
+ def help
+ require_application_and_environment!
+ load_generators
+
+ Rails::Generators.help self.class.command_name
+ end
end
def perform(*)
@@ -12,9 +17,9 @@ module Rails
return help unless generator
require_application_and_environment!
- Rails.application.load_generators
+ load_generators
- Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails.root
+ Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails::Command.root
end
end
end
diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb
index 2718b453a8..9dd7ad1012 100644
--- a/railties/lib/rails/commands/generate/generate_command.rb
+++ b/railties/lib/rails/commands/generate/generate_command.rb
@@ -3,11 +3,13 @@ require "rails/generators"
module Rails
module Command
class GenerateCommand < Base # :nodoc:
- def help
- require_application_and_environment!
- load_generators
+ no_commands do
+ def help
+ require_application_and_environment!
+ load_generators
- Rails::Generators.help self.class.command_name
+ Rails::Generators.help self.class.command_name
+ end
end
def perform(*)
diff --git a/railties/lib/rails/commands/new/new_command.rb b/railties/lib/rails/commands/new/new_command.rb
index 74d1fa5021..207dd5d995 100644
--- a/railties/lib/rails/commands/new/new_command.rb
+++ b/railties/lib/rails/commands/new/new_command.rb
@@ -1,8 +1,10 @@
module Rails
module Command
class NewCommand < Base # :nodoc:
- def help
- Rails::Command.invoke :application, [ "--help" ]
+ no_commands do
+ def help
+ Rails::Command.invoke :application, [ "--help" ]
+ end
end
def perform(*)
diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb
index 4989a7837d..6864a9726b 100644
--- a/railties/lib/rails/commands/runner/runner_command.rb
+++ b/railties/lib/rails/commands/runner/runner_command.rb
@@ -5,16 +5,18 @@ module Rails
default: Rails::Command.environment.dup,
desc: "The environment for the runner to operate under (test/development/production)"
- def help
- super
- puts self.class.desc
+ no_commands do
+ def help
+ super
+ puts self.class.desc
+ end
end
def self.banner(*)
"#{super} [<'Some.ruby(code)'> | <filename.rb>]"
end
- def perform(code_or_file = nil, *file_argv)
+ def perform(code_or_file = nil, *command_argv)
unless code_or_file
help
exit 1
@@ -25,9 +27,10 @@ module Rails
require_application_and_environment!
Rails.application.load_runner
+ ARGV.replace(command_argv)
+
if File.exist?(code_or_file)
$0 = code_or_file
- ARGV.replace(file_argv)
Kernel.load code_or_file
else
begin
diff --git a/railties/lib/rails/commands/secrets/USAGE b/railties/lib/rails/commands/secrets/USAGE
index 4b7deb4e2a..96e322fe91 100644
--- a/railties/lib/rails/commands/secrets/USAGE
+++ b/railties/lib/rails/commands/secrets/USAGE
@@ -40,6 +40,14 @@ be encrypted.
A `shared:` top level key is also supported such that any keys there is merged
into the other environments.
+Additionally, Rails won't read encrypted secrets out of the box even if you have
+the key. Add this:
+
+ config.read_encrypted_secrets = true
+
+to the environment you'd like to read encrypted secrets. `bin/rails secrets:setup`
+inserts this into the production environment by default.
+
=== Editing Secrets
After `bin/rails secrets:setup`, run `bin/rails secrets:edit`.
diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb
index 3ba8c0c85b..03a640bd65 100644
--- a/railties/lib/rails/commands/secrets/secrets_command.rb
+++ b/railties/lib/rails/commands/secrets/secrets_command.rb
@@ -4,10 +4,12 @@ require "rails/secrets"
module Rails
module Command
class SecretsCommand < Rails::Command::Base # :nodoc:
- def help
- say "Usage:\n #{self.class.banner}"
- say ""
- say self.class.desc
+ no_commands do
+ def help
+ say "Usage:\n #{self.class.banner}"
+ say ""
+ say self.class.desc
+ end
end
def setup
@@ -18,16 +20,27 @@ module Rails
end
def edit
+ if ENV["EDITOR"].to_s.empty?
+ say "No $EDITOR to open decrypted secrets in. Assign one like this:"
+ say ""
+ say %(EDITOR="mate --wait" bin/rails secrets:edit)
+ say ""
+ say "For editors that fork and exit immediately, it's important to pass a wait flag,"
+ say "otherwise the secrets will be saved immediately with no chance to edit."
+
+ return
+ end
+
require_application_and_environment!
Rails::Secrets.read_for_editing do |tmp_path|
- puts "Waiting for secrets file to be saved. Abort with Ctrl-C."
+ say "Waiting for secrets file to be saved. Abort with Ctrl-C."
system("\$EDITOR #{tmp_path}")
end
- puts "New secrets encrypted and saved."
+ say "New secrets encrypted and saved."
rescue Interrupt
- puts "Aborted changing encrypted secrets: nothing saved."
+ say "Aborted changing encrypted secrets: nothing saved."
rescue Rails::Secrets::MissingKeyError => error
say error.message
end
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index cde8b575b5..278fe63c51 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -99,8 +99,9 @@ module Rails
class_option :port, aliases: "-p", type: :numeric,
desc: "Runs Rails on the specified port.", banner: :port, default: 3000
- class_option :binding, aliases: "-b", type: :string, default: "localhost",
- desc: "Binds Rails to the specified IP.", banner: :IP
+ class_option :binding, aliases: "-b", type: :string,
+ desc: "Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments'.",
+ banner: :IP
class_option :config, aliases: "-c", type: :string, default: "config.ru",
desc: "Uses a custom rackup configuration.", banner: :file
class_option :daemon, aliases: "-d", type: :boolean, default: false,
@@ -176,7 +177,7 @@ module Rails
user_supplied_options << name
end
end
- user_supplied_options << :Host if ENV["Host"]
+ user_supplied_options << :Host if ENV["HOST"]
user_supplied_options << :Port if ENV["PORT"]
user_supplied_options.uniq
end
@@ -187,7 +188,12 @@ module Rails
end
def host
- ENV.fetch("HOST", options[:binding])
+ if options[:binding]
+ options[:binding]
+ else
+ default_host = environment == "development" ? "localhost" : "0.0.0.0"
+ ENV.fetch("HOST", default_host)
+ end
end
def environment
diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb
index 629fb5b425..65e16900ba 100644
--- a/railties/lib/rails/commands/test/test_command.rb
+++ b/railties/lib/rails/commands/test/test_command.rb
@@ -4,8 +4,10 @@ require "rails/test_unit/minitest_plugin"
module Rails
module Command
class TestCommand < Base # :nodoc:
- def help
- perform # Hand over help printing to minitest.
+ no_commands do
+ def help
+ perform # Hand over help printing to minitest.
+ end
end
def perform(*)
diff --git a/railties/lib/rails/engine/updater.rb b/railties/lib/rails/engine/updater.rb
new file mode 100644
index 0000000000..2ecf994a5c
--- /dev/null
+++ b/railties/lib/rails/engine/updater.rb
@@ -0,0 +1,19 @@
+require "rails/generators"
+require "rails/generators/rails/plugin/plugin_generator"
+
+module Rails
+ class Engine
+ class Updater
+ class << self
+ def generator
+ @generator ||= Rails::Generators::PluginGenerator.new ["plugin"],
+ { engine: true }, destination_root: ENGINE_ROOT
+ end
+
+ def run(action)
+ generator.send(action)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 3174ffb0dc..7bacf2e0ba 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -6,9 +6,9 @@ module Rails
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 3f1bf6a5bb..8ec805370b 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -63,7 +63,7 @@ module Rails
stylesheet_engine: :css,
scaffold_stylesheet: true,
system_tests: nil,
- test_framework: false,
+ test_framework: nil,
template_engine: :erb
}
}
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 04f6341471..9109be5e04 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -13,6 +13,7 @@ module Rails
DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver )
JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc )
DATABASES.concat(JDBC_DATABASES)
+ WEBPACKS = %w( react vue angular )
attr_accessor :rails_template
add_shebang_option!
@@ -30,11 +31,8 @@ module Rails
class_option :database, type: :string, aliases: "-d", default: "sqlite3",
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :javascript, type: :string, aliases: "-j",
- desc: "Preconfigure for selected JavaScript library"
-
class_option :webpack, type: :string, default: nil,
- desc: "Preconfigure for app-like JavaScript with Webpack"
+ desc: "Preconfigure for app-like JavaScript with Webpack (options: #{WEBPACKS.join('/')})"
class_option :skip_yarn, type: :boolean, default: false,
desc: "Don't use Yarn for managing JavaScript dependencies"
@@ -246,6 +244,7 @@ module Rails
def rails_gemfile_entry
dev_edge_common = [
+ GemfileEntry.github("arel", "rails/arel"),
]
if options.dev?
[
@@ -280,7 +279,7 @@ module Rails
case options[:database]
when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]]
when "postgresql" then ["pg", ["~> 0.18"]]
- when "oracle" then ["ruby-oci8", nil]
+ when "oracle" then ["activerecord-oracle_enhanced-adapter", nil]
when "frontbase" then ["ruby-frontbase", nil]
when "sqlserver" then ["activerecord-sqlserver-adapter", nil]
when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil]
@@ -296,7 +295,6 @@ module Rails
case options[:database]
when "postgresql" then options[:database].replace "jdbcpostgresql"
when "mysql" then options[:database].replace "jdbcmysql"
- when "oracle" then options[:database].replace "jdbc"
when "sqlite3" then options[:database].replace "jdbcsqlite3"
end
end
@@ -322,7 +320,7 @@ module Rails
return [] unless options[:webpack]
comment = "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker"
- GemfileEntry.github "webpacker", "rails/webpacker", nil, comment
+ GemfileEntry.new "webpacker", nil, comment
end
def jbuilder_gemfile_entry
@@ -341,11 +339,6 @@ module Rails
gems = [javascript_runtime_gemfile_entry]
gems << coffee_gemfile_entry unless options[:skip_coffee]
- if options[:javascript]
- gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
- "Use #{options[:javascript]} as the JavaScript library")
- end
-
unless options[:skip_turbolinks]
gems << GemfileEntry.version("turbolinks", "~> 5",
"Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks")
@@ -412,6 +405,10 @@ module Rails
!options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
end
+ def depends_on_system_test?
+ !(options[:skip_system_test] || options[:skip_test] || options[:api])
+ end
+
def depend_on_listen?
!options[:skip_listen] && os_supports_listen_out_of_the_box?
end
diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb
index d5e326d6ee..97d9ab29d4 100644
--- a/railties/lib/rails/generators/erb.rb
+++ b/railties/lib/rails/generators/erb.rb
@@ -17,8 +17,8 @@ module Erb # :nodoc:
:erb
end
- def filename_with_extensions(name, format = self.format)
- [name, format, handler].compact.join(".")
+ def filename_with_extensions(name, file_format = format)
+ [name, file_format, handler].compact.join(".")
end
end
end
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 519b6c8603..4f2e84f924 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -1,4 +1,4 @@
-<%%= form_for(<%= singular_table_name %>) do |f| %>
+<%%= form_with(model: <%= singular_table_name %>, local: true) do |form| %>
<%% if <%= singular_table_name %>.errors.any? %>
<div id="error_explanation">
<h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
@@ -14,21 +14,21 @@
<% attributes.each do |attribute| -%>
<div class="field">
<% if attribute.password_digest? -%>
- <%%= f.label :password %>
- <%%= f.password_field :password %>
+ <%%= form.label :password %>
+ <%%= form.password_field :password, id: :<%= field_id(:password) %> %>
</div>
<div class="field">
- <%%= f.label :password_confirmation %>
- <%%= f.password_field :password_confirmation %>
+ <%%= form.label :password_confirmation %>
+ <%%= form.password_field :password_confirmation, id: :<%= field_id(:password_confirmation) %> %>
<% else -%>
- <%%= f.label :<%= attribute.column_name %> %>
- <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %>
+ <%%= form.label :<%= attribute.column_name %> %>
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> %>
<% end -%>
</div>
<% end -%>
<div class="actions">
- <%%= f.submit %>
+ <%%= form.submit %>
</div>
<%% end %>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 02557b098a..aef66adb64 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -149,6 +149,10 @@ module Rails
"new_#{singular_table_name}_url"
end
+ def field_id(attribute_name)
+ [singular_table_name, attribute_name].join("_")
+ end
+
def singular_table_name # :doc:
@singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name)
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 86326d98ed..f4717bb35b 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -32,6 +32,14 @@ module Rails
# This allows you to override entire operations, like the creation of the
# Gemfile, README, or JavaScript files, without needing to know exactly
# what those operations do so you can create another template action.
+ #
+ # class CustomAppBuilder < Rails::AppBuilder
+ # def test
+ # @generator.gem "rspec-rails", group: [:development, :test]
+ # run "bundle install"
+ # generate "rspec:install"
+ # end
+ # end
class AppBuilder
def rakefile
template "Rakefile"
@@ -76,6 +84,16 @@ module Rails
chmod "bin", 0755 & ~File.umask, verbose: false
end
+ def bin_when_updating
+ bin_yarn_exist = File.exist?("bin/yarn")
+
+ bin
+
+ if options[:api] && !bin_yarn_exist
+ remove_file "bin/yarn"
+ end
+ end
+
def config
empty_directory "config"
@@ -98,11 +116,11 @@ module Rails
cookie_serializer_config_exist = File.exist?("config/initializers/cookies_serializer.rb")
action_cable_config_exist = File.exist?("config/cable.yml")
rack_cors_config_exist = File.exist?("config/initializers/cors.rb")
+ assets_config_exist = File.exist?("config/initializers/assets.rb")
+ new_framework_defaults_5_1_exist = File.exist?("config/initializers/new_framework_defaults_5_1.rb")
config
- gsub_file "config/environments/development.rb", /^(\s+)config\.file_watcher/, '\1# config.file_watcher'
-
unless cookie_serializer_config_exist
gsub_file "config/initializers/cookies_serializer.rb", /json(?!,)/, "marshal"
end
@@ -114,6 +132,22 @@ module Rails
unless rack_cors_config_exist
remove_file "config/initializers/cors.rb"
end
+
+ if options[:api]
+ unless cookie_serializer_config_exist
+ remove_file "config/initializers/cookies_serializer.rb"
+ end
+
+ unless assets_config_exist
+ remove_file "config/initializers/assets.rb"
+ end
+
+ # Sprockets owns the only new default for 5.1:
+ # In API-only Applications, we don't want the file.
+ unless new_framework_defaults_5_1_exist
+ remove_file "config/initializers/new_framework_defaults_5_1.rb"
+ end
+ end
end
def database_yml
@@ -224,6 +258,11 @@ module Rails
build(:bin)
end
+ def update_bin_files
+ build(:bin_when_updating)
+ end
+ remove_task :update_bin_files
+
def create_config_files
build(:config)
end
@@ -269,7 +308,7 @@ module Rails
end
def create_system_test_files
- build(:system_test) unless options[:skip_system_test] || options[:skip_test] || options[:api]
+ build(:system_test) if depends_on_system_test?
end
def create_tmp_files
@@ -363,6 +402,12 @@ module Rails
end
end
+ def delete_new_framework_defaults
+ unless options[:update]
+ remove_file "config/initializers/new_framework_defaults_5_1.rb"
+ end
+ end
+
def delete_bin_yarn_if_skip_yarn_option
remove_file "bin/yarn" if options[:skip_yarn]
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index b082d70cba..06f0dd6d6d 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -32,9 +32,9 @@ end
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
- <%- unless options.skip_system_test? || options.api? -%>
+ <%- if depends_on_system_test? -%>
# Adds support for Capybara system testing and selenium driver
- gem 'capybara', '~> 2.7.0'
+ gem 'capybara', '~> 2.13.0'
gem 'selenium-webdriver'
<%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index 25870f19c8..4206002a1b 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -1,7 +1,7 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
@@ -11,9 +11,6 @@
// about supported directives.
//
<% unless options[:skip_javascript] -%>
-<% if options[:javascript] -%>
-//= require <%= options[:javascript] %>
-<% end -%>
//= require rails-ujs
<% unless options[:skip_turbolinks] -%>
//= require turbolinks
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index 865300bef9..d05ea0f511 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -2,7 +2,7 @@
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
index c6607dbb2b..52b3de5ee5 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -19,9 +19,9 @@ chdir APP_ROOT do
<% unless options[:skip_yarn] %>
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
-
<% end %>
<% unless options.skip_active_record -%>
+
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# cp 'config/database.yml.sample', 'config/database.yml'
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index d23af018c7..d385b363c6 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -17,6 +17,7 @@ chdir APP_ROOT do
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
<% unless options.skip_active_record -%>
+
puts "\n== Updating database =="
system! 'bin/rails db:migrate'
<% end -%>
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 c0a0bd0a3e..d5d214052f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -22,6 +22,9 @@ Bundler.require(*Rails.groups)
module <%= app_const_base %>
class Application < Rails::Application
+ # Initialize configuration defaults for originally generated Rails version.
+ config.load_defaults <%= Rails::VERSION::STRING.to_f %>
+
# 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.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index d2499ea4fb..6da0601b24 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -1,4 +1,4 @@
-# Oracle/OCI 8i, 9, 10g
+# Oracle/OCI 11g or higher recommended
#
# Requires Ruby/OCI8:
# https://github.com/kubo/ruby-oci8
@@ -17,7 +17,7 @@
# cursor_sharing: similar
#
default: &default
- adapter: oracle
+ adapter: oracle_enhanced
pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= app_name %>
password:
@@ -45,7 +45,9 @@ test:
# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
-# DATABASE_URL="oracle://myuser:mypass@localhost/somedatabase"
+# DATABASE_URL="oracle-enhanced://myuser:mypass@localhost/somedatabase"
+#
+# Note that the adapter name uses a dash instead of an underscore.
#
# You can use this database configuration with:
#
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 511b4a82eb..b75b65c8df 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
@@ -13,6 +13,7 @@ Rails.application.configure do
config.consider_all_requests_local = true
# Enable/disable caching. By default caching is disabled.
+ # Run rails dev:cache to toggle caching.
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
deleted file mode 100644
index bd844f0503..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
+++ /dev/null
@@ -1,37 +0,0 @@
-# Be sure to restart your server when you modify this file.
-#
-# This file contains migration options to ease your Rails 5.0 upgrade.
-#
-<%- if options[:update] -%>
-# Once upgraded flip defaults one by one to migrate to the new default.
-#
-<%- end -%>
-# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-<%- unless options[:api] -%>
-
-# Enable per-form CSRF tokens. Previous versions had false.
-Rails.application.config.action_controller.per_form_csrf_tokens = <%= options[:update] ? false : true %>
-
-# Enable origin-checking CSRF mitigation. Previous versions had false.
-Rails.application.config.action_controller.forgery_protection_origin_check = <%= options[:update] ? false : true %>
-<%- end -%>
-
-# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
-# Previous versions had false.
-ActiveSupport.to_time_preserves_timezone = <%= options[:update] ? false : true %>
-<%- unless options[:skip_active_record] -%>
-
-# Require `belongs_to` associations by default. Previous versions had false.
-Rails.application.config.active_record.belongs_to_required_by_default = <%= options[:update] ? false : true %>
-<%- end -%>
-<%- unless options[:update] -%>
-
-# Configure SSL options to enable HSTS with subdomains. Previous versions had false.
-Rails.application.config.ssl_options = { hsts: { subdomains: true } }
-<%- end -%>
-<%- unless options[:skip_sprockets] -%>
-
-# Unknown asset fallback will return the path passed in when the given
-# asset is not present in the asset pipeline.
-Rails.application.config.assets.unknown_asset_fallback = <%= options[:update] ? true : false %>
-<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt
new file mode 100644
index 0000000000..a0c7f44b60
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt
@@ -0,0 +1,16 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.1 upgrade.
+#
+# Once upgraded flip defaults one by one to migrate to the new default.
+#
+# Read the Guide for Upgrading Ruby on Rails for more info on each option.
+
+# Make `form_with` generate non-remote forms.
+Rails.application.config.action_view.form_with_generates_remote_forms = false
+<%- unless options[:skip_sprockets] -%>
+
+# Unknown asset fallback will return the path passed in when the given
+# asset is not present in the asset pipeline.
+# Rails.application.config.assets.unknown_asset_fallback = false
+<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
index 816efcc5b1..ea9d47396c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
@@ -12,8 +12,8 @@
# Shared secrets are available across all environments.
-shared:
- api_key: 123
+# shared:
+# api_key: a1B2c3D4e5F6
# Environmental secrets are only available for that specific environment.
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 3fcd8607f0..118e44d9d0 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -92,6 +92,7 @@ task default: :test
opts[:api] = options.api?
opts[:skip_listen] = true
opts[:skip_git] = true
+ opts[:skip_turbolinks] = true
invoke Rails::Generators::AppGenerator,
[ File.expand_path(dummy_path, destination_root) ], opts
@@ -300,7 +301,7 @@ task default: :test
end
def engine?
- full? || mountable?
+ full? || mountable? || options[:engine]
end
def full?
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index af3be10a31..6bdb673215 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -205,7 +205,14 @@ module Rails
# Returns all expanded paths but only if they exist in the filesystem.
def existent
- expanded.select { |f| File.exist?(f) }
+ expanded.select do |f|
+ does_exist = File.exist?(f)
+
+ if !does_exist && File.symlink?(f)
+ raise "File #{f.inspect} is a symlink that does not point to a valid file"
+ end
+ does_exist
+ end
end
def existent_directories
diff --git a/railties/lib/rails/secrets.rb b/railties/lib/rails/secrets.rb
index a083914109..8b644f212c 100644
--- a/railties/lib/rails/secrets.rb
+++ b/railties/lib/rails/secrets.rb
@@ -1,4 +1,5 @@
require "yaml"
+require "active_support/message_encryptor"
module Rails
# Greatly inspired by Ara T. Howard's magnificent sekrets gem. 😘
@@ -12,12 +13,11 @@ module Rails
end
end
- @read_encrypted_secrets = false
+ @cipher = "aes-128-gcm"
@root = File # Wonky, but ensures `join` uses the current directory.
class << self
- attr_writer :root
- attr_accessor :read_encrypted_secrets
+ attr_writer :root
def parse(paths, env:)
paths.each_with_object(Hash.new) do |path, all_secrets|
@@ -30,20 +30,19 @@ module Rails
end
def generate_key
- cipher = new_cipher
- SecureRandom.hex(cipher.key_len)[0, cipher.key_len]
+ SecureRandom.hex(OpenSSL::Cipher.new(@cipher).key_len)
end
def key
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
end
- def encrypt(text)
- cipher(:encrypt, text)
+ def encrypt(data)
+ encryptor.encrypt_and_sign(data)
end
def decrypt(data)
- cipher(:decrypt, data)
+ encryptor.decrypt_and_verify(data)
end
def read
@@ -87,24 +86,14 @@ module Rails
def preprocess(path)
if path.end_with?(".enc")
- if @read_encrypted_secrets
- decrypt(IO.binread(path))
- else
- ""
- end
+ decrypt(IO.binread(path))
else
IO.read(path)
end
end
- def new_cipher
- OpenSSL::Cipher.new("aes-256-cbc")
- end
-
- def cipher(mode, data)
- cipher = new_cipher.public_send(mode)
- cipher.key = key
- cipher.update(data) << cipher.final
+ def encryptor
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: @cipher)
end
end
end
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index c92b42f6c1..177b138090 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -1,6 +1,17 @@
task "load_app" do
namespace :app do
load APP_RAKEFILE
+
+ desc "Update some initially generated files"
+ task update: [ "update:bin" ]
+
+ namespace :update do
+ require "rails/engine/updater"
+ # desc "Adds new executables to the engine bin/ directory"
+ task :bin do
+ Rails::Engine::Updater.run(:create_bin_files)
+ end
+ end
end
task environment: "app:environment"
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index f5586b53f0..32a6b109bc 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -63,7 +63,7 @@ namespace :app do
# desc "Adds new executables to the application bin/ directory"
task :bin do
- RailsUpdate.invoke_from_app_generator :create_bin_files
+ RailsUpdate.invoke_from_app_generator :update_bin_files
end
task :upgrade_guide_info do
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 09931c108a..0f9bf98737 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -11,10 +11,6 @@ require "rails/generators/test_case"
require "active_support/testing/autorun"
-if defined?(Capbyara)
- require "action_dispatch/system_test_case"
-end
-
if defined?(ActiveRecord::Base)
ActiveRecord::Migration.maintain_test_schema!
@@ -48,12 +44,3 @@ class ActionDispatch::IntegrationTest
super
end
end
-
-if defined? Capybara
- class ActionDispatch::SystemTestCase
- def before_setup # :nodoc:
- @routes = Rails.application.routes
- super
- end
- end
-end
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index e44fe78bbd..8decdb0f4f 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -62,9 +62,9 @@ module Minitest
options[:patterns] = opts.order! unless run_via.rake?
end
- def self.rake_run(patterns) # :nodoc:
+ def self.rake_run(patterns, exclude_patterns = []) # :nodoc:
self.run_via = :rake unless run_via.set?
- ::Rails::TestRequirer.require_files(patterns)
+ ::Rails::TestRequirer.require_files(patterns, exclude_patterns)
autorun
end
@@ -88,7 +88,13 @@ module Minitest
# If run via `ruby` we've been passed the files to run directly, or if run
# via `rake` then they have already been eagerly required.
unless run_via.ruby? || run_via.rake?
- ::Rails::TestRequirer.require_files(options[:patterns])
+ # If there are no given patterns, we can assume that the user
+ # simply runs the `bin/rails test` command without extra arguments.
+ if options[:patterns].empty?
+ ::Rails::TestRequirer.require_files(options[:patterns], ["test/system/**/*"])
+ else
+ ::Rails::TestRequirer.require_files(options[:patterns])
+ end
end
unless options[:full_backtrace] || ENV["BACKTRACE"]
diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb
index fe35934abc..92e5fcf0bc 100644
--- a/railties/lib/rails/test_unit/test_requirer.rb
+++ b/railties/lib/rails/test_unit/test_requirer.rb
@@ -4,10 +4,13 @@ require "rake/file_list"
module Rails
class TestRequirer # :nodoc:
class << self
- def require_files(patterns)
+ def require_files(patterns, exclude_patterns = [])
patterns = expand_patterns(patterns)
- Rake::FileList[patterns.compact.presence || "test/**/*_test.rb"].to_a.each do |file|
+ file_list = Rake::FileList[patterns.compact.presence || "test/**/*_test.rb"]
+ file_list.exclude(exclude_patterns)
+
+ file_list.to_a.each do |file|
require File.expand_path(file)
end
end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 4dde3d3c97..ef19bd7626 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -4,15 +4,15 @@ require "rails/test_unit/minitest_plugin"
task default: :test
-desc "Runs all tests in test folder"
+desc "Runs all tests in test folder except system ones"
task :test do
$: << "test"
- pattern = if ENV.key?("TEST")
- ENV["TEST"]
+
+ if ENV.key?("TEST")
+ Minitest.rake_run([ENV["TEST"]])
else
- "test"
+ Minitest.rake_run(["test"], ["test/system/**/*"])
end
- Minitest.rake_run([pattern])
end
namespace :test do
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index ee0d697599..fe581db286 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -189,6 +189,9 @@ module ApplicationTests
FileUtils.cd(rails_root) do
output = `bin/rails generate --help`
assert_no_match "active_record:migration", output
+
+ output = `bin/rails destroy --help`
+ assert_no_match "active_record:migration", output
end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 90927159dd..eb2c578f91 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -262,5 +262,13 @@ module ApplicationTests
Rails.env = orig_rails_env if orig_rails_env
end
end
+
+ test "connections checked out during initialization are returned to the pool" do
+ app_file "config/initializers/active_record.rb", <<-RUBY
+ ActiveRecord::Base.connection
+ RUBY
+ require "#{app_path}/config/environment"
+ assert !ActiveRecord::Base.connection_pool.active_connection?
+ end
end
end
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index dc1d816dc5..93b5263bf7 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -117,12 +117,12 @@ module ApplicationTests
assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
assert_equal "public", last_response.headers["Cache-Control"]
- body = last_response.body
etag = last_response.headers["ETag"]
- get "/expires/expires_etag", {}, "If-None-Match" => etag
+ get "/expires/expires_etag", {}, "HTTP_IF_NONE_MATCH" => etag
assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"]
- assert_equal body, last_response.body
+ assert_equal 304, last_response.status
+ assert_equal "", last_response.body
end
def test_cache_works_with_etags_private
@@ -137,7 +137,7 @@ module ApplicationTests
body = last_response.body
etag = last_response.headers["ETag"]
- get "/expires/expires_etag", { private: true }, "If-None-Match" => etag
+ get "/expires/expires_etag", { private: true }, "HTTP_IF_NONE_MATCH" => etag
assert_equal "miss", last_response.headers["X-Rack-Cache"]
assert_not_equal body, last_response.body
end
@@ -151,12 +151,12 @@ module ApplicationTests
assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
assert_equal "public", last_response.headers["Cache-Control"]
- body = last_response.body
last = last_response.headers["Last-Modified"]
- get "/expires/expires_last_modified", {}, "If-Modified-Since" => last
+ get "/expires/expires_last_modified", {}, "HTTP_IF_MODIFIED_SINCE" => last
assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"]
- assert_equal body, last_response.body
+ assert_equal 304, last_response.status
+ assert_equal "", last_response.body
end
def test_cache_works_with_last_modified_private
@@ -171,7 +171,7 @@ module ApplicationTests
body = last_response.body
last = last_response.headers["Last-Modified"]
- get "/expires/expires_last_modified", { private: true }, "If-Modified-Since" => last
+ get "/expires/expires_last_modified", { private: true }, "HTTP_IF_MODIFIED_SINCE" => last
assert_equal "miss", last_response.headers["X-Rack-Cache"]
assert_not_equal body, last_response.body
end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 76cb302c62..449d281967 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -37,6 +37,22 @@ module ApplicationTests
end
end
+ test "migration with empty version" do
+ Dir.chdir(app_path) do
+ output = `bin/rails db:migrate VERSION= 2>&1`
+ assert_match(/Empty VERSION provided/, output)
+
+ output = `bin/rails db:migrate:redo VERSION= 2>&1`
+ assert_match(/Empty VERSION provided/, output)
+
+ output = `bin/rails db:migrate:up VERSION= 2>&1`
+ assert_match(/VERSION is required/, output)
+
+ output = `bin/rails db:migrate:down VERSION= 2>&1`
+ assert_match(/VERSION is required - To go down one migration, use db:rollback/, output)
+ end
+ end
+
test "model and migration generator with change syntax" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
@@ -61,7 +77,7 @@ module ApplicationTests
assert_equal "Schema migrations table does not exist yet.\n", output
end
- test "test migration status" do
+ test "migration status" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
@@ -101,7 +117,7 @@ module ApplicationTests
end
end
- test "test migration status after rollback and redo" do
+ test "migration status after rollback and redo" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
@@ -208,7 +224,7 @@ module ApplicationTests
end
end
- test "test migration status migrated file is deleted" do
+ test "migration status migrated file is deleted" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index 7d058f6ee6..0c45bc398a 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -35,6 +35,14 @@ module ApplicationTests
assert_match "42", Dir.chdir(app_path) { `bin/rails runner "puts User.count"` }
end
+ def test_should_set_argv_when_running_code
+ output = Dir.chdir(app_path) {
+ # Both long and short args, at start and end of ARGV
+ `bin/rails runner "puts ARGV.join(',')" --foo a1 -b a2 a3 --moo`
+ }
+ assert_equal "--foo,a1,-b,a2,a3,--moo", output.chomp
+ end
+
def test_should_run_file
app_file "bin/count_users.rb", <<-SCRIPT
puts User.count
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index e773b52dbb..23b259b503 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -323,7 +323,7 @@ module ApplicationTests
assert true
end
- test "test line filter does not run this" do
+ test "line filter does not run this" do
assert true
end
end
@@ -576,6 +576,80 @@ module ApplicationTests
capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") })
end
+ def test_reset_sessions_before_rollback_on_system_tests
+ app_file "test/system/reset_session_before_rollback_test.rb", <<-RUBY
+ require "application_system_test_case"
+
+ class ResetSessionBeforeRollbackTest < ApplicationSystemTestCase
+ def teardown_fixtures
+ puts "rollback"
+ super
+ end
+
+ Capybara.singleton_class.prepend(Module.new do
+ def reset_sessions!
+ puts "reset sessions"
+ super
+ end
+ end)
+
+ test "dummy" do
+ end
+ end
+ RUBY
+
+ run_test_command("test/system/reset_session_before_rollback_test.rb").tap do |output|
+ assert_match "reset sessions\nrollback", output
+ assert_match "1 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output
+ end
+ end
+
+ def test_system_tests_are_not_run_with_the_default_test_command
+ app_file "test/system/dummy_test.rb", <<-RUBY
+ require "application_system_test_case"
+
+ class DummyTest < ApplicationSystemTestCase
+ test "something" do
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command("").tap do |output|
+ assert_match "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output
+ end
+ end
+
+ def test_system_tests_are_not_run_through_rake_test
+ app_file "test/system/dummy_test.rb", <<-RUBY
+ require "application_system_test_case"
+
+ class DummyTest < ApplicationSystemTestCase
+ test "something" do
+ assert true
+ end
+ end
+ RUBY
+
+ output = Dir.chdir(app_path) { `bin/rake test` }
+ assert_match "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output
+ end
+
+ def test_system_tests_are_run_through_rake_test_when_given_in_TEST
+ app_file "test/system/dummy_test.rb", <<-RUBY
+ require "application_system_test_case"
+
+ class DummyTest < ApplicationSystemTestCase
+ test "something" do
+ assert true
+ end
+ end
+ RUBY
+
+ output = Dir.chdir(app_path) { `bin/rake test TEST=test/system/dummy_test.rb` }
+ assert_match "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips", output
+ end
+
private
def run_test_command(arguments = "test/unit/test_test.rb")
Dir.chdir(app_path) { `bin/rails t #{arguments}` }
diff --git a/railties/test/command/base_test.rb b/railties/test/command/base_test.rb
new file mode 100644
index 0000000000..ebfc4d0ba9
--- /dev/null
+++ b/railties/test/command/base_test.rb
@@ -0,0 +1,11 @@
+require "abstract_unit"
+require "rails/command"
+require "rails/commands/generate/generate_command"
+require "rails/commands/secrets/secrets_command"
+
+class Rails::Command::BaseTest < ActiveSupport::TestCase
+ test "printing commands" do
+ assert_equal %w(generate), Rails::Command::GenerateCommand.printing_commands
+ assert_equal %w(secrets:setup secrets:edit), Rails::Command::SecretsCommand.printing_commands
+ end
+end
diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb
new file mode 100644
index 0000000000..00b0343397
--- /dev/null
+++ b/railties/test/commands/secrets_test.rb
@@ -0,0 +1,37 @@
+require "isolation/abstract_unit"
+require "rails/command"
+require "rails/commands/secrets/secrets_command"
+
+class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "edit without editor gives hint" do
+ assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "")
+ end
+
+ test "edit secrets" do
+ run_setup_command
+
+ # Run twice to ensure encrypted secrets can be reread after first edit pass.
+ 2.times do
+ assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289…/, run_edit_command)
+ end
+ end
+
+ private
+ def run_edit_command(editor: "cat")
+ Dir.chdir(app_path) { `EDITOR="#{editor}" bin/rails secrets:edit` }
+ end
+
+ def run_setup_command
+ Dir.chdir(app_path) { `bin/rails secrets:setup` }
+ end
+end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 0f236ff463..2d1f071969 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -121,6 +121,32 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
end
+ def test_host
+ with_rails_env "development" do
+ options = parse_arguments([])
+ assert_equal "localhost", options[:Host]
+ end
+
+ with_rails_env "production" do
+ options = parse_arguments([])
+ assert_equal "0.0.0.0", options[:Host]
+ end
+
+ with_rails_env "development" do
+ args = ["-b", "127.0.0.1"]
+ options = parse_arguments(args)
+ assert_equal "127.0.0.1", options[:Host]
+ end
+ end
+
+ def test_argument_precedence_over_environment_variable
+ switch_env "HOST", "1.2.3.4" do
+ args = ["-b", "127.0.0.1"]
+ options = parse_arguments(args)
+ assert_equal "127.0.0.1", options[:Host]
+ end
+ end
+
def test_records_user_supplied_options
server_options = parse_arguments(["-p", 3001])
assert_equal [:Port], server_options[:user_supplied_options]
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index bdef1798f8..2edb39c8e8 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -61,13 +61,26 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_generator_skips_per_form_csrf_token_and_origin_check_configs_for_api_apps
+ def test_app_update_does_not_generate_unnecessary_config_files
run_generator
- assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content|
- assert_no_match(/per_form_csrf_tokens/, initializer_content)
- assert_no_match(/forgery_protection_origin_check/, initializer_content)
- end
+ generator = Rails::Generators::AppGenerator.new ["rails"],
+ { api: true, update: true }, destination_root: destination_root, shell: @shell
+ quietly { generator.send(:update_config_files) }
+
+ assert_no_file "config/initializers/cookies_serializer.rb"
+ assert_no_file "config/initializers/assets.rb"
+ assert_no_file "config/initializers/new_framework_defaults_5_1.rb"
+ end
+
+ def test_app_update_does_not_generate_unnecessary_bin_files
+ run_generator
+
+ generator = Rails::Generators::AppGenerator.new ["rails"],
+ { api: true, update: true }, destination_root: destination_root, shell: @shell
+ quietly { generator.send(:update_bin_files) }
+
+ assert_no_file "bin/yarn"
end
private
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 986afb6d2a..d3e131db94 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -133,7 +133,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_generates_correct_session_key
+ def test_app_update_generates_correct_session_key
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -156,66 +156,65 @@ class AppGeneratorTest < Rails::Generators::TestCase
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")
+ def test_new_application_doesnt_need_defaults
+ assert_no_file "config/initializers/new_framework_defaults_5_1.rb"
+ end
+
+ def test_new_application_load_defaults
+ app_root = File.join(destination_root, "myfirstapp")
run_generator [app_root]
+ output = nil
- stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
+ Dir.chdir(app_root) do
+ output = `./bin/rails r "puts Rails.application.config.assets.unknown_asset_fallback"`
end
+
+ assert_equal "false\n", output
end
- def test_rails_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured
+ def test_app_update_keep_the_cookie_serializer_if_it_is_already_configured
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
- FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb")
-
stub_rails_application(app_root) do
generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
- assert_file("#{app_root}/config/initializers/cookies_serializer.rb",
- /Valid options are :json, :marshal, and :hybrid\.\nRails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
end
end
- def test_rails_update_dont_set_file_watcher
+ def test_app_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
+ FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb")
+
stub_rails_application(app_root) do
generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/environments/development.rb" do |content|
- assert_match(/# config.file_watcher/, content)
- end
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb",
+ /Valid options are :json, :marshal, and :hybrid\.\nRails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
end
end
- def test_rails_update_does_not_create_new_framework_defaults_by_default
+ def test_app_update_create_new_framework_defaults
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
- FileUtils.rm("#{app_root}/config/initializers/new_framework_defaults.rb")
+ assert_no_file "#{app_root}/config/initializers/new_framework_defaults_5_1.rb"
stub_rails_application(app_root) do
generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/initializers/new_framework_defaults.rb" do |content|
- assert_match(/Rails\.application\.config.active_record\.belongs_to_required_by_default = false/, content)
- assert_no_match(/Rails\.application\.config\.ssl_options/, content)
- end
+ assert_file "#{app_root}/config/initializers/new_framework_defaults_5_1.rb"
end
end
- def test_rails_update_does_not_create_rack_cors
+ def test_app_update_does_not_create_rack_cors
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -227,7 +226,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_rails_update_does_not_remove_rack_cors_if_already_present
+ def test_app_update_does_not_remove_rack_cors_if_already_present
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -367,10 +366,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "bin/update" do |update_content|
assert_no_match(/db:migrate/, update_content)
end
-
- assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content|
- assert_no_match(/belongs_to_required_by_default/, initializer_content)
- end
end
def test_generator_if_skip_action_mailer_is_given
@@ -415,9 +410,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/config\.assets\.js_compressor = :uglifier/, content)
assert_no_match(/config\.assets\.css_compressor = :sass/, content)
end
- assert_file "config/initializers/new_framework_defaults.rb" do |content|
- assert_no_match(/unknown_asset_fallback/, content)
- end
end
def test_generator_if_skip_yarn_is_given
@@ -443,6 +435,30 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /^# gem 'redis'/
end
+ def test_generator_if_skip_test_is_given
+ run_generator [destination_root, "--skip-test"]
+ assert_file "Gemfile" do |content|
+ assert_no_match(/capybara/, content)
+ assert_no_match(/selenium-webdriver/, content)
+ end
+ end
+
+ def test_generator_if_skip_system_test_is_given
+ run_generator [destination_root, "--skip_system_test"]
+ assert_file "Gemfile" do |content|
+ assert_no_match(/capybara/, content)
+ assert_no_match(/selenium-webdriver/, content)
+ end
+ end
+
+ def test_generator_if_api_is_given
+ run_generator [destination_root, "--api"]
+ assert_file "Gemfile" do |content|
+ assert_no_match(/capybara/, content)
+ assert_no_match(/selenium-webdriver/, content)
+ end
+ end
+
def test_inclusion_of_javascript_runtime
run_generator
if defined?(JRUBY_VERSION)
@@ -459,14 +475,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_inclusion_of_javascript_libraries_if_required
- run_generator [destination_root, "-j", "jquery"]
- assert_file "app/assets/javascripts/application.js" do |contents|
- assert_match %r{^//= require jquery}, contents
- end
- assert_gem "jquery-rails"
- end
-
def test_javascript_is_skipped_if_required
run_generator [destination_root, "--skip-javascript"]
diff --git a/railties/test/generators/encrypted_secrets_generator_test.rb b/railties/test/generators/encrypted_secrets_generator_test.rb
index 747abf19ed..21fdcab19f 100644
--- a/railties/test/generators/encrypted_secrets_generator_test.rb
+++ b/railties/test/generators/encrypted_secrets_generator_test.rb
@@ -12,11 +12,11 @@ class EncryptedSecretsGeneratorTest < Rails::Generators::TestCase
def test_generates_key_file_and_encrypted_secrets_file
run_generator
- assert_file "config/secrets.yml.key", /[\w\d]+/
+ assert_file "config/secrets.yml.key", /\w+/
assert File.exist?("config/secrets.yml.enc")
- assert_no_match(/production:\n# external_api_key: [\w\d]+/, IO.binread("config/secrets.yml.enc"))
- assert_match(/production:\n# external_api_key: [\w\d]+/, Rails::Secrets.read)
+ assert_no_match(/production:\n# external_api_key: \w+/, IO.binread("config/secrets.yml.enc"))
+ assert_match(/production:\n# external_api_key: \w+/, Rails::Secrets.read)
end
def test_appends_to_gitignore
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 8ec096e5c6..afb37b6a99 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -1,6 +1,7 @@
require "generators/generators_test_helper"
require "rails/generators/rails/plugin/plugin_generator"
require "generators/shared_generator_tests"
+require "rails/engine/updater"
DEFAULT_PLUGIN_FILES = %w(
.gitignore
@@ -731,6 +732,21 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_app_update_generates_bin_file
+ run_generator [destination_root, "--mountable"]
+
+ Object.const_set("ENGINE_ROOT", destination_root)
+ FileUtils.rm("#{destination_root}/bin/rails")
+
+ quietly { Rails::Engine::Updater.run(:create_bin_files) }
+
+ assert_file "#{destination_root}/bin/rails" do |content|
+ assert_match(%r|APP_PATH = File\.expand_path\('\.\./\.\./test/dummy/config/application', __FILE__\)|, content)
+ end
+ ensure
+ Object.send(:remove_const, "ENGINE_ROOT")
+ end
+
private
def action(*args, &block)
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 436fbd5d73..bc76cead18 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -437,8 +437,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "app/views/accounts/_form.html.erb" do |content|
- assert_match(/^\W{4}<%= f\.text_field :name %>/, content)
- assert_match(/^\W{4}<%= f\.text_field :currency_id %>/, content)
+ assert_match(/^\W{4}<%= form\.text_field :name, id: :account_name %>/, content)
+ assert_match(/^\W{4}<%= form\.text_field :currency_id, id: :account_currency_id %>/, content)
end
end
@@ -461,8 +461,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "app/views/users/_form.html.erb" do |content|
- assert_match(/<%= f\.password_field :password %>/, content)
- assert_match(/<%= f\.password_field :password_confirmation %>/, content)
+ assert_match(/<%= form\.password_field :password, id: :user_password %>/, content)
+ assert_match(/<%= form\.password_field :password_confirmation, id: :user_password_confirmation %>/, content)
end
assert_file "app/views/users/index.html.erb" do |content|
@@ -558,4 +558,59 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
+
+ def test_scaffold_on_invoke_inside_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly { `bin/rails generate scaffold User name:string age:integer` }
+
+ assert File.exist?("app/models/bukkits/user.rb")
+ assert File.exist?("test/models/bukkits/user_test.rb")
+ assert File.exist?("test/fixtures/bukkits/users.yml")
+
+ assert File.exist?("app/controllers/bukkits/users_controller.rb")
+ assert File.exist?("test/controllers/bukkits/users_controller_test.rb")
+
+ assert File.exist?("app/views/bukkits/users/index.html.erb")
+ assert File.exist?("app/views/bukkits/users/edit.html.erb")
+ assert File.exist?("app/views/bukkits/users/show.html.erb")
+ assert File.exist?("app/views/bukkits/users/new.html.erb")
+ assert File.exist?("app/views/bukkits/users/_form.html.erb")
+
+ assert File.exist?("app/helpers/bukkits/users_helper.rb")
+
+ assert File.exist?("app/assets/javascripts/bukkits/users.js")
+ assert File.exist?("app/assets/stylesheets/bukkits/users.css")
+ end
+ end
+
+ def test_scaffold_on_revoke_inside_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly { `bin/rails generate scaffold User name:string age:integer` }
+ quietly { `bin/rails destroy scaffold User` }
+
+ assert_not File.exist?("app/models/bukkits/user.rb")
+ assert_not File.exist?("test/models/bukkits/user_test.rb")
+ assert_not File.exist?("test/fixtures/bukkits/users.yml")
+
+ assert_not File.exist?("app/controllers/bukkits/users_controller.rb")
+ assert_not File.exist?("test/controllers/bukkits/users_controller_test.rb")
+
+ assert_not File.exist?("app/views/bukkits/users/index.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/edit.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/show.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/new.html.erb")
+ assert_not File.exist?("app/views/bukkits/users/_form.html.erb")
+
+ assert_not File.exist?("app/helpers/bukkits/users_helper.rb")
+
+ assert_not File.exist?("app/assets/javascripts/bukkits/users.js")
+ assert_not File.exist?("app/assets/stylesheets/bukkits/users.css")
+ end
+ end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 68ba435393..c3c16b6f86 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -200,7 +200,7 @@ class GeneratorsTest < Rails::Generators::TestCase
self.class.class_eval(<<-end_eval, __FILE__, __LINE__ + 1)
class WithOptionsGenerator < Rails::Generators::Base
- class_option :generate, :default => true
+ class_option :generate, default: true, type: :boolean
end
end_eval
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 7b2551062a..f3db0a51d2 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -274,3 +274,23 @@ class PathsTest < ActiveSupport::TestCase
end
end
end
+
+class PathsIntegrationTest < ActiveSupport::TestCase
+ test "A failed symlink is still a valid file" do
+ Dir.mktmpdir do |dir|
+ Dir.chdir(dir) do
+ FileUtils.mkdir_p("foo")
+ File.symlink("foo/doesnotexist.rb", "foo/bar.rb")
+ assert_equal true, File.symlink?("foo/bar.rb")
+
+ root = Rails::Paths::Root.new("foo")
+ root.add "bar.rb"
+
+ exception = assert_raises(RuntimeError) do
+ root["bar.rb"].existent
+ end
+ assert_match File.expand_path("foo/bar.rb"), exception.message
+ end
+ end
+ end
+end
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 5838d0d7e7..6639e55382 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -141,7 +141,7 @@ module ApplicationTests
end
def engine_asset_path
- render inline: "<%= asset_path 'images/foo.png' %>"
+ render inline: "<%= asset_path 'images/foo.png', skip_pipeline: true %>"
end
end
end
diff --git a/railties/test/secrets_test.rb b/railties/test/secrets_test.rb
index 36e42cf1f9..36c8ef1fd9 100644
--- a/railties/test/secrets_test.rb
+++ b/railties/test/secrets_test.rb
@@ -9,22 +9,22 @@ class Rails::SecretsTest < ActiveSupport::TestCase
def setup
build_app
-
- @old_read_encrypted_secrets, Rails::Secrets.read_encrypted_secrets =
- Rails::Secrets.read_encrypted_secrets, true
end
def teardown
- Rails::Secrets.read_encrypted_secrets = @old_read_encrypted_secrets
-
teardown_app
end
test "setting read to false skips parsing" do
- Rails::Secrets.read_encrypted_secrets = false
+ run_secrets_generator do
+ Rails::Secrets.write(<<-end_of_secrets)
+ test:
+ yeah_yeah: lets-walk-in-the-cool-evening-light
+ end_of_secrets
- Dir.chdir(app_path) do
- assert_equal Hash.new, Rails::Secrets.parse(%w( config/secrets.yml.enc ), env: "production")
+ Rails.application.config.read_encrypted_secrets = false
+ Rails.application.instance_variable_set(:@secrets, nil) # Dance around caching 💃🕺
+ assert_not Rails.application.secrets.yeah_yeah
end
end
@@ -54,9 +54,9 @@ class Rails::SecretsTest < ActiveSupport::TestCase
test "reading from key file" do
run_secrets_generator do
- File.binwrite("config/secrets.yml.key", "How do I know you feel it?")
+ File.binwrite("config/secrets.yml.key", "00112233445566778899aabbccddeeff")
- assert_equal "How do I know you feel it?", Rails::Secrets.key
+ assert_equal "00112233445566778899aabbccddeeff", Rails::Secrets.key
end
end
@@ -90,11 +90,27 @@ class Rails::SecretsTest < ActiveSupport::TestCase
end_of_secrets
Rails.application.config.root = app_path
+ Rails.application.config.read_encrypted_secrets = true
Rails.application.instance_variable_set(:@secrets, nil) # Dance around caching 💃🕺
assert_equal "lets-walk-in-the-cool-evening-light", Rails.application.secrets.yeah_yeah
end
end
+ test "refer secrets inside env config" do
+ run_secrets_generator do
+ Rails::Secrets.write(<<-end_of_yaml)
+ production:
+ some_secret: yeah yeah
+ end_of_yaml
+
+ add_to_env_config "production", <<-end_of_config
+ config.dereferenced_secret = Rails.application.secrets.some_secret
+ end_of_config
+
+ assert_equal "yeah yeah\n", `bin/rails runner -e production "puts Rails.application.config.dereferenced_secret"`
+ end
+ end
+
private
def run_secrets_generator
Dir.chdir(app_path) do
diff --git a/tasks/release.rb b/tasks/release.rb
index 8fb151ceb4..b021535245 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -229,7 +229,7 @@ MSG
To see the full list of changes, [check out all the commits on
GitHub](https://github.com/rails/rails/compare/v#{previous_version}...v#{version}).
-## SHA-1
+## SHA-256
If you'd like to verify that your gem is the same as the one I've uploaded,
please use these SHA-256 hashes.
diff --git a/version.rb b/version.rb
index 3174ffb0dc..7bacf2e0ba 100644
--- a/version.rb
+++ b/version.rb
@@ -6,9 +6,9 @@ module Rails
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end