aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml3
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock80
-rw-r--r--RAILS_VERSION2
-rw-r--r--actioncable/CHANGELOG.md9
-rw-r--r--actioncable/MIT-LICENSE2
-rw-r--r--actioncable/README.md78
-rw-r--r--actioncable/actioncable.gemspec10
-rw-r--r--actioncable/lib/action_cable.rb3
-rw-r--r--actioncable/lib/action_cable/channel/base.rb4
-rw-r--r--actioncable/lib/action_cable/channel/periodic_timers.rb6
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb8
-rw-r--r--actioncable/lib/action_cable/connection.rb5
-rw-r--r--actioncable/lib/action_cable/connection/base.rb43
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb152
-rw-r--r--actioncable/lib/action_cable/connection/identification.rb2
-rw-r--r--actioncable/lib/action_cable/connection/internal_channel.rb12
-rw-r--r--actioncable/lib/action_cable/connection/stream.rb59
-rw-r--r--actioncable/lib/action_cable/connection/stream_event_loop.rb68
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb1
-rw-r--r--actioncable/lib/action_cable/connection/web_socket.rb22
-rw-r--r--actioncable/lib/action_cable/engine.rb6
-rw-r--r--actioncable/lib/action_cable/gem_version.rb2
-rw-r--r--actioncable/lib/action_cable/process/logging.rb10
-rw-r--r--actioncable/lib/action_cable/remote_connections.rb2
-rw-r--r--actioncable/lib/action_cable/server.rb4
-rw-r--r--actioncable/lib/action_cable/server/base.rb26
-rw-r--r--actioncable/lib/action_cable/server/broadcasting.rb9
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb30
-rw-r--r--actioncable/lib/action_cable/server/connections.rb8
-rw-r--r--actioncable/lib/action_cable/server/worker.rb55
-rw-r--r--actioncable/lib/action_cable/subscription_adapter.rb8
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/async.rb22
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/base.rb28
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/inline.rb26
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/postgresql.rb101
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/redis.rb58
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb53
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/connection.coffee3
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee5
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/consumer.coffee6
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee14
-rw-r--r--actioncable/test/channel/periodic_timers_test.rb2
-rw-r--r--actioncable/test/channel/stream_test.rb28
-rw-r--r--actioncable/test/connection/authorization_test.rb1
-rw-r--r--actioncable/test/connection/base_test.rb20
-rw-r--r--actioncable/test/connection/cross_site_forgery_test.rb1
-rw-r--r--actioncable/test/connection/identifier_test.rb12
-rw-r--r--actioncable/test/connection/multiple_identifiers_test.rb4
-rw-r--r--actioncable/test/connection/string_identifier_test.rb1
-rw-r--r--actioncable/test/connection/subscriptions_test.rb1
-rw-r--r--actioncable/test/stubs/test_adapter.rb10
-rw-r--r--actioncable/test/stubs/test_connection.rb4
-rw-r--r--actioncable/test/stubs/test_server.rb9
-rw-r--r--actioncable/test/subscription_adapter/async_test.rb17
-rw-r--r--actioncable/test/subscription_adapter/base_test.rb73
-rw-r--r--actioncable/test/subscription_adapter/common.rb139
-rw-r--r--actioncable/test/subscription_adapter/inline_test.rb17
-rw-r--r--actioncable/test/subscription_adapter/postgresql_test.rb32
-rw-r--r--actioncable/test/subscription_adapter/redis_test.rb10
-rw-r--r--actioncable/test/test_helper.rb30
-rw-r--r--actioncable/test/worker_test.rb7
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb2
-rw-r--r--actionmailer/test/fixtures/async_mailer/welcome.erb1
-rw-r--r--actionpack/CHANGELOG.md52
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb8
-rw-r--r--actionpack/lib/action_controller.rb4
-rw-r--r--actionpack/lib/action_controller/api.rb2
-rw-r--r--actionpack/lib/action_controller/api/api_rendering.rb14
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb2
-rw-r--r--actionpack/lib/action_controller/metal/head.rb1
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb7
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb13
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb107
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb65
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb43
-rw-r--r--actionpack/lib/action_controller/test_case.rb16
-rw-r--r--actionpack/lib/action_dispatch.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb18
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb25
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb76
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb13
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb49
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb28
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb2
-rw-r--r--actionpack/test/abstract_unit.rb12
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb19
-rw-r--r--actionpack/test/controller/api/renderers_test.rb22
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb21
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/metal/renderers_test.rb42
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb12
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb18
-rw-r--r--actionpack/test/controller/render_other_test.rb24
-rw-r--r--actionpack/test/controller/render_test.rb33
-rw-r--r--actionpack/test/controller/renderers_test.rb90
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb172
-rw-r--r--actionpack/test/controller/required_params_test.rb13
-rw-r--r--actionpack/test/dispatch/request_test.rb21
-rw-r--r--actionpack/test/dispatch/response_test.rb35
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb38
-rw-r--r--actionpack/test/dispatch/routing_test.rb84
-rw-r--r--actionpack/test/dispatch/ssl_test.rb45
-rw-r--r--actionpack/test/dispatch/static_test.rb4
-rw-r--r--actionview/CHANGELOG.md24
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/flows.rb2
-rw-r--r--actionview/lib/action_view/gem_version.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb24
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb8
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb21
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb10
-rw-r--r--actionview/lib/action_view/lookup_context.rb6
-rw-r--r--actionview/lib/action_view/path_set.rb28
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb2
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb2
-rw-r--r--actionview/lib/action_view/template/handlers.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/html.rb9
-rw-r--r--actionview/lib/action_view/template/handlers/raw.rb4
-rw-r--r--actionview/lib/action_view/template/resolver.rb23
-rw-r--r--actionview/lib/action_view/template/types.rb29
-rw-r--r--actionview/lib/action_view/test_case.rb2
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb4
-rw-r--r--actionview/test/fixtures/layouts/render_partial_html.erb2
-rw-r--r--actionview/test/fixtures/test/_partialhtml.html1
-rw-r--r--actionview/test/lib/controller/fake_models.rb21
-rw-r--r--actionview/test/template/active_model_helper_test.rb2
-rw-r--r--actionview/test/template/capture_helper_test.rb17
-rw-r--r--actionview/test/template/date_helper_test.rb2
-rw-r--r--actionview/test/template/form_collections_helper_test.rb12
-rw-r--r--actionview/test/template/form_helper_test.rb64
-rw-r--r--actionview/test/template/form_options_helper_test.rb2
-rw-r--r--actionview/test/template/form_tag_helper_test.rb31
-rw-r--r--actionview/test/template/output_safety_helper_test.rb4
-rw-r--r--actionview/test/template/render_test.rb14
-rw-r--r--actionview/test/template/tag_helper_test.rb4
-rw-r--r--actionview/test/template/test_case_test.rb4
-rw-r--r--actionview/test/template/text_helper_test.rb32
-rw-r--r--actionview/test/template/translation_helper_test.rb2
-rw-r--r--actionview/test/template/url_helper_test.rb22
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/arguments.rb4
-rw-r--r--activejob/lib/active_job/gem_version.rb2
-rw-r--r--activejob/test/cases/argument_serialization_test.rb2
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/gem_version.rb2
-rw-r--r--activemodel/lib/active_model/serializers/json.rb2
-rw-r--r--activemodel/lib/active_model/type/time.rb2
-rw-r--r--activemodel/lib/active_model/type/value.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb3
-rw-r--r--activemodel/test/cases/types_test.rb3
-rw-r--r--activerecord/CHANGELOG.md91
-rw-r--r--activerecord/MIT-LICENSE4
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/associations.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb18
-rw-r--r--activerecord/lib/active_record/autosave_association.rb21
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb151
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb16
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/counter_cache.rb8
-rw-r--r--activerecord/lib/active_record/enum.rb15
-rw-r--r--activerecord/lib/active_record/gem_version.rb2
-rw-r--r--activerecord/lib/active_record/inheritance.rb1
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb51
-rw-r--r--activerecord/lib/active_record/migration.rb105
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb17
-rw-r--r--activerecord/lib/active_record/model_schema.rb26
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb14
-rw-r--r--activerecord/lib/active_record/railtie.rb7
-rw-r--r--activerecord/lib/active_record/railties/databases.rake26
-rw-r--r--activerecord/lib/active_record/reflection.rb6
-rw-r--r--activerecord/lib/active_record/relation.rb4
-rw-r--r--activerecord/lib/active_record/relation/batches.rb67
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb12
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb49
-rw-r--r--activerecord/lib/active_record/relation/from_clause.rb2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb3
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb20
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb18
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb38
-rw-r--r--activerecord/lib/active_record/relation/record_fetch_warning.rb10
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb1
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema.rb3
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/scoping.rb28
-rw-r--r--activerecord/lib/active_record/scoping/default.rb6
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb18
-rw-r--r--activerecord/lib/active_record/timestamp.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb6
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb9
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb44
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb36
-rw-r--r--activerecord/test/cases/adapters/mysql2/sp_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb17
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb5
-rw-r--r--activerecord/test/cases/ar_schema_test.rb4
-rw-r--r--activerecord/test/cases/autosave_association_test.rb47
-rw-r--r--activerecord/test/cases/base_test.rb5
-rw-r--r--activerecord/test/cases/batches_test.rb45
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb19
-rw-r--r--activerecord/test/cases/column_definition_test.rb28
-rw-r--r--activerecord/test/cases/database_statements_test.rb15
-rw-r--r--activerecord/test/cases/defaults_test.rb26
-rw-r--r--activerecord/test/cases/enum_test.rb10
-rw-r--r--activerecord/test/cases/finder_test.rb9
-rw-r--r--activerecord/test/cases/fixtures_test.rb4
-rw-r--r--activerecord/test/cases/inheritance_test.rb76
-rw-r--r--activerecord/test/cases/invalid_connection_test.rb2
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb34
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb4
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb8
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb10
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb74
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb13
-rw-r--r--activerecord/test/cases/reflection_test.rb10
-rw-r--r--activerecord/test/cases/relation/merging_test.rb7
-rw-r--r--activerecord/test/cases/relation/or_test.rb4
-rw-r--r--activerecord/test/cases/relation_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb29
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb4
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb14
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb28
-rw-r--r--activerecord/test/cases/transactions_test.rb5
-rw-r--r--activerecord/test/fixtures/author_addresses.yml6
-rw-r--r--activerecord/test/fixtures/authors.yml2
-rw-r--r--activerecord/test/fixtures/naked/yml/trees.yml3
-rw-r--r--activerecord/test/models/book.rb1
-rw-r--r--activerecord/test/models/chef.rb4
-rw-r--r--activerecord/test/models/hotel.rb4
-rw-r--r--activerecord/test/models/mocktail_designer.rb2
-rw-r--r--activerecord/test/models/post.rb7
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb29
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb37
-rw-r--r--activerecord/test/schema/schema.rb3
-rw-r--r--activesupport/CHANGELOG.md22
-rw-r--r--activesupport/MIT-LICENSE4
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb12
-rw-r--r--activesupport/lib/active_support/callbacks.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb18
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb6
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb24
-rw-r--r--activesupport/lib/active_support/locale/en.yml2
-rw-r--r--activesupport/lib/active_support/logger.rb16
-rw-r--r--activesupport/lib/active_support/logger_silence.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb51
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb1
-rw-r--r--activesupport/lib/active_support/number_helper.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb2
-rw-r--r--activesupport/lib/active_support/security_utils.rb7
-rw-r--r--activesupport/lib/active_support/test_case.rb10
-rw-r--r--activesupport/lib/active_support/testing/composite_filter.rb54
-rw-r--r--activesupport/test/broadcast_logger_test.rb50
-rw-r--r--activesupport/test/caching_test.rb8
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb40
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb6
-rw-r--r--activesupport/test/logger_test.rb12
-rw-r--r--activesupport/test/multibyte_chars_test.rb32
-rw-r--r--activesupport/test/multibyte_conformance_test.rb26
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance_test.rb76
-rw-r--r--activesupport/test/multibyte_normalization_conformance_test.rb129
-rw-r--r--activesupport/test/notifications_test.rb2
-rw-r--r--activesupport/test/number_helper_test.rb14
-rw-r--r--guides/assets/images/belongs_to.pngbin26076 -> 25803 bytes
-rw-r--r--guides/assets/images/has_many.pngbin28988 -> 28919 bytes
-rw-r--r--guides/assets/images/rails_guides_logo.gifbin5106 -> 3770 bytes
-rw-r--r--[-rwxr-xr-x]guides/assets/javascripts/responsive-tables.js0
-rw-r--r--guides/source/5_0_release_notes.md15
-rw-r--r--guides/source/action_controller_overview.md12
-rw-r--r--guides/source/active_job_basics.md4
-rw-r--r--guides/source/active_record_callbacks.md2
-rw-r--r--guides/source/active_record_migrations.md14
-rw-r--r--guides/source/active_record_postgresql.md4
-rw-r--r--guides/source/active_record_querying.md52
-rw-r--r--guides/source/active_support_core_extensions.md14
-rw-r--r--guides/source/asset_pipeline.md8
-rw-r--r--guides/source/association_basics.md480
-rw-r--r--guides/source/command_line.md122
-rw-r--r--guides/source/configuring.md55
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md1
-rw-r--r--guides/source/debugging_rails_applications.md10
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/engines.md22
-rw-r--r--guides/source/getting_started.md32
-rw-r--r--guides/source/initialization.md19
-rw-r--r--guides/source/layout.html.erb15
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/maintenance_policy.md6
-rw-r--r--guides/source/plugins.md2
-rw-r--r--guides/source/rails_application_templates.md4
-rw-r--r--guides/source/rails_on_rack.md4
-rw-r--r--guides/source/routing.md10
-rw-r--r--guides/source/testing.md48
-rw-r--r--railties/CHANGELOG.md14
-rw-r--r--railties/MIT-LICENSE4
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb2
-rw-r--r--railties/lib/rails/code_statistics.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb8
-rw-r--r--railties/lib/rails/commands/server.rb10
-rw-r--r--railties/lib/rails/gem_version.rb2
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb14
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb5
-rw-r--r--railties/lib/rails/generators/named_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/cable.yml12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/secrets.yml2
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE8
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/app/models/application_record.rb.tt)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/USAGE2
-rw-r--r--railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb2
-rw-r--r--railties/lib/rails/tasks/log.rake25
-rw-r--r--railties/lib/rails/test_unit/line_filtering.rb70
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb3
-rw-r--r--railties/lib/rails/test_unit/railtie.rb6
-rw-r--r--railties/lib/rails/test_unit/test_requirer.rb2
-rw-r--r--railties/lib/rails/test_unit/testing.rake7
-rw-r--r--railties/test/application/asset_debugging_test.rb2
-rw-r--r--railties/test/application/assets_test.rb4
-rw-r--r--railties/test/application/bin_setup_test.rb8
-rw-r--r--railties/test/application/configuration_test.rb20
-rw-r--r--railties/test/application/initializers/frameworks_test.rb4
-rw-r--r--railties/test/application/loading_test.rb6
-rw-r--r--railties/test/application/middleware/session_test.rb9
-rw-r--r--railties/test/application/rake/dbs_test.rb46
-rw-r--r--railties/test/application/rake/migrations_test.rb64
-rw-r--r--railties/test/application/rake/notes_test.rb6
-rw-r--r--railties/test/application/rake_test.rb74
-rw-r--r--railties/test/application/runner_test.rb10
-rw-r--r--railties/test/application/test_runner_test.rb109
-rw-r--r--railties/test/application/test_test.rb2
-rw-r--r--railties/test/code_statistics_test.rb15
-rw-r--r--railties/test/commands/server_test.rb9
-rw-r--r--railties/test/generators/actions_test.rb8
-rw-r--r--railties/test/generators/api_app_generator_test.rb1
-rw-r--r--railties/test/generators/app_generator_test.rb35
-rw-r--r--railties/test/generators/migration_generator_test.rb12
-rw-r--r--railties/test/generators/model_generator_test.rb20
-rw-r--r--railties/test/generators/plugin_generator_test.rb23
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb8
-rw-r--r--railties/test/generators/scaffold_generator_test.rb8
-rw-r--r--tasks/release.rb2
-rw-r--r--version.rb2
422 files changed, 5724 insertions, 2073 deletions
diff --git a/.travis.yml b/.travis.yml
index f1031a3750..ae38617b99 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,6 +25,7 @@ env:
- "GEM=guides"
rvm:
- 2.2.4
+ - 2.3.0
- ruby-head
matrix:
allow_failures:
@@ -48,4 +49,4 @@ services:
- redis
- rabbitmq
addons:
- postgresql: "9.3"
+ postgresql: "9.4"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6871664a22..961b48733c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/rails/rails/issues).
-* If unable to find an open issue addressing the problem, [open a new one](https://github.com/rails/rails/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
+* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/rails/rails/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
* If possible, use the relevant bug report templates to create the issue. Simply copy the content of the appropriate template into a .rb file, make the necessary changes to demonstrate the issue, and **paste the content into the issue description**:
* [**Active Record** (models, database) issues](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb)
diff --git a/Gemfile b/Gemfile
index 51e7c0043c..b69c05025f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -48,7 +48,7 @@ group :job do
gem 'resque', require: false
gem 'resque-scheduler', require: false
gem 'sidekiq', require: false
- gem 'sucker_punch', require: false
+ gem 'sucker_punch', '< 2.0', require: false
gem 'delayed_job', require: false
gem 'queue_classic', github: "QueueClassic/queue_classic", branch: 'master', require: false, platforms: :ruby
gem 'sneakers', require: false
@@ -63,6 +63,9 @@ end
# Action Cable
group :cable do
gem 'puma', require: false
+
+ gem 'em-hiredis', require: false
+ gem 'redis', require: false
end
# Add your own local bundler stuff.
diff --git a/Gemfile.lock b/Gemfile.lock
index 4e8c1e1744..a7b1daaef4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -29,65 +29,61 @@ GIT
PATH
remote: .
specs:
- actioncable (5.0.0.beta1)
- actionpack (= 5.0.0.beta1)
- celluloid (~> 0.17.2)
+ actioncable (5.0.0.beta1.1)
+ actionpack (= 5.0.0.beta1.1)
coffee-rails (~> 4.1.0)
- em-hiredis (~> 0.3.0)
- faye-websocket (~> 0.10.0)
- redis (~> 3.0)
+ nio4r (~> 1.2)
websocket-driver (~> 0.6.1)
- actionmailer (5.0.0.beta1)
- actionpack (= 5.0.0.beta1)
- actionview (= 5.0.0.beta1)
- activejob (= 5.0.0.beta1)
+ actionmailer (5.0.0.beta1.1)
+ actionpack (= 5.0.0.beta1.1)
+ actionview (= 5.0.0.beta1.1)
+ activejob (= 5.0.0.beta1.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (5.0.0.beta1)
- actionview (= 5.0.0.beta1)
- activesupport (= 5.0.0.beta1)
+ actionpack (5.0.0.beta1.1)
+ actionview (= 5.0.0.beta1.1)
+ activesupport (= 5.0.0.beta1.1)
rack (~> 2.x)
rack-test (~> 0.6.3)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.0.0.beta1)
- activesupport (= 5.0.0.beta1)
+ actionview (5.0.0.beta1.1)
+ activesupport (= 5.0.0.beta1.1)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (5.0.0.beta1)
- activesupport (= 5.0.0.beta1)
+ activejob (5.0.0.beta1.1)
+ activesupport (= 5.0.0.beta1.1)
globalid (>= 0.3.6)
- activemodel (5.0.0.beta1)
- activesupport (= 5.0.0.beta1)
- builder (~> 3.1)
- activerecord (5.0.0.beta1)
- activemodel (= 5.0.0.beta1)
- activesupport (= 5.0.0.beta1)
+ activemodel (5.0.0.beta1.1)
+ activesupport (= 5.0.0.beta1.1)
+ activerecord (5.0.0.beta1.1)
+ activemodel (= 5.0.0.beta1.1)
+ activesupport (= 5.0.0.beta1.1)
arel (~> 7.0)
- activesupport (5.0.0.beta1)
+ activesupport (5.0.0.beta1.1)
concurrent-ruby (~> 1.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
method_source
minitest (~> 5.1)
tzinfo (~> 1.1)
- rails (5.0.0.beta1)
- actioncable (= 5.0.0.beta1)
- actionmailer (= 5.0.0.beta1)
- actionpack (= 5.0.0.beta1)
- actionview (= 5.0.0.beta1)
- activejob (= 5.0.0.beta1)
- activemodel (= 5.0.0.beta1)
- activerecord (= 5.0.0.beta1)
- activesupport (= 5.0.0.beta1)
+ rails (5.0.0.beta1.1)
+ actioncable (= 5.0.0.beta1.1)
+ actionmailer (= 5.0.0.beta1.1)
+ actionpack (= 5.0.0.beta1.1)
+ actionview (= 5.0.0.beta1.1)
+ activejob (= 5.0.0.beta1.1)
+ activemodel (= 5.0.0.beta1.1)
+ activerecord (= 5.0.0.beta1.1)
+ activesupport (= 5.0.0.beta1.1)
bundler (>= 1.3.0, < 2.0)
- railties (= 5.0.0.beta1)
+ railties (= 5.0.0.beta1.1)
sprockets-rails (>= 2.0.0)
- railties (5.0.0.beta1)
- actionpack (= 5.0.0.beta1)
- activesupport (= 5.0.0.beta1)
+ railties (5.0.0.beta1.1)
+ actionpack (= 5.0.0.beta1.1)
+ activesupport (= 5.0.0.beta1.1)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -146,11 +142,8 @@ GEM
eventmachine (~> 1.0)
hiredis (~> 0.5.0)
erubis (2.7.0)
- eventmachine (1.0.8)
+ eventmachine (1.0.9.1)
execjs (2.6.0)
- faye-websocket (0.10.2)
- eventmachine (>= 0.12.0)
- websocket-driver (>= 0.5.1)
ffi (1.9.10)
ffi (1.9.10-x64-mingw32)
ffi (1.9.10-x86-mingw32)
@@ -188,6 +181,7 @@ GEM
mysql2 (0.4.2)
mysql2 (0.4.2-x64-mingw32)
mysql2 (0.4.2-x86-mingw32)
+ nio4r (1.2.0)
nokogiri (1.6.7.1)
mini_portile2 (~> 2.0.0.rc2)
nokogiri (1.6.7.1-x64-mingw32)
@@ -309,6 +303,7 @@ DEPENDENCIES
dalli (>= 2.2.1)
delayed_job
delayed_job_active_record
+ em-hiredis
jquery-rails
json
kindlerb (= 0.1.1)
@@ -329,6 +324,7 @@ DEPENDENCIES
rails!
rake (>= 10.3)
redcarpet (~> 3.2.3)
+ redis
resque
resque-scheduler
sass!
@@ -338,7 +334,7 @@ DEPENDENCIES
sneakers
sqlite3 (~> 1.3.6)
stackprof
- sucker_punch
+ sucker_punch (< 2.0)
turbolinks
tzinfo-data
uglifier (>= 1.3.0)
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 80bb1e4be2..8422265fdd 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-5.0.0.beta1
+5.0.0.beta1.1
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index 22126d82b7..a33b7b6b4b 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,3 +1,12 @@
+* Create notion of an `ActionCable::SubscriptionAdapter`.
+ Separate out Redis functionality into
+ `ActionCable::SubscriptionAdapter::Redis`, and add a
+ PostgreSQL adapter as well. Configuration file for
+ ActionCable was changed from`config/redis/cable.yml` to
+ `config/cable.yml`.
+
+ *Jon Moss*
+
## Rails 5.0.0.beta1 (December 18, 2015) ##
* Added to Rails!
diff --git a/actioncable/MIT-LICENSE b/actioncable/MIT-LICENSE
index a4910677eb..27a17cf41b 100644
--- a/actioncable/MIT-LICENSE
+++ b/actioncable/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015 Basecamp, LLC
+Copyright (c) 2015-2016 Basecamp, LLC
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actioncable/README.md b/actioncable/README.md
index c7420d48bc..cad71ddf94 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -66,6 +66,13 @@ end
Here `identified_by` is a connection identifier that can be used to find the specific connection again or later.
Note that anything marked as an identifier will automatically create a delegate by the same name on any channel instances created off the connection.
+This relies on the fact that you will already have handled authentication of the user, and
+that a successful authentication sets a signed cookie with the `user_id`. This cookie is then
+automatically sent to the connection instance when a new connection is attempted, and you
+use that to set the `current_user`. By identifying the connection by this same current_user,
+you're also ensuring that you can later retrieve all open connections by a given user (and
+potentially disconnect them all if the user is deleted or deauthorized).
+
Then you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
shared logic between your channels.
@@ -77,13 +84,6 @@ module ApplicationCable
end
```
-This relies on the fact that you will already have handled authentication of the user, and
-that a successful authentication sets a signed cookie with the `user_id`. This cookie is then
-automatically sent to the connection instance when a new connection is attempted, and you
-use that to set the `current_user`. By identifying the connection by this same current_user,
-you're also ensuring that you can later retrieve all open connections by a given user (and
-potentially disconnect them all if the user is deleted or deauthorized).
-
The client-side needs to setup a consumer instance of this connection. That's done like so:
```coffeescript
@@ -298,11 +298,12 @@ See the [rails/actioncable-examples](http://github.com/rails/actioncable-example
## Configuration
-Action Cable has two required configurations: the Redis connection and specifying allowed request origins.
+Action Cable has three required configurations: the Redis connection, allowed request origins, and the cable server url (which can optionally be set on the client side).
### Redis
-By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/redis/cable.yml')`. The file must follow the following format:
+By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/cable.yml')`.
+This file must specify a Redis url for each Rails environment. It may use the following format:
```yaml
production: &production
@@ -312,11 +313,10 @@ development: &development
test: *development
```
-This format allows you to specify one configuration per Rails environment. You can also change the location of the Redis config file in
-a Rails initializer with something like:
+You can also change the location of the Redis config file in a Rails initializer with something like:
```ruby
-Rails.application.paths.add "config/redis/cable", with: "somewhere/else/cable.yml"
+Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml"
```
### Allowed Request Origins
@@ -327,29 +327,31 @@ Action Cable will only accept requests from specified origins, which are passed
ActionCable.server.config.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
```
+When running in the development environment, this defaults to "http://localhost:3000".
+
To disable and allow requests from any origin:
```ruby
ActionCable.server.config.disable_request_forgery_protection = true
```
-By default, Action Cable allows all requests from localhost:3000 when running in the development environment.
+### Consumer Configuration
-### Other Configurations
+Once you have decided how to run your cable server (see below), you must provide the server url (or path) to your client-side setup.
+There are two ways you can do this.
-The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
+The first is to simply pass it in when creating your consumer. For a standalone server,
+this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
+something like: `App.cable = ActionCable.createConsumer("/cable")`.
-```ruby
-ActionCable.server.config.log_tags = [
- -> request { request.env['bc.account_id'] || "no-account" },
- :action_cable,
- -> request { request.uuid }
-]
-```
+The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
+This uses a url or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
-Your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
+This method is especially useful if your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
for your ActionCable server, but development might remain http and use the ws scheme. You might use localhost in development and your
-domain in production. In any case, to vary the websocket url between environments, add the following configuration to each environment:
+domain in production.
+
+In any case, to vary the websocket url between environments, add the following configuration to each environment:
```ruby
config.action_cable.url = "ws://example.com:28080"
@@ -367,6 +369,18 @@ And finally, create your consumer like so:
App.cable = ActionCable.createConsumer()
```
+### Other Configurations
+
+The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
+
+```ruby
+ActionCable.server.config.log_tags = [
+ -> request { request.env['bc.account_id'] || "no-account" },
+ :action_cable,
+ -> request { request.uuid }
+]
+```
+
For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 100, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute.
@@ -394,8 +408,7 @@ Then you start the server using a binstub in bin/cable ala:
bundle exec puma -p 28080 cable/config.ru
```
-The above will start a cable server on port 28080. Remember to point your client-side setup against that using something like:
-`App.cable = ActionCable.createConsumer("ws://basecamp.dev:28080")`.
+The above will start a cable server on port 28080.
### In app
@@ -408,8 +421,6 @@ Example::Application.routes.draw do
end
```
-You can use `App.cable = ActionCable.createConsumer()` to connect to the cable server if `action_cable_meta_tag` is included in the layout. A custom path is specified as first argument to `createConsumer` (e.g. `App.cable = ActionCable.createConsumer("/websocket")`).
-
For every instance of your server you create and for every worker your server spawns, you will also have a new instance of ActionCable, but the use of Redis keeps messages synced across connections.
### Notes
@@ -427,16 +438,15 @@ messages back and forth over the WebSocket cable connection. This dependency may
be alleviated in the future, but for the moment that's what it is. So be sure to have
Redis installed and running.
-The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [celluloid](https://github.com/celluloid/celluloid).
+The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
## Deployment
-Action Cable is powered by a combination of EventMachine and threads. The
-framework plumbing needed for connection handling is handled in the
-EventMachine loop, but the actual channel, user-specified, work is handled
-in a normal Ruby thread. This means you can use all your regular Rails models
-with no problem, as long as you haven't committed any thread-safety sins.
+Action Cable is powered by a combination of websockets and threads. All of the
+connection management is handled internally by utilizing Ruby’s native thread
+support, which means you can use all your regular Rails models with no problems
+as long as you haven’t committed any thread-safety sins.
But this also means that Action Cable needs to run in its own server process.
So you'll have one set of server processes for your normal web work, and another
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index 74c21bd24d..14f968f1ef 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -21,12 +21,12 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
s.add_dependency 'coffee-rails', '~> 4.1.0'
- s.add_dependency 'faye-websocket', '~> 0.10.0'
+ s.add_dependency 'nio4r', '~> 1.2'
s.add_dependency 'websocket-driver', '~> 0.6.1'
- s.add_dependency 'celluloid', '~> 0.17.2'
- s.add_dependency 'em-hiredis', '~> 0.3.0'
- s.add_dependency 'redis', '~> 3.0'
- s.add_development_dependency 'puma'
+ s.add_development_dependency 'em-hiredis', '~> 0.3.0'
s.add_development_dependency 'mocha'
+ s.add_development_dependency 'pg'
+ s.add_development_dependency 'puma'
+ s.add_development_dependency 'redis', '~> 3.0'
end
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index f27698765c..1dc66ef3ad 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2015 Basecamp, LLC
+# Copyright (c) 2015-2016 Basecamp, LLC
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -47,4 +47,5 @@ module ActionCable
autoload :Connection
autoload :Channel
autoload :RemoteConnections
+ autoload :SubscriptionAdapter
end
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index ce9d62635c..88cdc1cab1 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -133,8 +133,8 @@ module ActionCable
@identifier = identifier
@params = params
- # When a channel is streaming via redis pubsub, we want to delay the confirmation
- # transmission until redis pubsub subscription is confirmed.
+ # When a channel is streaming via pubsub, we want to delay the confirmation
+ # transmission until pubsub subscription is confirmed.
@defer_subscription_confirmation = false
@reject_subscription = nil
diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb
index 25fe8e5e54..56597d02d7 100644
--- a/actioncable/lib/action_cable/channel/periodic_timers.rb
+++ b/actioncable/lib/action_cable/channel/periodic_timers.rb
@@ -27,14 +27,14 @@ module ActionCable
def start_periodic_timers
self.class.periodic_timers.each do |callback, options|
- active_periodic_timers << EventMachine::PeriodicTimer.new(options[:every]) do
- connection.worker_pool.async.run_periodic_timer(self, callback)
+ active_periodic_timers << Concurrent::TimerTask.new(execution_interval: options[:every]) do
+ connection.worker_pool.async_run_periodic_timer(self, callback)
end
end
end
def stop_periodic_timers
- active_periodic_timers.each { |timer| timer.cancel }
+ active_periodic_timers.each { |timer| timer.shutdown }
end
end
end
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index b5ffa17f72..a26373e387 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -75,11 +75,11 @@ module ActionCable
callback ||= default_stream_callback(broadcasting)
streams << [ broadcasting, callback ]
- EM.next_tick do
- pubsub.subscribe(broadcasting, &callback).callback do |reply|
+ Concurrent.global_io_executor.post do
+ pubsub.subscribe(broadcasting, callback, lambda do
transmit_subscription_confirmation
logger.info "#{self.class.name} is streaming from #{broadcasting}"
- end
+ end)
end
end
@@ -92,7 +92,7 @@ module ActionCable
def stop_all_streams
streams.each do |broadcasting, callback|
- pubsub.unsubscribe_proc broadcasting, callback
+ pubsub.unsubscribe broadcasting, callback
logger.info "#{self.class.name} stopped streaming from #{broadcasting}"
end.clear
end
diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb
index b672e00682..902efb07e2 100644
--- a/actioncable/lib/action_cable/connection.rb
+++ b/actioncable/lib/action_cable/connection.rb
@@ -5,12 +5,15 @@ module ActionCable
eager_autoload do
autoload :Authorization
autoload :Base
+ autoload :ClientSocket
autoload :Identification
autoload :InternalChannel
autoload :MessageBuffer
- autoload :WebSocket
+ autoload :Stream
+ autoload :StreamEventLoop
autoload :Subscriptions
autoload :TaggedLoggerProxy
+ autoload :WebSocket
end
end
end
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index f7b18a85ae..0016d1a1a4 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -48,21 +48,19 @@ module ActionCable
include InternalChannel
include Authorization
- attr_reader :server, :env, :subscriptions
- delegate :worker_pool, :pubsub, to: :server
-
- attr_reader :logger
+ attr_reader :server, :env, :subscriptions, :logger
+ delegate :stream_event_loop, :worker_pool, :pubsub, to: :server
def initialize(server, env)
@server, @env = server, env
@logger = new_tagged_logger
- @websocket = ActionCable::Connection::WebSocket.new(env)
+ @websocket = ActionCable::Connection::WebSocket.new(env, self, stream_event_loop)
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
@message_buffer = ActionCable::Connection::MessageBuffer.new(self)
- @_internal_redis_subscriptions = nil
+ @_internal_subscriptions = nil
@started_at = Time.now
end
@@ -72,10 +70,6 @@ module ActionCable
logger.info started_request_message
if websocket.possible? && allow_request_origin?
- websocket.on(:open) { |event| send_async :on_open }
- websocket.on(:message) { |event| on_message event.data }
- websocket.on(:close) { |event| send_async :on_close }
-
respond_to_successful_request
else
respond_to_invalid_request
@@ -105,7 +99,7 @@ module ActionCable
# Invoke a method on the connection asynchronously through the pool of thread workers.
def send_async(method, *arguments)
- worker_pool.async.invoke(self, method, *arguments)
+ worker_pool.async_invoke(self, method, *arguments)
end
# Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`.
@@ -123,6 +117,21 @@ module ActionCable
transmit ActiveSupport::JSON.encode(identifier: ActionCable::INTERNAL[:identifiers][:ping], message: Time.now.to_i)
end
+ def on_open # :nodoc:
+ send_async :handle_open
+ end
+
+ def on_message(message) # :nodoc:
+ message_buffer.append message
+ end
+
+ def on_error(message) # :nodoc:
+ # ignore
+ end
+
+ def on_close # :nodoc:
+ send_async :handle_close
+ end
protected
# The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
@@ -138,13 +147,11 @@ module ActionCable
request.cookie_jar
end
-
- protected
attr_reader :websocket
attr_reader :message_buffer
private
- def on_open
+ def handle_open
connect if respond_to?(:connect)
subscribe_to_internal_channel
beat
@@ -155,11 +162,7 @@ module ActionCable
respond_to_invalid_request
end
- def on_message(message)
- message_buffer.append message
- end
-
- def on_close
+ def handle_close
logger.info finished_request_message
server.remove_connection(self)
@@ -170,7 +173,6 @@ module ActionCable
disconnect if respond_to?(:disconnect)
end
-
def allow_request_origin?
return true if server.config.disable_request_forgery_protection
@@ -193,7 +195,6 @@ module ActionCable
[ 404, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ]
end
-
# Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags.
def new_tagged_logger
TaggedLoggerProxy.new server.logger,
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
new file mode 100644
index 0000000000..62dd753646
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -0,0 +1,152 @@
+require 'websocket/driver'
+
+module ActionCable
+ module Connection
+ #--
+ # This class is heavily based on faye-websocket-ruby
+ #
+ # Copyright (c) 2010-2015 James Coglan
+ class ClientSocket # :nodoc:
+ def self.determine_url(env)
+ scheme = secure_request?(env) ? 'wss:' : 'ws:'
+ "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
+ end
+
+ def self.secure_request?(env)
+ return true if env['HTTPS'] == 'on'
+ return true if env['HTTP_X_FORWARDED_SSL'] == 'on'
+ return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https'
+ return true if env['HTTP_X_FORWARDED_PROTO'] == 'https'
+ return true if env['rack.url_scheme'] == 'https'
+
+ return false
+ end
+
+ CONNECTING = 0
+ OPEN = 1
+ CLOSING = 2
+ CLOSED = 3
+
+ attr_reader :env, :url
+
+ def initialize(env, event_target, stream_event_loop)
+ @env = env
+ @event_target = event_target
+ @stream_event_loop = stream_event_loop
+
+ @url = ClientSocket.determine_url(@env)
+
+ @driver = @driver_started = nil
+
+ @ready_state = CONNECTING
+
+ # The driver calls +env+, +url+, and +write+
+ @driver = ::WebSocket::Driver.rack(self)
+
+ @driver.on(:open) { |e| open }
+ @driver.on(:message) { |e| receive_message(e.data) }
+ @driver.on(:close) { |e| begin_close(e.reason, e.code) }
+ @driver.on(:error) { |e| emit_error(e.message) }
+
+ @stream = ActionCable::Connection::Stream.new(@stream_event_loop, self)
+
+ if callback = @env['async.callback']
+ callback.call([101, {}, @stream])
+ end
+ end
+
+ def start_driver
+ return if @driver.nil? || @driver_started
+ @driver_started = true
+ @driver.start
+ end
+
+ def rack_response
+ start_driver
+ [ -1, {}, [] ]
+ end
+
+ def write(data)
+ @stream.write(data)
+ end
+
+ def transmit(message)
+ return false if @ready_state > OPEN
+ case message
+ when Numeric then @driver.text(message.to_s)
+ when String then @driver.text(message)
+ when Array then @driver.binary(message)
+ else false
+ end
+ end
+
+ def close(code = nil, reason = nil)
+ code ||= 1000
+ reason ||= ''
+
+ unless code == 1000 or (code >= 3000 and code <= 4999)
+ raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
+ "The code must be either 1000, or between 3000 and 4999. " +
+ "#{code} is neither."
+ end
+
+ @ready_state = CLOSING unless @ready_state == CLOSED
+ @driver.close(reason, code)
+ end
+
+ def parse(data)
+ @driver.parse(data)
+ end
+
+ def client_gone
+ finalize_close
+ end
+
+ def alive?
+ @ready_state == OPEN
+ end
+
+ private
+ def open
+ return unless @ready_state == CONNECTING
+ @ready_state = OPEN
+
+ @event_target.on_open
+ end
+
+ def receive_message(data)
+ return unless @ready_state == OPEN
+
+ @event_target.on_message(data)
+ end
+
+ def emit_error(message)
+ return if @ready_state >= CLOSING
+
+ @event_target.on_error(message)
+ end
+
+ def begin_close(reason, code)
+ return if @ready_state == CLOSED
+ @ready_state = CLOSING
+ @close_params = [reason, code]
+
+ if @stream
+ @stream.shutdown
+ else
+ finalize_close
+ end
+ end
+
+ def finalize_close
+ return if @ready_state == CLOSED
+ @ready_state = CLOSED
+
+ reason = @close_params ? @close_params[0] : ''
+ code = @close_params ? @close_params[1] : 1006
+
+ @event_target.on_close(code, reason)
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb
index 2d75ff8d6d..885ff3f102 100644
--- a/actioncable/lib/action_cable/connection/identification.rb
+++ b/actioncable/lib/action_cable/connection/identification.rb
@@ -11,7 +11,7 @@ module ActionCable
end
class_methods do
- # Mark a key as being a connection identifier index that can then used to find the specific connection again later.
+ # Mark a key as being a connection identifier index that can then be used to find the specific connection again later.
# Common identifiers are current_user and current_account, but could be anything really.
#
# Note that anything marked as an identifier will automatically create a delegate by the same name on any
diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb
index c065a24ab7..27826792b3 100644
--- a/actioncable/lib/action_cable/connection/internal_channel.rb
+++ b/actioncable/lib/action_cable/connection/internal_channel.rb
@@ -5,24 +5,24 @@ module ActionCable
extend ActiveSupport::Concern
private
- def internal_redis_channel
+ def internal_channel
"action_cable/#{connection_identifier}"
end
def subscribe_to_internal_channel
if connection_identifier.present?
callback = -> (message) { process_internal_message(message) }
- @_internal_redis_subscriptions ||= []
- @_internal_redis_subscriptions << [ internal_redis_channel, callback ]
+ @_internal_subscriptions ||= []
+ @_internal_subscriptions << [ internal_channel, callback ]
- EM.next_tick { pubsub.subscribe(internal_redis_channel, &callback) }
+ Concurrent.global_io_executor.post { pubsub.subscribe(internal_channel, callback) }
logger.info "Registered connection (#{connection_identifier})"
end
end
def unsubscribe_from_internal_channel
- if @_internal_redis_subscriptions.present?
- @_internal_redis_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe_proc(channel, callback) } }
+ if @_internal_subscriptions.present?
+ @_internal_subscriptions.each { |channel, callback| Concurrent.global_io_executor.post { pubsub.unsubscribe(channel, callback) } }
end
end
diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb
new file mode 100644
index 0000000000..ace250cd16
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/stream.rb
@@ -0,0 +1,59 @@
+module ActionCable
+ module Connection
+ #--
+ # This class is heavily based on faye-websocket-ruby
+ #
+ # Copyright (c) 2010-2015 James Coglan
+ class Stream
+ def initialize(event_loop, socket)
+ @event_loop = event_loop
+ @socket_object = socket
+ @stream_send = socket.env['stream.send']
+
+ @rack_hijack_io = nil
+
+ hijack_rack_socket
+ end
+
+ def each(&callback)
+ @stream_send ||= callback
+ end
+
+ def close
+ shutdown
+ @socket_object.client_gone
+ end
+
+ def shutdown
+ clean_rack_hijack
+ end
+
+ def write(data)
+ return @rack_hijack_io.write(data) if @rack_hijack_io
+ return @stream_send.call(data) if @stream_send
+ rescue EOFError
+ @socket_object.client_gone
+ end
+
+ def receive(data)
+ @socket_object.parse(data)
+ end
+
+ private
+ def hijack_rack_socket
+ return unless @socket_object.env['rack.hijack']
+
+ @socket_object.env['rack.hijack'].call
+ @rack_hijack_io = @socket_object.env['rack.hijack_io']
+
+ @event_loop.attach(@rack_hijack_io, self)
+ end
+
+ def clean_rack_hijack
+ return unless @rack_hijack_io
+ @event_loop.detach(@rack_hijack_io, self)
+ @rack_hijack_io = nil
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb
new file mode 100644
index 0000000000..f773814973
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/stream_event_loop.rb
@@ -0,0 +1,68 @@
+require 'nio'
+
+module ActionCable
+ module Connection
+ class StreamEventLoop
+ def initialize
+ @nio = NIO::Selector.new
+ @map = {}
+ @stopping = false
+ @todo = Queue.new
+
+ Thread.new do
+ Thread.current.abort_on_exception = true
+ run
+ end
+ end
+
+ def attach(io, stream)
+ @todo << lambda do
+ @map[io] = stream
+ @nio.register(io, :r)
+ end
+ @nio.wakeup
+ end
+
+ def detach(io, stream)
+ @todo << lambda do
+ @nio.deregister(io)
+ @map.delete io
+ end
+ @nio.wakeup
+ end
+
+ def stop
+ @stopping = true
+ @nio.wakeup
+ end
+
+ def run
+ loop do
+ if @stopping
+ @nio.close
+ break
+ end
+
+ until @todo.empty?
+ @todo.pop(true).call
+ end
+
+ if monitors = @nio.select
+ monitors.each do |monitor|
+ io = monitor.io
+ stream = @map[io]
+
+ begin
+ stream.receive io.read_nonblock(4096)
+ rescue IO::WaitReadable
+ next
+ rescue EOFError
+ stream.close
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index 65d6634bb0..d7f95e6a62 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -49,7 +49,6 @@ module ActionCable
find(data).perform_action ActiveSupport::JSON.decode(data['data'])
end
-
def identifiers
subscriptions.keys
end
diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb
index 670d5690ae..5e89fb9b72 100644
--- a/actioncable/lib/action_cable/connection/web_socket.rb
+++ b/actioncable/lib/action_cable/connection/web_socket.rb
@@ -1,13 +1,11 @@
-require 'faye/websocket'
+require 'websocket/driver'
module ActionCable
module Connection
- # Decorate the Faye::WebSocket with helpers we need.
+ # Wrap the real socket to minimize the externally-presented API
class WebSocket
- delegate :rack_response, :close, :on, to: :websocket
-
- def initialize(env)
- @websocket = Faye::WebSocket.websocket?(env) ? Faye::WebSocket.new(env) : nil
+ def initialize(env, event_target, stream_event_loop)
+ @websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, stream_event_loop) : nil
end
def possible?
@@ -15,11 +13,19 @@ module ActionCable
end
def alive?
- websocket && websocket.ready_state == Faye::WebSocket::API::OPEN
+ websocket && websocket.alive?
end
def transmit(data)
- websocket.send data
+ websocket.transmit data
+ end
+
+ def close
+ websocket.close
+ end
+
+ def rack_response
+ websocket.rack_response
end
protected
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index 2d3caa5b0a..f5e233e091 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -24,11 +24,11 @@ module ActionCable
options = app.config.action_cable
options.allowed_request_origins ||= "http://localhost:3000" if ::Rails.env.development?
- app.paths.add "config/redis/cable", with: "config/redis/cable.yml"
+ app.paths.add "config/cable", with: "config/cable.yml"
ActiveSupport.on_load(:action_cable) do
- if (redis_cable_path = Pathname.new(app.config.paths["config/redis/cable"].first)).exist?
- self.redis = Rails.application.config_for(redis_cable_path).with_indifferent_access
+ if (config_path = Pathname.new(app.config.paths["config/cable"].first)).exist?
+ self.cable = Rails.application.config_for(config_path).with_indifferent_access
end
options.each { |k,v| send("#{k}=", v) }
diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb
index b1286aea6f..c652fb91ae 100644
--- a/actioncable/lib/action_cable/gem_version.rb
+++ b/actioncable/lib/action_cable/gem_version.rb
@@ -8,7 +8,7 @@ module ActionCable
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actioncable/lib/action_cable/process/logging.rb b/actioncable/lib/action_cable/process/logging.rb
deleted file mode 100644
index 72b1a080d1..0000000000
--- a/actioncable/lib/action_cable/process/logging.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'action_cable/server'
-require 'eventmachine'
-require 'celluloid'
-
-EM.error_handler do |e|
- puts "Error raised inside the event loop: #{e.message}"
- puts e.backtrace.join("\n")
-end
-
-Celluloid.logger = ActionCable.server.logger
diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb
index 1230d905ad..aa2fc95d2f 100644
--- a/actioncable/lib/action_cable/remote_connections.rb
+++ b/actioncable/lib/action_cable/remote_connections.rb
@@ -39,7 +39,7 @@ module ActionCable
# Uses the internal channel to disconnect the connection.
def disconnect
- server.broadcast internal_redis_channel, type: 'disconnect'
+ server.broadcast internal_channel, type: 'disconnect'
end
# Returns all the identifiers that were applied to this connection.
diff --git a/actioncable/lib/action_cable/server.rb b/actioncable/lib/action_cable/server.rb
index a2a89d5f1e..bd6a3826a3 100644
--- a/actioncable/lib/action_cable/server.rb
+++ b/actioncable/lib/action_cable/server.rb
@@ -1,7 +1,3 @@
-require 'eventmachine'
-EventMachine.epoll if EventMachine.epoll?
-EventMachine.kqueue if EventMachine.kqueue?
-
module ActionCable
module Server
extend ActiveSupport::Autoload
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index 740e4b301e..b00abd208c 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -1,8 +1,3 @@
-# FIXME: Cargo culted fix from https://github.com/celluloid/celluloid-pool/issues/10
-require 'celluloid/current'
-
-require 'em-hiredis'
-
module ActionCable
module Server
# A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but
@@ -37,9 +32,13 @@ module ActionCable
@remote_connections ||= RemoteConnections.new(self)
end
+ def stream_event_loop
+ @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new
+ end
+
# The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size.
def worker_pool
- @worker_pool ||= ActionCable::Server::Worker.pool(size: config.worker_pool_size)
+ @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size)
end
# Requires and returns a hash of all the channel class constants keyed by name.
@@ -50,20 +49,9 @@ module ActionCable
end
end
- # The redis pubsub adapter used for all streams/broadcasting.
+ # Adapter used for all streams/broadcasting.
def pubsub
- @pubsub ||= redis.pubsub
- end
-
- # The EventMachine Redis instance used by the pubsub adapter.
- def redis
- @redis ||= EM::Hiredis.connect(config.redis[:url]).tap do |redis|
- redis.on(:reconnect_failed) do
- logger.info "[ActionCable] Redis reconnect failed."
- # logger.info "[ActionCable] Redis reconnected. Closing all the open connections."
- # @connections.map &:close
- end
- end
+ @pubsub ||= config.pubsub_adapter.new(self)
end
# All the identifiers applied to the connection class associated with this server.
diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb
index c759239a0e..4a26ed9269 100644
--- a/actioncable/lib/action_cable/server/broadcasting.rb
+++ b/actioncable/lib/action_cable/server/broadcasting.rb
@@ -1,5 +1,3 @@
-require 'redis'
-
module ActionCable
module Server
# Broadcasting is how other parts of your application can send messages to the channel subscribers. As explained in Channel, most of the time, these
@@ -31,11 +29,6 @@ module ActionCable
Broadcaster.new(self, broadcasting)
end
- # The redis instance used for broadcasting. Not intended for direct user use.
- def broadcasting_redis
- @broadcasting_redis ||= Redis.new(config.redis)
- end
-
private
class Broadcaster
attr_reader :server, :broadcasting
@@ -46,7 +39,7 @@ module ActionCable
def broadcast(message)
server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}"
- server.broadcasting_redis.publish broadcasting, ActiveSupport::JSON.encode(message)
+ server.pubsub.broadcast broadcasting, ActiveSupport::JSON.encode(message)
end
end
end
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index 935133cbba..9a248933c4 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -5,9 +5,9 @@ module ActionCable
class Configuration
attr_accessor :logger, :log_tags
attr_accessor :connection_class, :worker_pool_size
- attr_accessor :redis, :channels_path
+ attr_accessor :channel_load_paths
attr_accessor :disable_request_forgery_protection, :allowed_request_origins
- attr_accessor :url
+ attr_accessor :cable, :url
def initialize
@log_tags = []
@@ -15,13 +15,15 @@ module ActionCable
@connection_class = ApplicationCable::Connection
@worker_pool_size = 100
- @channels_path = Rails.root.join('app/channels')
+ @channel_load_paths = [Rails.root.join('app/channels')]
@disable_request_forgery_protection = false
end
def channel_paths
- @channels ||= Dir["#{channels_path}/**/*_channel.rb"]
+ @channel_paths ||= channel_load_paths.flat_map do |path|
+ Dir["#{path}/**/*_channel.rb"]
+ end
end
def channel_class_names
@@ -29,7 +31,25 @@ module ActionCable
Pathname.new(channel_path).basename.to_s.split('.').first.camelize
end
end
+
+ # Returns constant of subscription adapter specified in config/cable.yml.
+ # If the adapter cannot be found, this will default to the Redis adapter.
+ # Also makes sure proper dependencies are required.
+ def pubsub_adapter
+ adapter = (cable.fetch('adapter') { 'redis' })
+ path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
+ begin
+ require path_to_adapter
+ rescue Gem::LoadError => e
+ raise Gem::LoadError, "Specified '#{adapter}' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by Action Cable)."
+ rescue LoadError => e
+ raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/cable.yml is valid. If you use an adapter other than 'postgresql' or 'redis' add the necessary adapter gem to the Gemfile.", e.backtrace
+ end
+
+ adapter = adapter.camelize
+ adapter = 'PostgreSQL' if adapter == 'Postgresql'
+ "ActionCable::SubscriptionAdapter::#{adapter}".constantize
+ end
end
end
end
-
diff --git a/actioncable/lib/action_cable/server/connections.rb b/actioncable/lib/action_cable/server/connections.rb
index 47dcea8c20..8671dd5ebd 100644
--- a/actioncable/lib/action_cable/server/connections.rb
+++ b/actioncable/lib/action_cable/server/connections.rb
@@ -22,11 +22,9 @@ module ActionCable
# then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically
# disconnect.
def setup_heartbeat_timer
- EM.next_tick do
- @heartbeat_timer ||= EventMachine.add_periodic_timer(BEAT_INTERVAL) do
- EM.next_tick { connections.map(&:beat) }
- end
- end
+ @heartbeat_timer ||= Concurrent::TimerTask.new(execution_interval: BEAT_INTERVAL) do
+ Concurrent.global_io_executor.post { connections.map(&:beat) }
+ end.tap(&:execute)
end
def open_connections_statistics
diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb
index e063b2a2e1..3b6c6d44a1 100644
--- a/actioncable/lib/action_cable/server/worker.rb
+++ b/actioncable/lib/action_cable/server/worker.rb
@@ -1,39 +1,68 @@
-require 'celluloid'
require 'active_support/callbacks'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
+require 'concurrent'
module ActionCable
module Server
# Worker used by Server.send_async to do connection work in threads. Only for internal use.
class Worker
include ActiveSupport::Callbacks
- include Celluloid
- attr_reader :connection
+ thread_mattr_accessor :connection
define_callbacks :work
include ActiveRecordConnectionManagement
+ def initialize(max_size: 5)
+ @pool = Concurrent::ThreadPoolExecutor.new(
+ min_threads: 1,
+ max_threads: max_size,
+ max_queue: 0,
+ )
+ end
+
+ def async_invoke(receiver, method, *args)
+ @pool.post do
+ invoke(receiver, method, *args)
+ end
+ end
+
def invoke(receiver, method, *args)
- @connection = receiver
+ begin
+ self.connection = receiver
- run_callbacks :work do
- receiver.send method, *args
+ run_callbacks :work do
+ receiver.send method, *args
+ end
+ rescue Exception => e
+ logger.error "There was an exception - #{e.class}(#{e.message})"
+ logger.error e.backtrace.join("\n")
+
+ receiver.handle_exception if receiver.respond_to?(:handle_exception)
+ ensure
+ self.connection = nil
end
- rescue Exception => e
- logger.error "There was an exception - #{e.class}(#{e.message})"
- logger.error e.backtrace.join("\n")
+ end
- receiver.handle_exception if receiver.respond_to?(:handle_exception)
+ def async_run_periodic_timer(channel, callback)
+ @pool.post do
+ run_periodic_timer(channel, callback)
+ end
end
def run_periodic_timer(channel, callback)
- @connection = channel.connection
+ begin
+ self.connection = channel.connection
- run_callbacks :work do
- callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
+ run_callbacks :work do
+ callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
+ end
+ ensure
+ self.connection = nil
end
end
private
+
def logger
ActionCable.server.logger
end
diff --git a/actioncable/lib/action_cable/subscription_adapter.rb b/actioncable/lib/action_cable/subscription_adapter.rb
new file mode 100644
index 0000000000..72e62f3daf
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter.rb
@@ -0,0 +1,8 @@
+module ActionCable
+ module SubscriptionAdapter
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+ autoload :SubscriberMap
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb
new file mode 100644
index 0000000000..c88b03947a
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/async.rb
@@ -0,0 +1,22 @@
+require 'action_cable/subscription_adapter/inline'
+
+module ActionCable
+ module SubscriptionAdapter
+ class Async < Inline # :nodoc:
+ private
+ def subscriber_map
+ @subscriber_map ||= AsyncSubscriberMap.new
+ end
+
+ class AsyncSubscriberMap < SubscriberMap
+ def add_subscriber(*)
+ Concurrent.global_io_executor.post { super }
+ end
+
+ def invoke_callback(*)
+ Concurrent.global_io_executor.post { super }
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/base.rb b/actioncable/lib/action_cable/subscription_adapter/base.rb
new file mode 100644
index 0000000000..796db5ffa3
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/base.rb
@@ -0,0 +1,28 @@
+module ActionCable
+ module SubscriptionAdapter
+ class Base
+ attr_reader :logger, :server
+
+ def initialize(server)
+ @server = server
+ @logger = @server.logger
+ end
+
+ def broadcast(channel, payload)
+ raise NotImplementedError
+ end
+
+ def subscribe(channel, message_callback, success_callback = nil)
+ raise NotImplementedError
+ end
+
+ def unsubscribe(channel, message_callback)
+ raise NotImplementedError
+ end
+
+ def shutdown
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/inline.rb b/actioncable/lib/action_cable/subscription_adapter/inline.rb
new file mode 100644
index 0000000000..4a2a8d23a2
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/inline.rb
@@ -0,0 +1,26 @@
+module ActionCable
+ module SubscriptionAdapter
+ class Inline < Base # :nodoc:
+ def broadcast(channel, payload)
+ subscriber_map.broadcast(channel, payload)
+ end
+
+ def subscribe(channel, callback, success_callback = nil)
+ subscriber_map.add_subscriber(channel, callback, success_callback)
+ end
+
+ def unsubscribe(channel, callback)
+ subscriber_map.remove_subscriber(channel, callback)
+ end
+
+ def shutdown
+ # nothing to do
+ end
+
+ private
+ def subscriber_map
+ @subscriber_map ||= SubscriberMap.new
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
new file mode 100644
index 0000000000..3ce1bbed68
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
@@ -0,0 +1,101 @@
+gem 'pg', '~> 0.18'
+require 'pg'
+require 'thread'
+
+module ActionCable
+ module SubscriptionAdapter
+ class PostgreSQL < Base # :nodoc:
+ def broadcast(channel, payload)
+ with_connection do |pg_conn|
+ pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel)}, '#{pg_conn.escape_string(payload)}'")
+ end
+ end
+
+ def subscribe(channel, callback, success_callback = nil)
+ listener.add_subscriber(channel, callback, success_callback)
+ end
+
+ def unsubscribe(channel, callback)
+ listener.remove_subscriber(channel, callback)
+ end
+
+ def shutdown
+ listener.shutdown
+ end
+
+ def with_connection(&block) # :nodoc:
+ ActiveRecord::Base.connection_pool.with_connection do |ar_conn|
+ pg_conn = ar_conn.raw_connection
+
+ unless pg_conn.is_a?(PG::Connection)
+ raise 'ActiveRecord database must be Postgres in order to use the Postgres ActionCable storage adapter'
+ end
+
+ yield pg_conn
+ end
+ end
+
+ private
+ def listener
+ @listener ||= Listener.new(self)
+ end
+
+ class Listener < SubscriberMap
+ def initialize(adapter)
+ super()
+
+ @adapter = adapter
+ @queue = Queue.new
+
+ @thread = Thread.new do
+ Thread.current.abort_on_exception = true
+ listen
+ end
+ end
+
+ def listen
+ @adapter.with_connection do |pg_conn|
+ catch :shutdown do
+ loop do
+ until @queue.empty?
+ action, channel, callback = @queue.pop(true)
+
+ case action
+ when :listen
+ pg_conn.exec("LISTEN #{pg_conn.escape_identifier channel}")
+ Concurrent.global_io_executor << callback if callback
+ when :unlisten
+ pg_conn.exec("UNLISTEN #{pg_conn.escape_identifier channel}")
+ when :shutdown
+ throw :shutdown
+ end
+ end
+
+ pg_conn.wait_for_notify(1) do |chan, pid, message|
+ broadcast(chan, message)
+ end
+ end
+ end
+ end
+ end
+
+ def shutdown
+ @queue.push([:shutdown])
+ Thread.pass while @thread.alive?
+ end
+
+ def add_channel(channel, on_success)
+ @queue.push([:listen, channel, on_success])
+ end
+
+ def remove_channel(channel)
+ @queue.push([:unlisten, channel])
+ end
+
+ def invoke_callback(*)
+ Concurrent.global_io_executor.post { super }
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb
new file mode 100644
index 0000000000..a035e3988d
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb
@@ -0,0 +1,58 @@
+require 'thread'
+
+gem 'em-hiredis', '~> 0.3.0'
+gem 'redis', '~> 3.0'
+require 'em-hiredis'
+require 'redis'
+
+EventMachine.epoll if EventMachine.epoll?
+EventMachine.kqueue if EventMachine.kqueue?
+
+module ActionCable
+ module SubscriptionAdapter
+ class Redis < Base # :nodoc:
+ @@mutex = Mutex.new
+
+ def broadcast(channel, payload)
+ redis_connection_for_broadcasts.publish(channel, payload)
+ end
+
+ def subscribe(channel, message_callback, success_callback = nil)
+ redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result|
+ result.callback { |reply| success_callback.call } if success_callback
+ end
+ end
+
+ def unsubscribe(channel, message_callback)
+ redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback)
+ end
+
+ def shutdown
+ redis_connection_for_subscriptions.pubsub.close_connection
+ @redis_connection_for_subscriptions = nil
+ end
+
+ private
+ def redis_connection_for_subscriptions
+ ensure_reactor_running
+ @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis|
+ redis.on(:reconnect_failed) do
+ @logger.info "[ActionCable] Redis reconnect failed."
+ end
+ end
+ end
+
+ def redis_connection_for_broadcasts
+ @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable)
+ end
+
+ def ensure_reactor_running
+ return if EventMachine.reactor_running?
+ @@mutex.synchronize do
+ Thread.new { EventMachine.run } unless EventMachine.reactor_running?
+ Thread.pass until EventMachine.reactor_running?
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb
new file mode 100644
index 0000000000..37eed09793
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb
@@ -0,0 +1,53 @@
+module ActionCable
+ module SubscriptionAdapter
+ class SubscriberMap
+ def initialize
+ @subscribers = Hash.new { |h,k| h[k] = [] }
+ @sync = Mutex.new
+ end
+
+ def add_subscriber(channel, subscriber, on_success)
+ @sync.synchronize do
+ new_channel = !@subscribers.key?(channel)
+
+ @subscribers[channel] << subscriber
+
+ if new_channel
+ add_channel channel, on_success
+ elsif on_success
+ on_success.call
+ end
+ end
+ end
+
+ def remove_subscriber(channel, subscriber)
+ @sync.synchronize do
+ @subscribers[channel].delete(subscriber)
+
+ if @subscribers[channel].empty?
+ @subscribers.delete channel
+ remove_channel channel
+ end
+ end
+ end
+
+ def broadcast(channel, message)
+ list = @sync.synchronize { @subscribers[channel].dup }
+ list.each do |subscriber|
+ invoke_callback(subscriber, message)
+ end
+ end
+
+ def add_channel(channel, on_success)
+ on_success.call if on_success
+ end
+
+ def remove_channel(channel)
+ end
+
+ def invoke_callback(callback, message)
+ callback.call message
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/assets/javascripts/action_cable/connection.coffee b/actioncable/lib/assets/javascripts/action_cable/connection.coffee
index 2f69a9b26c..fbd7dbd35b 100644
--- a/actioncable/lib/assets/javascripts/action_cable/connection.coffee
+++ b/actioncable/lib/assets/javascripts/action_cable/connection.coffee
@@ -79,6 +79,3 @@ class ActionCable.Connection
return if @disconnected
@disconnected = true
@consumer.subscriptions.notifyAll("disconnected")
-
- toJSON: ->
- state: @getState()
diff --git a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee
index b594802be1..99b9a1c6d5 100644
--- a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee
+++ b/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee
@@ -69,11 +69,6 @@ class ActionCable.ConnectionMonitor
@consumer.connection.reopen()
, 200
- toJSON: ->
- interval = @getInterval()
- connectionIsStale = @connectionIsStale()
- {@startedAt, @stoppedAt, @pingedAt, @reconnectAttempts, connectionIsStale, interval}
-
now = ->
new Date().getTime()
diff --git a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee b/actioncable/lib/assets/javascripts/action_cable/consumer.coffee
index 5cf8978d77..fcd8d0fb6c 100644
--- a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee
+++ b/actioncable/lib/assets/javascripts/action_cable/consumer.coffee
@@ -23,9 +23,3 @@ class ActionCable.Consumer
send: (data) ->
@connection.send(data)
-
- inspect: ->
- JSON.stringify(this, null, 2)
-
- toJSON: ->
- {@url, @subscriptions, @connection, @connectionMonitor}
diff --git a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee
index 0316f76a24..ae041ffa2b 100644
--- a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee
+++ b/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee
@@ -9,7 +9,6 @@
class ActionCable.Subscriptions
constructor: (@consumer) ->
@subscriptions = []
- @history = []
create: (channelName, mixin) ->
channel = channelName
@@ -57,22 +56,9 @@ class ActionCable.Subscriptions
for subscription in subscriptions
subscription[callbackName]?(args...)
- if callbackName in ["initialized", "connected", "disconnected", "rejected"]
- {identifier} = subscription
- @record(notification: {identifier, callbackName, args})
-
sendCommand: (subscription, command) ->
{identifier} = subscription
if identifier is ActionCable.INTERNAL.identifiers.ping
@consumer.connection.isOpen()
else
@consumer.send({command, identifier})
-
- record: (data) ->
- data.time = new Date()
- @history = @history.slice(-19)
- @history.push(data)
-
- toJSON: ->
- history: @history
- identifiers: (subscription.identifier for subscription in @subscriptions)
diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb
index 1590a12f09..64f0247cd6 100644
--- a/actioncable/test/channel/periodic_timers_test.rb
+++ b/actioncable/test/channel/periodic_timers_test.rb
@@ -31,7 +31,7 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase
end
test "timer start and stop" do
- EventMachine::PeriodicTimer.expects(:new).times(2).returns(true)
+ Concurrent::TimerTask.expects(:new).times(2).returns(true)
channel = ChatChannel.new @connection, "{id: 1}", { id: 1 }
channel.expects(:stop_periodic_timers).once
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
index 1424ded04c..947efd96d4 100644
--- a/actioncable/test/channel/stream_test.rb
+++ b/actioncable/test/channel/stream_test.rb
@@ -20,10 +20,10 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
test "streaming start and stop" do
run_in_eventmachine do
connection = TestConnection.new
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1").returns stub_everything(:pubsub) }
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
channel = ChatChannel.new connection, "{id: 1}", { id: 1 }
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe_proc) }
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
channel.unsubscribe_from_channel
end
end
@@ -31,9 +31,7 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
test "stream_for" do
run_in_eventmachine do
connection = TestConnection.new
- EM.next_tick do
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire").returns stub_everything(:pubsub) }
- end
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
channel = ChatChannel.new connection, ""
channel.stream_for Room.new(1)
@@ -41,39 +39,35 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
end
test "stream_from subscription confirmation" do
- EM.run do
+ run_in_eventmachine do
connection = TestConnection.new
- connection.expects(:pubsub).returns EM::Hiredis.connect.pubsub
ChatChannel.new connection, "{id: 1}", { id: 1 }
assert_nil connection.last_transmission
- EM::Timer.new(0.1) do
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
- assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
+ wait_for_async
- EM.run_deferred_callbacks
- EM.stop
- end
+ expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
+ connection.transmit(expected)
+
+ assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
end
end
test "subscription confirmation should only be sent out once" do
- EM.run do
+ run_in_eventmachine do
connection = TestConnection.new
- connection.stubs(:pubsub).returns EM::Hiredis.connect.pubsub
channel = ChatChannel.new connection, "test_channel"
channel.send_confirmation
channel.send_confirmation
- EM.run_deferred_callbacks
+ wait_for_async
expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription"
assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation"
assert_equal 1, connection.transmissions.size
- EM.stop
end
end
diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb
index 68668b2835..87d0e79ef3 100644
--- a/actioncable/test/connection/authorization_test.rb
+++ b/actioncable/test/connection/authorization_test.rb
@@ -10,7 +10,6 @@ class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase
end
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb
index da6041db4a..e2b017a9a1 100644
--- a/actioncable/test/connection/base_test.rb
+++ b/actioncable/test/connection/base_test.rb
@@ -14,7 +14,6 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
end
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
@@ -38,6 +37,8 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
connection.process
assert connection.websocket.possible?
+
+ wait_for_async
assert connection.websocket.alive?
end
end
@@ -54,16 +55,15 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
test "on connection open" do
run_in_eventmachine do
connection = open_connection
- connection.process
connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/))
connection.message_buffer.expects(:process!)
- # Allow EM to run on_open callback
- EM.next_tick do
- assert_equal [ connection ], @server.connections
- assert connection.connected
- end
+ connection.process
+ wait_for_async
+
+ assert_equal [ connection ], @server.connections
+ assert connection.connected
end
end
@@ -73,12 +73,12 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
connection.process
# Setup the connection
- EventMachine.stubs(:add_periodic_timer).returns(true)
- connection.send :on_open
+ Concurrent::TimerTask.stubs(:new).returns(true)
+ connection.send :handle_open
assert connection.connected
connection.subscriptions.expects(:unsubscribe_from_all)
- connection.send :on_close
+ connection.send :handle_close
assert ! connection.connected
assert_equal [], @server.connections
diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb
index d445e08f2a..a29f65fb97 100644
--- a/actioncable/test/connection/cross_site_forgery_test.rb
+++ b/actioncable/test/connection/cross_site_forgery_test.rb
@@ -6,7 +6,6 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb
index 02e6b21845..1019ad541e 100644
--- a/actioncable/test/connection/identifier_test.rb
+++ b/actioncable/test/connection/identifier_test.rb
@@ -23,9 +23,9 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
test "should subscribe to internal channel on open and unsubscribe on close" do
run_in_eventmachine do
- pubsub = mock('pubsub')
- pubsub.expects(:subscribe).with('action_cable/User#lifo')
- pubsub.expects(:unsubscribe_proc).with('action_cable/User#lifo', kind_of(Proc))
+ pubsub = mock('pubsub_adapter')
+ pubsub.expects(:subscribe).with('action_cable/User#lifo', kind_of(Proc))
+ pubsub.expects(:unsubscribe).with('action_cable/User#lifo', kind_of(Proc))
server = TestServer.new
server.stubs(:pubsub).returns(pubsub)
@@ -58,7 +58,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
protected
def open_connection_with_stubbed_pubsub
server = TestServer.new
- server.stubs(:pubsub).returns(stub_everything('pubsub'))
+ server.stubs(:adapter).returns(stub_everything('adapter'))
open_connection server: server
end
@@ -68,10 +68,10 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
@connection = Connection.new(server, env)
@connection.process
- @connection.send :on_open
+ @connection.send :handle_open
end
def close_connection
- @connection.send :on_close
+ @connection.send :handle_close
end
end
diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb
index 55a9f96cb3..e9bb4e6d7f 100644
--- a/actioncable/test/connection/multiple_identifiers_test.rb
+++ b/actioncable/test/connection/multiple_identifiers_test.rb
@@ -32,10 +32,10 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase
@connection = Connection.new(server, env)
@connection.process
- @connection.send :on_open
+ @connection.send :handle_open
end
def close_connection
- @connection.send :on_close
+ @connection.send :handle_close
end
end
diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb
index ab69df57b3..9d0bda83ef 100644
--- a/actioncable/test/connection/string_identifier_test.rb
+++ b/actioncable/test/connection/string_identifier_test.rb
@@ -10,7 +10,6 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase
end
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb
index 4f6760827e..62e41484fe 100644
--- a/actioncable/test/connection/subscriptions_test.rb
+++ b/actioncable/test/connection/subscriptions_test.rb
@@ -5,7 +5,6 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
attr_reader :websocket
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/stubs/test_adapter.rb b/actioncable/test/stubs/test_adapter.rb
new file mode 100644
index 0000000000..bbd142b287
--- /dev/null
+++ b/actioncable/test/stubs/test_adapter.rb
@@ -0,0 +1,10 @@
+class SuccessAdapter < ActionCable::SubscriptionAdapter::Base
+ def broadcast(channel, payload)
+ end
+
+ def subscribe(channel, callback, success_callback = nil)
+ end
+
+ def unsubscribe(channel, callback)
+ end
+end
diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb
index 384abc5e76..da98201900 100644
--- a/actioncable/test/stubs/test_connection.rb
+++ b/actioncable/test/stubs/test_connection.rb
@@ -11,6 +11,10 @@ class TestConnection
@transmissions = []
end
+ def pubsub
+ SuccessAdapter.new(TestServer.new)
+ end
+
def transmit(data)
@transmissions << data
end
diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb
index f9168f9b78..56d132b30a 100644
--- a/actioncable/test/stubs/test_server.rb
+++ b/actioncable/test/stubs/test_server.rb
@@ -7,9 +7,14 @@ class TestServer
def initialize
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
- @config = OpenStruct.new(log_tags: [])
+ @config = OpenStruct.new(log_tags: [], subscription_adapter: SuccessAdapter)
end
- def send_async
+ def pubsub
+ @config.subscription_adapter.new(self)
+ end
+
+ def stream_event_loop
+ @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new
end
end
diff --git a/actioncable/test/subscription_adapter/async_test.rb b/actioncable/test/subscription_adapter/async_test.rb
new file mode 100644
index 0000000000..8f413f14c2
--- /dev/null
+++ b/actioncable/test/subscription_adapter/async_test.rb
@@ -0,0 +1,17 @@
+require 'test_helper'
+require_relative './common'
+
+class AsyncAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ super
+
+ @tx_adapter.shutdown
+ @tx_adapter = @rx_adapter
+ end
+
+ def cable_config
+ { adapter: 'async' }
+ end
+end
diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb
new file mode 100644
index 0000000000..7a7ae131e6
--- /dev/null
+++ b/actioncable/test/subscription_adapter/base_test.rb
@@ -0,0 +1,73 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase
+ ## TEST THAT ERRORS ARE RETURNED FOR INHERITORS THAT DON'T OVERRIDE METHODS
+
+ class BrokenAdapter < ActionCable::SubscriptionAdapter::Base
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.subscription_adapter = BrokenAdapter
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test "#broadcast returns NotImplementedError by default" do
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).broadcast('channel', 'payload')
+ end
+ end
+
+ test "#subscribe returns NotImplementedError by default" do
+ callback = lambda { puts 'callback' }
+ success_callback = lambda { puts 'success' }
+
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).subscribe('channel', callback, success_callback)
+ end
+ end
+
+ test "#unsubscribe returns NotImplementedError by default" do
+ callback = lambda { puts 'callback' }
+
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).unsubscribe('channel', callback)
+ end
+ end
+
+ # TEST METHODS THAT ARE REQUIRED OF THE ADAPTER'S BACKEND STORAGE OBJECT
+
+ test "#broadcast is implemented" do
+ broadcast = SuccessAdapter.new(@server).broadcast('channel', 'payload')
+
+ assert_respond_to(SuccessAdapter.new(@server), :broadcast)
+
+ assert_nothing_raised NotImplementedError do
+ broadcast
+ end
+ end
+
+ test "#subscribe is implemented" do
+ callback = lambda { puts 'callback' }
+ success_callback = lambda { puts 'success' }
+ subscribe = SuccessAdapter.new(@server).subscribe('channel', callback, success_callback)
+
+ assert_respond_to(SuccessAdapter.new(@server), :subscribe)
+
+ assert_nothing_raised NotImplementedError do
+ subscribe
+ end
+ end
+
+ test "#unsubscribe is implemented" do
+ callback = lambda { puts 'callback' }
+ unsubscribe = SuccessAdapter.new(@server).unsubscribe('channel', callback)
+
+ assert_respond_to(SuccessAdapter.new(@server), :unsubscribe)
+
+ assert_nothing_raised NotImplementedError do
+ unsubscribe
+ end
+ end
+end
diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb
new file mode 100644
index 0000000000..361858784e
--- /dev/null
+++ b/actioncable/test/subscription_adapter/common.rb
@@ -0,0 +1,139 @@
+require 'test_helper'
+require 'concurrent'
+
+require 'active_support/core_ext/hash/indifferent_access'
+require 'pathname'
+
+module CommonSubscriptionAdapterTest
+ WAIT_WHEN_EXPECTING_EVENT = 3
+ WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2
+
+ def setup
+ # TODO: ActionCable requires a *lot* of setup at the moment...
+ ::Object.const_set(:ApplicationCable, Module.new)
+ ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base))
+
+ ::Object.const_set(:Rails, Module.new)
+ ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) }
+
+ server = ActionCable::Server::Base.new
+ server.config = ActionCable::Server::Configuration.new
+ inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+ server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: [])
+
+
+ # and now the "real" setup for our test:
+ server.config.cable = cable_config.with_indifferent_access
+
+ adapter_klass = server.config.pubsub_adapter
+
+ @rx_adapter = adapter_klass.new(server)
+ @tx_adapter = adapter_klass.new(server)
+ end
+
+ def teardown
+ @tx_adapter.shutdown if @tx_adapter && @tx_adapter != @rx_adapter
+ @rx_adapter.shutdown if @rx_adapter
+
+ begin
+ ::Object.send(:remove_const, :ApplicationCable)
+ rescue NameError
+ end
+ begin
+ ::Object.send(:remove_const, :Rails)
+ rescue NameError
+ end
+ end
+
+
+ def subscribe_as_queue(channel, adapter = @rx_adapter)
+ queue = Queue.new
+
+ callback = -> data { queue << data }
+ subscribed = Concurrent::Event.new
+ adapter.subscribe(channel, callback, Proc.new { subscribed.set })
+ subscribed.wait(WAIT_WHEN_EXPECTING_EVENT)
+ assert subscribed.set?
+
+ yield queue
+
+ sleep WAIT_WHEN_NOT_EXPECTING_EVENT
+ assert_empty queue
+ ensure
+ adapter.unsubscribe(channel, callback) if subscribed.set?
+ end
+
+
+ def test_subscribe_and_unsubscribe
+ subscribe_as_queue('channel') do |queue|
+ end
+ end
+
+ def test_basic_broadcast
+ subscribe_as_queue('channel') do |queue|
+ @tx_adapter.broadcast('channel', 'hello world')
+
+ assert_equal 'hello world', queue.pop
+ end
+ end
+
+ def test_broadcast_after_unsubscribe
+ keep_queue = nil
+ subscribe_as_queue('channel') do |queue|
+ keep_queue = queue
+
+ @tx_adapter.broadcast('channel', 'hello world')
+
+ assert_equal 'hello world', queue.pop
+ end
+
+ @tx_adapter.broadcast('channel', 'hello void')
+
+ sleep WAIT_WHEN_NOT_EXPECTING_EVENT
+ assert_empty keep_queue
+ end
+
+ def test_multiple_broadcast
+ subscribe_as_queue('channel') do |queue|
+ @tx_adapter.broadcast('channel', 'bananas')
+ @tx_adapter.broadcast('channel', 'apples')
+
+ received = []
+ 2.times { received << queue.pop }
+ assert_equal ['apples', 'bananas'], received.sort
+ end
+ end
+
+ def test_identical_subscriptions
+ subscribe_as_queue('channel') do |queue|
+ subscribe_as_queue('channel') do |queue_2|
+ @tx_adapter.broadcast('channel', 'hello')
+
+ assert_equal 'hello', queue_2.pop
+ end
+
+ assert_equal 'hello', queue.pop
+ end
+ end
+
+ def test_simultaneous_subscriptions
+ subscribe_as_queue('channel') do |queue|
+ subscribe_as_queue('other channel') do |queue_2|
+ @tx_adapter.broadcast('channel', 'apples')
+ @tx_adapter.broadcast('other channel', 'oranges')
+
+ assert_equal 'apples', queue.pop
+ assert_equal 'oranges', queue_2.pop
+ end
+ end
+ end
+
+ def test_channel_filtered_broadcast
+ subscribe_as_queue('channel') do |queue|
+ @tx_adapter.broadcast('other channel', 'one')
+ @tx_adapter.broadcast('channel', 'two')
+
+ assert_equal 'two', queue.pop
+ end
+ end
+end
diff --git a/actioncable/test/subscription_adapter/inline_test.rb b/actioncable/test/subscription_adapter/inline_test.rb
new file mode 100644
index 0000000000..75ea51e6b3
--- /dev/null
+++ b/actioncable/test/subscription_adapter/inline_test.rb
@@ -0,0 +1,17 @@
+require 'test_helper'
+require_relative './common'
+
+class InlineAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ super
+
+ @tx_adapter.shutdown
+ @tx_adapter = @rx_adapter
+ end
+
+ def cable_config
+ { adapter: 'inline' }
+ end
+end
diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb
new file mode 100644
index 0000000000..64c632b0cd
--- /dev/null
+++ b/actioncable/test/subscription_adapter/postgresql_test.rb
@@ -0,0 +1,32 @@
+require 'test_helper'
+require_relative './common'
+
+require 'active_record'
+
+class PostgresqlAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ database_config = { 'adapter' => 'postgresql', 'database' => 'activerecord_unittest' }
+ ar_tests = File.expand_path('../../../activerecord/test', __dir__)
+ if Dir.exist?(ar_tests)
+ require File.join(ar_tests, 'config')
+ require File.join(ar_tests, 'support/config')
+ local_config = ARTest.config['arunit']
+ database_config.update local_config if local_config
+ end
+ ActiveRecord::Base.establish_connection database_config
+
+ super
+ end
+
+ def teardown
+ super
+
+ ActiveRecord::Base.clear_all_connections!
+ end
+
+ def cable_config
+ { adapter: 'postgresql' }
+ end
+end
diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb
new file mode 100644
index 0000000000..8d52832c87
--- /dev/null
+++ b/actioncable/test/subscription_adapter/redis_test.rb
@@ -0,0 +1,10 @@
+require 'test_helper'
+require_relative './common'
+
+class RedisAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def cable_config
+ { adapter: 'redis', url: 'redis://127.0.0.1:6379/12' }
+ end
+end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index 12dcd98402..8ddbd4e764 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -5,7 +5,6 @@ require 'active_support/testing/autorun'
require 'puma'
-require 'em-hiredis'
require 'mocha/setup'
@@ -14,29 +13,16 @@ require 'rack/mock'
# Require all the stubs and models
Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file }
-$CELLULOID_DEBUG = false
-$CELLULOID_TEST = false
-require 'celluloid'
-Celluloid.logger = Logger.new(StringIO.new)
-
-require 'faye/websocket'
-class << Faye::WebSocket
- remove_method :ensure_reactor_running
-
- # We don't want Faye to start the EM reactor in tests because it makes testing much harder.
- # We want to be able to start and stop EM loop in tests to make things simpler.
- def ensure_reactor_running
- # no-op
+class ActionCable::TestCase < ActiveSupport::TestCase
+ def wait_for_async
+ e = Concurrent.global_io_executor
+ until e.completed_task_count == e.scheduled_task_count
+ sleep 0.1
+ end
end
-end
-class ActionCable::TestCase < ActiveSupport::TestCase
def run_in_eventmachine
- EM.run do
- yield
-
- EM.run_deferred_callbacks
- EM.stop
- end
+ yield
+ wait_for_async
end
end
diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb
index 69c4b6529d..4a699cde27 100644
--- a/actioncable/test/worker_test.rb
+++ b/actioncable/test/worker_test.rb
@@ -13,12 +13,15 @@ class WorkerTest < ActiveSupport::TestCase
end
def connection
+ self
+ end
+
+ def logger
+ ActionCable.server.logger
end
end
setup do
- Celluloid.boot
-
@worker = ActionCable::Server::Worker.new
@receiver = Receiver.new
end
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 312dd1997c..55c017e338 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index 175122e00a..d28568b6a1 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -8,7 +8,7 @@ module ActionMailer
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/test/fixtures/async_mailer/welcome.erb b/actionmailer/test/fixtures/async_mailer/welcome.erb
deleted file mode 100644
index 01f3f00c63..0000000000
--- a/actionmailer/test/fixtures/async_mailer/welcome.erb
+++ /dev/null
@@ -1 +0,0 @@
-Welcome \ No newline at end of file
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index dec8e0cadf..f6ffe45490 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,51 @@
+* Response etags to always be weak: Prefixes 'W/' to value returned by
+ `ActionDispatch::Http::Cache::Response#etag=`, such that etags set in
+ `fresh_when` and `stale?` are weak.
+
+ Fixes #17556.
+
+ *Abhishek Yadav*
+
+* Provide the name of HTTP Status code in assertions.
+
+ *Sean Collins*
+
+* More explicit error message when running `rake routes`. `CONTROLLER` argument
+ can now be supplied in different ways:
+ `Rails::WelcomeController`, `Rails::Welcome`, `rails/welcome`.
+
+ Fixes #22918.
+
+ *Edouard Chin*
+
+* Allow `ActionController::Parameters` instances as an argument to URL
+ helper methods. An `ArgumentError` will be raised if the passed parameters
+ are not secure.
+
+ Fixes #22832.
+
+ *Prathamesh Sonpatki*
+
+* Add option for per-form CSRF tokens.
+
+ *Greg Ose & Ben Toews*
+
+* Add tests and documentation for `ActionController::Renderers::use_renderers`.
+
+ *Benjamin Fleischer*
+
+* Fix `ActionController::Parameters#convert_parameters_to_hashes` to return filtered
+ or unfiltered values based on from where it is called, `to_h` or `to_unsafe_h`
+ respectively.
+
+ Fixes #22841.
+
+ *Prathamesh Sonpatki*
+
+* Add `ActionController::Parameters#include?`
+
+ *Justin Coyne*
+
## Rails 5.0.0.beta1 (December 18, 2015) ##
* Deprecate `redirect_to :back` in favor of `redirect_back`, which accepts a
@@ -40,7 +88,7 @@
https://github.com/rails/rails/pull/18334#issuecomment-69234050 we want
`protect_from_forgery` to default to `prepend: false`.
- `protect_from_forgery` will now be insterted into the callback chain at the
+ `protect_from_forgery` will now be inserted into the callback chain at the
point it is called in your application. This is useful for cases where you
want to `protect_from_forgery` after you perform required authentication
callbacks or other callbacks that are required to run after forgery protection.
@@ -314,7 +362,7 @@
* Allow `Bearer` as token-keyword in `Authorization-Header`.
- Aditionally to `Token`, the keyword `Bearer` is acceptable as a keyword
+ Additionally to `Token`, the keyword `Bearer` is acceptable as a keyword
for the auth-token. The `Bearer` keyword is described in the original
OAuth RFC and used in libraries like Angular-JWT.
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index a73f188623..63fd76d9b7 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -82,7 +82,13 @@ module AbstractController
# <tt>render :file => "foo/bar"</tt>.
# :api: plugin
def _normalize_args(action=nil, options={})
- if action.is_a? Hash
+ case action
+ when ActionController::Parameters
+ unless action.permitted?
+ raise ArgumentError, "render parameters are not permitted"
+ end
+ action
+ when Hash
action
else
options
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 3d3af555c9..40f33a9de0 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -41,6 +41,10 @@ module ActionController
autoload :UrlFor
end
+ autoload_under "api" do
+ autoload :ApiRendering
+ end
+
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index 1a46d49a49..ff12705abe 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -112,7 +112,7 @@ module ActionController
UrlFor,
Redirecting,
- Rendering,
+ ApiRendering,
Renderers::All,
ConditionalGet,
BasicImplicitRender,
diff --git a/actionpack/lib/action_controller/api/api_rendering.rb b/actionpack/lib/action_controller/api/api_rendering.rb
new file mode 100644
index 0000000000..3a08d28c39
--- /dev/null
+++ b/actionpack/lib/action_controller/api/api_rendering.rb
@@ -0,0 +1,14 @@
+module ActionController
+ module ApiRendering
+ extend ActiveSupport::Concern
+
+ included do
+ include Rendering
+ end
+
+ def render_to_body(options = {})
+ _process_options(options)
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index d86a793e4c..f8e0d9cf6c 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -228,7 +228,7 @@ module ActionController
expires_in 100.years, public: public
yield if stale?(etag: "#{version}-#{request.fullpath}",
- last_modified: Time.parse('2011-01-01').utc,
+ last_modified: Time.new(2011, 1, 1).utc,
public: public)
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index b2110bf946..5e9832fd4e 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -50,7 +50,6 @@ module ActionController
end
private
- # :nodoc:
def include_content?(status)
case status
when 100..199
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 2ac6e37e34..35be6d9300 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -1,4 +1,5 @@
require 'base64'
+require 'active_support/security_utils'
module ActionController
# Makes it dead easy to do HTTP Basic, Digest and Token authentication.
@@ -68,7 +69,11 @@ module ActionController
def http_basic_authenticate_with(options = {})
before_action(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
- name == options[:name] && password == options[:password]
+ # This comparison uses & so that it doesn't short circuit and
+ # uses `variable_size_secure_compare` so that length information
+ # isn't leaked.
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) &
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password])
end
end
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 6e346fadfe..173a14a1d2 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -9,6 +9,13 @@ module ActionController #:nodoc:
# @people = Person.all
# end
#
+ # That action implicitly responds to all formats, but formats can also be whitelisted:
+ #
+ # def index
+ # @people = Person.all
+ # respond_to :html, :js
+ # end
+ #
# Here's the same action, with web-service support baked in:
#
# def index
@@ -16,11 +23,12 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
+ # format.js
# format.xml { render xml: @people }
# end
# end
#
- # What that says is, "if the client wants HTML in response to this action, just respond as we
+ # What that says is, "if the client wants HTML or JS in response to this action, just respond as we
# would have before, but if the client wants XML, return them the list of people in XML format."
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
#
@@ -180,9 +188,6 @@ module ActionController #:nodoc:
# format.html.none
# format.html.phone # this gets rendered
# end
- #
- # Be sure to check the documentation of <tt>ActionController::MimeResponds.respond_to</tt>
- # for more examples.
def respond_to(*mimes)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 22e0bb5955..90fb34e386 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -11,6 +11,7 @@ module ActionController
Renderers.remove(key)
end
+ # See <tt>Responder#api_behavior</tt>
class MissingRenderer < LoadError
def initialize(format)
super "No renderer defined for format: #{format}"
@@ -20,40 +21,25 @@ module ActionController
module Renderers
extend ActiveSupport::Concern
+ # A Set containing renderer names that correspond to available renderer procs.
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
+ RENDERERS = Set.new
+
included do
class_attribute :_renderers
self._renderers = Set.new.freeze
end
- module ClassMethods
- def use_renderers(*args)
- renderers = _renderers + args
- self._renderers = renderers.freeze
- end
- alias use_renderer use_renderers
- end
-
- def render_to_body(options)
- _render_to_body_with_renderer(options) || super
- end
+ # Used in <tt>ActionController::Base</tt>
+ # and <tt>ActionController::API</tt> to include all
+ # renderers by default.
+ module All
+ extend ActiveSupport::Concern
+ include Renderers
- def _render_to_body_with_renderer(options)
- _renderers.each do |name|
- if options.key?(name)
- _process_options(options)
- method_name = Renderers._render_with_renderer_method_name(name)
- return send(method_name, options.delete(name), options)
- end
+ included do
+ self._renderers = RENDERERS
end
- nil
- end
-
- # A Set containing renderer names that correspond to available renderer procs.
- # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
- RENDERERS = Set.new
-
- def self._render_with_renderer_method_name(key)
- "_render_with_renderer_#{key}"
end
# Adds a new renderer to call within controller actions.
@@ -103,13 +89,70 @@ module ActionController
remove_method(method_name) if method_defined?(method_name)
end
- module All
- extend ActiveSupport::Concern
- include Renderers
+ def self._render_with_renderer_method_name(key)
+ "_render_with_renderer_#{key}"
+ end
- included do
- self._renderers = RENDERERS
+ module ClassMethods
+
+ # Adds, by name, a renderer or renderers to the +_renderers+ available
+ # to call within controller actions.
+ #
+ # It is useful when rendering from an <tt>ActionController::Metal</tt> controller or
+ # otherwise to add an available renderer proc to a specific controller.
+ #
+ # Both <tt>ActionController::Base</tt> and <tt>ActionController::API</tt>
+ # include <tt>ActionController::Renderers::All</tt>, making all renderers
+ # avaialable in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
+ #
+ # Since <tt>ActionController::Metal</tt> controllers cannot render, the controller
+ # must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>,
+ # and <tt>ActionController::Renderers</tt>, and have at lest one renderer.
+ #
+ # Rather than including <tt>ActionController::Renderers::All</tt> and including all renderers,
+ # you may specify which renderers to include by passing the renderer name or names to
+ # +use_renderers+. For example, a controller that includes only the <tt>:json</tt> renderer
+ # (+_render_with_renderer_json+) might look like:
+ #
+ # class MetalRenderingController < ActionController::Metal
+ # include AbstractController::Rendering
+ # include ActionController::Rendering
+ # include ActionController::Renderers
+ #
+ # use_renderers :json
+ #
+ # def show
+ # render json: record
+ # end
+ # end
+ #
+ # You must specify a +use_renderer+, else the +controller.renderer+ and
+ # +controller._renderers+ will be <tt>nil</tt>, and the action will fail.
+ def use_renderers(*args)
+ renderers = _renderers + args
+ self._renderers = renderers.freeze
end
+ alias use_renderer use_renderers
+ end
+
+ # Called by +render+ in <tt>AbstractController::Rendering</tt>
+ # which sets the return value as the +response_body+.
+ #
+ # If no renderer is found, +super+ returns control to
+ # <tt>ActionView::Rendering.render_to_body</tt>, if present.
+ def render_to_body(options)
+ _render_to_body_with_renderer(options) || super
+ end
+
+ def _render_to_body_with_renderer(options)
+ _renderers.each do |name|
+ if options.key?(name)
+ _process_options(options)
+ method_name = Renderers._render_with_renderer_method_name(name)
+ return send(method_name, options.delete(name), options)
+ end
+ end
+ nil
end
add :json do |json, options|
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 26c4550f89..91b3403ad5 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -81,6 +81,10 @@ module ActionController #:nodoc:
config_accessor :forgery_protection_origin_check
self.forgery_protection_origin_check = false
+ # Controls whether form-action/method specific CSRF tokens are used.
+ config_accessor :per_form_csrf_tokens
+ self.per_form_csrf_tokens = false
+
helper_method :form_authenticity_token
helper_method :protect_against_forgery?
end
@@ -277,16 +281,25 @@ module ActionController #:nodoc:
end
# Sets the token value for the current session.
- def form_authenticity_token
- masked_authenticity_token(session)
+ def form_authenticity_token(form_options: {})
+ masked_authenticity_token(session, form_options: form_options)
end
# Creates a masked version of the authenticity token that varies
# on each request. The masking is used to mitigate SSL attacks
# like BREACH.
- def masked_authenticity_token(session)
+ def masked_authenticity_token(session, form_options: {})
+ action, method = form_options.values_at(:action, :method)
+
+ raw_token = if per_form_csrf_tokens && action && method
+ action_path = normalize_action_path(action)
+ per_form_csrf_token(session, action_path, method)
+ else
+ real_csrf_token(session)
+ end
+
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
- encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
masked_token = one_time_pad + encrypted_csrf_token
Base64.strict_encode64(masked_token)
end
@@ -316,28 +329,54 @@ module ActionController #:nodoc:
compare_with_real_token masked_token, session
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
- # Split the token into the one-time pad and the encrypted
- # value and decrypt it
- one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
- encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
- csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)
-
- compare_with_real_token csrf_token, session
+ csrf_token = unmask_token(masked_token)
+ compare_with_real_token(csrf_token, session) ||
+ valid_per_form_csrf_token?(csrf_token, session)
else
false # Token is malformed
end
end
+ def unmask_token(masked_token)
+ # Split the token into the one-time pad and the encrypted
+ # value and decrypt it
+ one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
+ encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
+ xor_byte_strings(one_time_pad, encrypted_csrf_token)
+ end
+
def compare_with_real_token(token, session)
ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
end
+ def valid_per_form_csrf_token?(token, session)
+ if per_form_csrf_tokens
+ correct_token = per_form_csrf_token(
+ session,
+ normalize_action_path(request.fullpath),
+ request.request_method
+ )
+
+ ActiveSupport::SecurityUtils.secure_compare(token, correct_token)
+ else
+ false
+ end
+ end
+
def real_csrf_token(session)
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
Base64.strict_decode64(session[:_csrf_token])
end
+ def per_form_csrf_token(session, action_path, method)
+ OpenSSL::HMAC.digest(
+ OpenSSL::Digest::SHA256.new,
+ real_csrf_token(session),
+ [action_path, method.downcase].join("#")
+ )
+ end
+
def xor_byte_strings(s1, s2)
s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
end
@@ -362,5 +401,9 @@ module ActionController #:nodoc:
true
end
end
+
+ def normalize_action_path(action_path)
+ action_path.split('?').first.to_s.chomp('/')
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 957aa746c0..ba03430930 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -109,7 +109,8 @@ module ActionController
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
- delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
+ delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect,
+ :as_json, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
@@ -159,7 +160,11 @@ module ActionController
if other_hash.respond_to?(:permitted?)
super
else
- @parameters == other_hash
+ if other_hash.is_a?(Hash)
+ @parameters == other_hash.with_indifferent_access
+ else
+ @parameters == other_hash
+ end
end
end
@@ -176,7 +181,7 @@ module ActionController
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h
if permitted?
- convert_parameters_to_hashes(@parameters)
+ convert_parameters_to_hashes(@parameters, :to_h)
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
@@ -186,7 +191,7 @@ module ActionController
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
# parameter.
def to_unsafe_h
- convert_parameters_to_hashes(@parameters)
+ convert_parameters_to_hashes(@parameters, :to_unsafe_h)
end
alias_method :to_unsafe_hash, :to_unsafe_h
@@ -419,7 +424,7 @@ module ActionController
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
- def fetch(key, *args, &block)
+ def fetch(key, *args)
convert_value_to_parameters(
@parameters.fetch(key) {
if block_given?
@@ -514,7 +519,7 @@ module ActionController
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
- def delete(key, &block)
+ def delete(key)
convert_value_to_parameters(@parameters.delete(key))
end
@@ -579,6 +584,24 @@ module ActionController
dup
end
+ def method_missing(method_sym, *args, &block)
+ if @parameters.respond_to?(method_sym)
+ message = <<-DEPRECATE.squish
+ Method #{ method_sym } is deprecated and will be removed in Rails 5.1,
+ as `ActionController::Parameters` no longer inherits from
+ hash. Using this deprecated behavior exposes potential security
+ problems. If you continue to use this method you may be creating
+ a security vulunerability in your app that can be exploited. Instead,
+ consider using one of these documented methods which are not
+ deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html
+ DEPRECATE
+ ActiveSupport::Deprecation.warn(message)
+ @parameters.public_send(method_sym, *args, &block)
+ else
+ super
+ end
+ end
+
protected
def permitted=(new_permitted)
@permitted = new_permitted
@@ -595,16 +618,16 @@ module ActionController
end
end
- def convert_parameters_to_hashes(value)
+ def convert_parameters_to_hashes(value, using)
case value
when Array
- value.map { |v| convert_parameters_to_hashes(v) }
+ value.map { |v| convert_parameters_to_hashes(v, using) }
when Hash
value.transform_values do |v|
- convert_parameters_to_hashes(v)
+ convert_parameters_to_hashes(v, using)
end.with_indifferent_access
when Parameters
- value.to_h
+ value.send(using)
else
value
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index c55720859e..b43bb9dc17 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -8,6 +8,10 @@ require 'rails-dom-testing'
module ActionController
# :stopdoc:
+ class Metal
+ include Testing::Functional
+ end
+
# ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
@@ -455,16 +459,16 @@ module ActionController
parameters = nil
end
- if parameters.present? || session.present? || flash.present?
+ if parameters || session || flash
non_kwarg_request_warning
end
end
- if body.present?
+ if body
@request.set_header 'RAW_POST_DATA', body
end
- if http_method.present?
+ if http_method
http_method = http_method.to_s.upcase
else
http_method = "GET"
@@ -472,16 +476,12 @@ module ActionController
parameters ||= {}
- if format.present?
+ if format
parameters[:format] = format
end
@html_document = nil
- unless @controller.respond_to?(:recycle!)
- @controller.extend(Testing::Functional)
- end
-
self.cookies.update @request.cookies
self.cookies.update_cookies_from_jar
@request.set_header 'HTTP_COOKIE', cookies.to_header
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index f6336c8c7a..1e4df07d6e 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -95,6 +95,7 @@ module ActionDispatch
autoload :TestProcess
autoload :TestRequest
autoload :TestResponse
+ autoload :AssertionResponse
end
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 30ade14c26..0f7898a3f8 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -82,7 +82,7 @@ module ActionDispatch
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
- super %("#{Digest::MD5.hexdigest(key)}")
+ super %(W/"#{Digest::MD5.hexdigest(key)}")
end
def etag?; etag; end
@@ -91,7 +91,7 @@ module ActionDispatch
DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
- SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate])
+ SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
def cache_control_segments
if cache_control = _cache_control
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 0152c17ed4..e9b25339dc 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -67,10 +67,10 @@ module ActionDispatch
v = if params_readable
Array(Mime[parameters[:format]])
- elsif format = format_from_path_extension
- Array(Mime[format])
elsif use_accept_header && valid_accept_header
accepts
+ elsif extension_format = format_from_path_extension
+ [extension_format]
elsif xhr?
[Mime[:js]]
else
@@ -166,7 +166,7 @@ module ActionDispatch
def format_from_path_extension
path = @env['action_dispatch.original_path'] || @env['PATH_INFO']
if match = path && path.match(/\.(\w+)\z/)
- match.captures.first
+ Mime[match.captures.first]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index b8d395854c..6abbf9c754 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -31,7 +31,7 @@ module Mime
SET = Mimes.new
EXTENSION_LOOKUP = {}
- LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
+ LOOKUP = {}
class << self
def [](type)
@@ -177,7 +177,7 @@ module Mime
end
def lookup(string)
- LOOKUP[string]
+ LOOKUP[string] || Type.new(string)
end
def lookup_by_extension(extension)
@@ -255,9 +255,12 @@ module Mime
end
end
+ attr_reader :hash
+
def initialize(string, symbol = nil, synonyms = [])
@symbol, @synonyms = symbol, synonyms
@string = string
+ @hash = [@string, @synonyms, @symbol].hash
end
def to_s
@@ -291,6 +294,13 @@ module Mime
end
end
+ def eql?(other)
+ super || (self.class == other.class &&
+ @string == other.string &&
+ @synonyms == other.synonyms &&
+ @symbol == other.symbol)
+ end
+
def =~(mime_type)
return false unless mime_type
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
@@ -303,6 +313,10 @@ module Mime
def all?; false; end
+ protected
+
+ attr_reader :string, :synonyms
+
private
def to_ary; end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 29cf821090..5427425ef7 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -259,7 +259,7 @@ module ActionDispatch
end
# Returns the IP address of client as a +String+,
- # usually set by the RemoteIp middleware.
+ # usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 9b11111a67..14f86c7c07 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -232,7 +232,7 @@ module ActionDispatch # :nodoc:
end
# Sets the HTTP character set. In case of nil parameter
- # it sets the charset to utf-8.
+ # it sets the charset to utf-8.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
@@ -412,6 +412,13 @@ module ActionDispatch # :nodoc:
end
def before_sending
+ # Normally we've already committed by now, but it's possible
+ # (e.g., if the controller action tries to read back its own
+ # response) to get here before that. In that case, we must force
+ # an "early" commit: we're about to freeze the headers, so this is
+ # our last chance.
+ commit! unless committed?
+
headers.freeze
request.commit_cookie_jar! unless committed?
end
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 5ee8810066..018b89a2b7 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -124,7 +124,7 @@ module ActionDispatch
end
def captures
- (length - 1).times.map { |i| self[i + 1] }
+ Array.new(length - 1) { |i| self[i + 1] }
end
def [](x)
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 47f475559a..735b5939dd 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -1,19 +1,23 @@
module ActionDispatch
- # This middleware is added to the stack when `config.force_ssl = true`.
- # It does three jobs to enforce secure HTTP requests:
+ # This middleware is added to the stack when `config.force_ssl = true`, and is passed
+ # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
+ # requests:
#
- # 1. TLS redirect. http:// requests are permanently redirected to https://
- # with the same URL host, path, etc. Pass `:host` and/or `:port` to
- # modify the destination URL. This is always enabled.
+ # 1. TLS redirect: Permanently redirects http:// requests to https://
+ # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
+ # to modify the destination URL
+ # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
+ # `redirect: false` to disable this feature.
#
- # 2. Secure cookies. Sets the `secure` flag on cookies to tell browsers they
- # mustn't be sent along with http:// requests. This is always enabled.
+ # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
+ # mustn't be sent along with http:// requests. Enabled by default. Set
+ # `config.ssl_options` with `secure_cookies: false` to disable this feature.
#
- # 3. HTTP Strict Transport Security (HSTS). Tells the browser to remember
+ # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
# this site as TLS-only and automatically redirect non-TLS requests.
- # Enabled by default. Pass `hsts: false` to disable.
+ # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
#
- # Configure HSTS with `hsts: { … }`:
+ # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
# * `expires`: How long, in seconds, these settings will stick. Defaults to
# `180.days` (recommended). The minimum required to qualify for browser
# preload lists is `18.weeks`.
@@ -26,10 +30,10 @@ module ActionDispatch
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
# Go to https://hstspreload.appspot.com to submit your site for inclusion.
#
- # Disabling HSTS: To turn off HSTS, omitting the header is not enough.
- # Browsers will remember the original HSTS directive until it expires.
- # Instead, use the header to tell browsers to expire HSTS immediately.
- # Setting `hsts: false` is a shortcut for `hsts: { expires: 0 }`.
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
+ # expire HSTS immediately. Setting `hsts: false` is a shortcut for
+ # `hsts: { expires: 0 }`.
class SSL
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
@@ -39,7 +43,7 @@ module ActionDispatch
{ expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
end
- def initialize(app, redirect: {}, hsts: {}, **options)
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
@app = app
if options[:host] || options[:port]
@@ -52,6 +56,7 @@ module ActionDispatch
@redirect = redirect
end
+ @secure_cookies = secure_cookies
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
@@ -61,10 +66,11 @@ module ActionDispatch
if request.ssl?
@app.call(env).tap do |status, headers, body|
set_hsts_header! headers
- flag_cookies_as_secure! headers
+ flag_cookies_as_secure! headers if @secure_cookies
end
else
- redirect_to_https request
+ return redirect_to_https request if @redirect
+ @app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index ea9ab3821d..41c220236a 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -27,7 +27,7 @@ module ActionDispatch
# in the server's `public/` directory (see Static#call).
def match?(path)
path = ::Rack::Utils.unescape_path path
- return false unless path.valid_encoding?
+ return false unless valid_path?(path)
path = Rack::Utils.clean_path_info path
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
@@ -94,6 +94,10 @@ module ActionDispatch
false
end
end
+
+ def valid_path?(path)
+ path.valid_encoding? && !path.include?("\0")
+ end
end
# This middleware will attempt to return the contents of a file's body from
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 9e7fcbd849..42890225fa 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -109,7 +109,7 @@ module ActionDispatch
@delegate.values
end
- # Writes given value to given key of the session.
+ # Writes given value to given key of the session.
def []=(key, value)
load_for_write!
@delegate[key.to_s] = value
@@ -148,8 +148,8 @@ module ActionDispatch
@delegate.delete key.to_s
end
- # Returns value of given key from the session, or raises +KeyError+
- # if can't find given key in case of not setted dafault value.
+ # Returns value of the given key from the session, or raises +KeyError+
+ # if can't find the given key and no default value is set.
# Returns default value if specified.
#
# session.fetch(:foo)
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index f3a5268d2e..69e6dd5215 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -65,7 +65,7 @@ module ActionDispatch
routes = collect_routes(routes_to_display)
if routes.none?
- formatter.no_routes
+ formatter.no_routes(collect_routes(@routes), filter)
return formatter.result
end
@@ -84,7 +84,8 @@ module ActionDispatch
def filter_routes(filter)
if filter
- @routes.select { |route| route.defaults[:controller] == filter }
+ filter_name = filter.underscore.sub(/_controller$/, '')
+ @routes.select { |route| route.defaults[:controller] == filter_name }
else
@routes
end
@@ -136,17 +137,27 @@ module ActionDispatch
@buffer << draw_header(routes)
end
- def no_routes
- @buffer << <<-MESSAGE.strip_heredoc
+ def no_routes(routes, filter)
+ @buffer <<
+ if routes.none?
+ <<-MESSAGE.strip_heredoc
You don't have any routes defined!
Please add some routes in config/routes.rb.
-
- For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
MESSAGE
+ elsif missing_controller?(filter)
+ "The controller #{filter} does not exist!"
+ else
+ "No routes were found for this controller"
+ end
+ @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
end
private
+ def missing_controller?(controller_name)
+ [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize)
+ end
+
def draw_section(routes)
header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
@@ -187,7 +198,7 @@ module ActionDispatch
def header(routes)
end
- def no_routes
+ def no_routes(*)
@buffer << <<-MESSAGE.strip_heredoc
<p>You don't have any routes defined!</p>
<ul>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 18cd205bad..afbaa45d20 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -184,26 +184,32 @@ module ActionDispatch
def build_path(ast, requirements, anchor)
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
- # Get all the symbol nodes followed by literals that are not the
- # dummy node.
- symbols = ast.find_all { |n|
- n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
- }.map(&:left)
-
- # Get all the symbol nodes preceded by literals.
- symbols.concat ast.find_all { |n|
- n.cat? && n.left.literal? && n.right.cat? && n.right.left.symbol?
- }.map { |n| n.right.left }
-
- symbols.each { |x|
- x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ # Find all the symbol nodes that are adjacent to literal nodes and alter
+ # the regexp so that Journey will partition them into custom routes.
+ ast.find_all { |node|
+ next unless node.cat?
+
+ if node.left.literal? && node.right.symbol?
+ symbol = node.right
+ elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
+ symbol = node.right.left
+ elsif node.left.symbol? && node.right.literal?
+ symbol = node.left
+ elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
+ symbol = node.left
+ else
+ next
+ end
+
+ if symbol
+ symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
+ end
}
pattern
end
private :build_path
-
private
def add_wildcard_options(options, formatted, path_ast)
# Add a constraint for wildcard route to make it non-greedy and match the
@@ -387,24 +393,6 @@ module ActionDispatch
end
module Base
- # You can specify what Rails should route "/" to with the root method:
- #
- # root to: 'pages#main'
- #
- # For options, see +match+, as +root+ uses it internally.
- #
- # You can also pass a string which will expand
- #
- # root 'pages#main'
- #
- # You should put the root route at the top of <tt>config/routes.rb</tt>,
- # because this means it will be matched first. As this is the most popular route
- # of most Rails applications, this is beneficial.
- def root(options = {})
- name = has_named_route?(:root) ? nil : :root
- match '/', { as: name, via: :get }.merge!(options)
- end
-
# Matches a url pattern to one or more routes.
#
# You should not use the +match+ method in your router
@@ -1689,7 +1677,20 @@ to this:
@set.add_route(mapping, ast, as, anchor)
end
- def root(path, options={})
+ # You can specify what Rails should route "/" to with the root method:
+ #
+ # root to: 'pages#main'
+ #
+ # For options, see +match+, as +root+ uses it internally.
+ #
+ # You can also pass a string which will expand
+ #
+ # root 'pages#main'
+ #
+ # You should put the root route at the top of <tt>config/routes.rb</tt>,
+ # because this means it will be matched first. As this is the most popular route
+ # of most Rails applications, this is beneficial.
+ def root(path, options = {})
if path.is_a?(String)
options[:to] = path
elsif path.is_a?(Hash) and options.empty?
@@ -1701,11 +1702,11 @@ to this:
if @scope.resources?
with_scope_level(:root) do
path_scope(parent_resource.path) do
- super(options)
+ match_root_route(options)
end
end
else
- super(options)
+ match_root_route(options)
end
end
@@ -1900,6 +1901,11 @@ to this:
ensure
@scope = @scope.parent
end
+
+ def match_root_route(options)
+ name = has_named_route?(:root) ? nil : :root
+ match '/', { :as => name, :via => :get }.merge!(options)
+ end
end
# Routing Concerns allow you to declare common routes that can be reused
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2bd2e53252..846b5fa1fc 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -281,8 +281,17 @@ module ActionDispatch
helper = UrlHelper.create(route, opts, route_key, url_strategy)
mod.module_eval do
define_method(name) do |*args|
- options = nil
- options = args.pop if args.last.is_a? Hash
+ last = args.last
+ options = case last
+ when Hash
+ args.pop
+ when ActionController::Parameters
+ if last.permitted?
+ args.pop.to_h
+ else
+ raise ArgumentError, "Generating an URL from non sanitized request parameters is insecure!"
+ end
+ end
helper.call self, args, options
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
new file mode 100644
index 0000000000..3fb81ff083
--- /dev/null
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -0,0 +1,49 @@
+module ActionDispatch
+ # This is a class that abstracts away an asserted response.
+ # It purposely does not inherit from Response, because it doesn't need it.
+ # That means it does not have headers or a body.
+ #
+ # As an input to the initializer, we take a Fixnum, a String, or a Symbol.
+ # If it's a Fixnum or String, we figure out what its symbolized name.
+ # If it's a Symbol, we figure out what its corresponding code is.
+ # The resulting code will be a Fixnum, for real HTTP codes, and it will
+ # be a String for the pseudo-HTTP codes, such as:
+ # :success, :missing, :redirect and :error
+ class AssertionResponse
+ attr_reader :code, :name
+
+ GENERIC_RESPONSE_CODES = { # :nodoc:
+ success: "2XX",
+ missing: "404",
+ redirect: "3XX",
+ error: "5XX"
+ }
+
+ def initialize(code_or_name)
+ if code_or_name.is_a?(Symbol)
+ @name = code_or_name
+ @code = code_from_name(code_or_name)
+ else
+ @name = name_from_code(code_or_name)
+ @code = code_or_name
+ end
+
+ raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
+ raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
+ end
+
+ def code_and_name
+ "#{code}: #{name}"
+ end
+
+ private
+
+ def code_from_name(name)
+ GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
+ end
+
+ def name_from_code(code)
+ GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index c138660a21..cd55b7d975 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,4 +1,3 @@
-
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -29,18 +28,10 @@ module ActionDispatch
def assert_response(type, message = nil)
message ||= generate_response_message(type)
- if Symbol === type
- if [:success, :missing, :redirect, :error].include?(type)
- assert @response.send(RESPONSE_PREDICATES[type]), message
- else
- code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
- if code.nil?
- raise ArgumentError, "Invalid response type :#{type}"
- end
- assert_equal code, @response.response_code, message
- end
+ if RESPONSE_PREDICATES.keys.include?(type)
+ assert @response.send(RESPONSE_PREDICATES[type]), message
else
- assert_equal type, @response.response_code, message
+ assert_equal AssertionResponse.new(type).code, @response.response_code, message
end
end
@@ -85,8 +76,9 @@ module ActionDispatch
end
end
- def generate_response_message(type, code = @response.response_code)
- "Expected response to be a <#{type}>, but was a <#{code}>"
+ def generate_response_message(expected, actual = @response.response_code)
+ "Expected response to be a <#{code_with_name(expected)}>,"\
+ " but was a <#{code_with_name(actual)}>"
.concat location_if_redirected
end
@@ -95,6 +87,14 @@ module ActionDispatch
location = normalize_argument_to_redirection(@response.location)
" redirect to <#{location}>"
end
+
+ def code_with_name(code_or_name)
+ if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
+ code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
+ end
+
+ AssertionResponse.new(code_or_name).code_and_name
+ end
end
end
end
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index f664dab620..941877d10d 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 5cfb5f02d8..3a1410d492 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -8,7 +8,7 @@ module ActionPack
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index ef7aab72c6..1ef10ff956 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -20,7 +20,11 @@ rescue LoadError
puts "'drb/unix' is not available"
end
-PROCESS_COUNT = (ENV['N'] || 4).to_i
+if ENV['TRAVIS']
+ PROCESS_COUNT = 0
+else
+ PROCESS_COUNT = (ENV['N'] || 4).to_i
+end
require 'active_support/testing/autorun'
require 'abstract_controller'
@@ -371,6 +375,12 @@ module RoutingTestHelpers
end
end
+class MetalRenderingController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+ include ActionController::Renderers
+end
+
class ResourcesController < ActionController::Base
def index() head :ok end
alias_method :show, :index
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 841fa6aaad..579ce0ed29 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -74,7 +74,18 @@ module ActionDispatch
@response.status = 404
error = assert_raises(Minitest::Assertion) { assert_response :success }
- expected = "Expected response to be a <success>, but was a <404>"
+ expected = "Expected response to be a <2XX: success>,"\
+ " but was a <404: Not Found>"
+ assert_match expected, error.message
+ end
+
+ def test_error_message_shows_404_when_asserted_for_200
+ @response = ActionDispatch::Response.new
+ @response.status = 404
+
+ error = assert_raises(Minitest::Assertion) { assert_response 200 }
+ expected = "Expected response to be a <200: OK>,"\
+ " but was a <404: Not Found>"
assert_match expected, error.message
end
@@ -84,7 +95,8 @@ module ActionDispatch
@response.location = 'http://test.host/posts/redirect/1'
error = assert_raises(Minitest::Assertion) { assert_response :success }
- expected = "Expected response to be a <success>, but was a <302>" \
+ expected = "Expected response to be a <2XX: success>,"\
+ " but was a <302: Found>" \
" redirect to <http://test.host/posts/redirect/1>"
assert_match expected, error.message
end
@@ -95,7 +107,8 @@ module ActionDispatch
@response.location = 'http://test.host/posts/redirect/2'
error = assert_raises(Minitest::Assertion) { assert_response 301 }
- expected = "Expected response to be a <301>, but was a <302>" \
+ expected = "Expected response to be a <301: Moved Permanently>,"\
+ " but was a <302: Found>" \
" redirect to <http://test.host/posts/redirect/2>"
assert_match expected, error.message
end
diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb
index 9405538833..911a8144b2 100644
--- a/actionpack/test/controller/api/renderers_test.rb
+++ b/actionpack/test/controller/api/renderers_test.rb
@@ -19,6 +19,14 @@ class RenderersApiController < ActionController::API
def two
render xml: Model.new
end
+
+ def plain
+ render plain: 'Hi from plain', status: 500
+ end
+
+ def text
+ render text: 'Hi from text', status: 500
+ end
end
class RenderersApiTest < ActionController::TestCase
@@ -35,4 +43,18 @@ class RenderersApiTest < ActionController::TestCase
assert_response :success
assert_equal({ a: 'b' }.to_xml, @response.body)
end
+
+ def test_render_plain
+ get :plain
+ assert_response :internal_server_error
+ assert_equal('Hi from plain', @response.body)
+ end
+
+ def test_render_text
+ assert_deprecated do
+ get :text
+ end
+ assert_response :internal_server_error
+ assert_equal('Hi from text', @response.body)
+ end
end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 0a5e5402b9..adcf259317 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -5,6 +5,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
before_action :authenticate, only: :index
before_action :authenticate_with_request, only: :display
before_action :authenticate_long_credentials, only: :show
+ before_action :auth_with_special_chars, only: :special_creds
http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
@@ -20,6 +21,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
render plain: 'Only for loooooong credentials'
end
+ def special_creds
+ render plain: 'Only for special credentials'
+ end
+
def search
render plain: 'All inline'
end
@@ -40,6 +45,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
end
end
+ def auth_with_special_chars
+ authenticate_or_request_with_http_basic do |username, password|
+ username == 'login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' && password == 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t'
+ end
+ end
+
def authenticate_long_credentials
authenticate_or_request_with_http_basic do |username, password|
username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890'
@@ -100,7 +111,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_no_match(/\n/, result)
end
- test "succesful authentication with uppercase authorization scheme" do
+ test "successful authentication with uppercase authorization scheme" do
@request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}"
get :index
@@ -133,6 +144,14 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_equal 'Definitely Maybe', @response.body
end
+ test "authentication request with valid credential special chars" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t', 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t')
+ get :special_creds
+
+ assert_response :success
+ assert_equal 'Only for special credentials', @response.body
+ end
+
test "authenticate with class method" do
@request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath')
get :search
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index aab2d9545d..2ef9734269 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -429,7 +429,7 @@ module ActionController
end
def test_stale_with_etag
- @request.if_none_match = Digest::MD5.hexdigest("123")
+ @request.if_none_match = %(W/"#{Digest::MD5.hexdigest('123')}")
get :with_stale
assert_equal 304, response.status.to_i
end
diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb
new file mode 100644
index 0000000000..007866a559
--- /dev/null
+++ b/actionpack/test/controller/metal/renderers_test.rb
@@ -0,0 +1,42 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/conversions'
+
+class MetalRenderingJsonController < MetalRenderingController
+ class Model
+ def to_json(options = {})
+ { a: 'b' }.to_json(options)
+ end
+
+ def to_xml(options = {})
+ { a: 'b' }.to_xml(options)
+ end
+ end
+
+ use_renderers :json
+
+ def one
+ render json: Model.new
+ end
+
+ def two
+ render xml: Model.new
+ end
+end
+
+class RenderersMetalTest < ActionController::TestCase
+ tests MetalRenderingJsonController
+
+ def test_render_json
+ get :one
+ assert_response :success
+ assert_equal({ a: 'b' }.to_json, @response.body)
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_xml
+ get :two
+ assert_response :success
+ assert_equal(" ", @response.body)
+ assert_equal 'text/plain', @response.content_type
+ end
+end
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 97875c3cbb..bd43ff7697 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -27,6 +27,12 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params[:person][:name].permitted?
end
+ test "as_json returns the JSON representation of the parameters hash" do
+ assert_not @params.as_json.key? "parameters"
+ assert_not @params.as_json.key? "permitted"
+ assert @params.as_json.key? "person"
+ end
+
test "each carries permitted status" do
@params.permit!
@params.each { |key, value| assert(value.permitted?) if key == "person" }
@@ -122,4 +128,10 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params.values_at(:person).first.permitted?
assert_not @params[:person].values_at(:name).first.permitted?
end
+
+ test "equality with another hash works" do
+ hash1 = { foo: :bar }
+ params1 = ActionController::Parameters.new(hash1)
+ assert(params1 == hash1)
+ end
end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index f23aa599c1..3299f2d9d0 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -294,8 +294,16 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "to_unsafe_h returns unfiltered params" do
- assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess
- assert_not @params.to_h.is_a? ActionController::Parameters
+ assert @params.to_unsafe_h.is_a? ActiveSupport::HashWithIndifferentAccess
+ assert_not @params.to_unsafe_h.is_a? ActionController::Parameters
+ end
+
+ test "to_unsafe_h returns unfiltered params even after accessing few keys" do
+ params = ActionController::Parameters.new("f"=>{"language_facet"=>["Tibetan"]})
+ expected = {"f"=>{"language_facet"=>["Tibetan"]}}
+
+ assert params['f'].is_a? ActionController::Parameters
+ assert_equal expected, params.to_unsafe_h
end
test "to_h only deep dups Ruby collections" do
@@ -325,4 +333,10 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal({ 'companies' => [ company, :acme ] }, params.to_unsafe_h)
assert_not company.dupped
end
+
+ test "include? returns true when the key is present" do
+ assert @params.include? :person
+ assert @params.include? 'person'
+ assert_not @params.include? :gorilla
+ end
end
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
deleted file mode 100644
index 1f5215ac55..0000000000
--- a/actionpack/test/controller/render_other_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'abstract_unit'
-
-
-class RenderOtherTest < ActionController::TestCase
- class TestController < ActionController::Base
- def render_simon_says
- render :simon => "foo"
- end
- end
-
- tests TestController
-
- def test_using_custom_render_option
- ActionController.add_renderer :simon do |says, options|
- self.content_type = Mime[:text]
- self.response_body = "Simon says: #{says}"
- end
-
- get :render_simon_says
- assert_equal "Simon says: foo", @response.body
- ensure
- ActionController.remove_renderer :simon
- end
-end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 256ebf6a07..3f569230c2 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -62,6 +62,16 @@ class TestController < ActionController::Base
end
end
+ def dynamic_render
+ render params[:id] # => String, AC:Params
+ end
+
+ def dynamic_render_with_file
+ # This is extremely bad, but should be possible to do.
+ file = params[:id] # => String, AC:Params
+ render file: file
+ end
+
class Collection
def initialize(records)
@records = records
@@ -243,6 +253,27 @@ end
class ExpiresInRenderTest < ActionController::TestCase
tests TestController
+ def test_dynamic_render_with_file
+ # This is extremely bad, but should be possible to do.
+ assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
+ response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' }
+ assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')),
+ response.body
+ end
+
+ def test_dynamic_render
+ assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
+ assert_raises ActionView::MissingTemplate do
+ get :dynamic_render, params: { id: '../\\../test/abstract_unit.rb' }
+ end
+ end
+
+ def test_dynamic_render_file_hash
+ assert_raises ArgumentError do
+ get :dynamic_render, params: { id: { file: '../\\../test/abstract_unit.rb' } }
+ end
+ end
+
def test_expires_in_header
get :conditional_hello_with_expires_in
assert_equal "max-age=60, private", @response.headers["Cache-Control"]
@@ -461,7 +492,7 @@ class EtagRenderTest < ActionController::TestCase
end
def etag(record)
- Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record)).inspect
+ %(W/"#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
end
end
diff --git a/actionpack/test/controller/renderers_test.rb b/actionpack/test/controller/renderers_test.rb
new file mode 100644
index 0000000000..e6c2e4636e
--- /dev/null
+++ b/actionpack/test/controller/renderers_test.rb
@@ -0,0 +1,90 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+require 'active_support/logger'
+
+class RenderersTest < ActionController::TestCase
+ class XmlRenderable
+ def to_xml(options)
+ options[:root] ||= "i-am-xml"
+ "<#{options[:root]}/>"
+ end
+ end
+ class JsonRenderable
+ def as_json(options={})
+ hash = { :a => :b, :c => :d, :e => :f }
+ hash.except!(*options[:except]) if options[:except]
+ hash
+ end
+
+ def to_json(options = {})
+ super :except => [:c, :e]
+ end
+ end
+ class CsvRenderable
+ def to_csv
+ "c,s,v"
+ end
+ end
+ class TestController < ActionController::Base
+
+ def render_simon_says
+ render :simon => "foo"
+ end
+
+ def respond_to_mime
+ respond_to do |type|
+ type.json do
+ render json: JsonRenderable.new
+ end
+ type.js { render json: 'JS', callback: 'alert' }
+ type.csv { render csv: CsvRenderable.new }
+ type.xml { render xml: XmlRenderable.new }
+ type.html { render body: "HTML" }
+ type.rss { render body: "RSS" }
+ type.all { render body: "Nothing" }
+ type.any(:js, :xml) { render body: "Either JS or XML" }
+ end
+ end
+ end
+
+ tests TestController
+
+ def setup
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ super
+ @controller.logger = ActiveSupport::Logger.new(nil)
+ end
+
+ def test_using_custom_render_option
+ ActionController.add_renderer :simon do |says, options|
+ self.content_type = Mime[:text]
+ self.response_body = "Simon says: #{says}"
+ end
+
+ get :render_simon_says
+ assert_equal "Simon says: foo", @response.body
+ ensure
+ ActionController.remove_renderer :simon
+ end
+
+ def test_raises_missing_template_no_renderer
+ assert_raise ActionView::MissingTemplate do
+ get :respond_to_mime, format: 'csv'
+ end
+ assert_equal Mime[:csv], @response.content_type
+ assert_equal "", @response.body
+ end
+
+ def test_adding_csv_rendering_via_renderers_add
+ ActionController::Renderers.add :csv do |value, options|
+ send_data value.to_csv, type: Mime[:csv]
+ end
+ @request.accept = "text/csv"
+ get :respond_to_mime, format: 'csv'
+ assert_equal Mime[:csv], @response.content_type
+ assert_equal "c,s,v", @response.body
+ ensure
+ ActionController::Renderers.remove :csv
+ end
+end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 87a8ed3dc9..1984ad8825 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -128,6 +128,23 @@ class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsin
end
end
+class PerFormTokensController < ActionController::Base
+ protect_from_forgery :with => :exception
+ self.per_form_csrf_tokens = true
+
+ def index
+ render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>"
+ end
+
+ def post_one
+ render plain: ''
+ end
+
+ def post_two
+ render plain: ''
+ end
+end
+
# common test methods
module RequestForgeryProtectionTests
def setup
@@ -623,3 +640,158 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase
end
end
end
+
+class PerFormTokensControllerTest < ActionController::TestCase
+ def test_per_form_token_is_same_size_as_global_token
+ get :index
+ expected = ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH
+ actual = @controller.send(:per_form_csrf_token, session, '/path', 'post').size
+ assert_equal expected, actual
+ end
+
+ def test_accepts_token_for_correct_path_and_method
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+
+ def test_rejects_token_for_incorrect_path
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_two'
+ assert_raises(ActionController::InvalidAuthenticityToken) do
+ post :post_two, params: {custom_authenticity_token: form_token}
+ end
+ end
+
+ def test_rejects_token_for_incorrect_method
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_raises(ActionController::InvalidAuthenticityToken) do
+ patch :post_one, params: {custom_authenticity_token: form_token}
+ end
+ end
+
+ def test_accepts_global_csrf_token
+ get :index
+
+ token = @controller.send(:form_authenticity_token)
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: token}
+ end
+ assert_response :success
+ end
+
+ def test_ignores_params
+ get :index, params: {form_path: '/per_form_tokens/post_one?foo=bar'}
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
+ assert_equal expected, actual
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one?foo=baz'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token, baz: 'foo'}
+ end
+ assert_response :success
+ end
+
+ def test_ignores_trailing_slash_during_generation
+ get :index, params: {form_path: '/per_form_tokens/post_one/'}
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+
+ def test_ignores_trailing_slash_during_validation
+ get :index
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+
+ def test_method_is_case_insensitive
+ get :index, params: {form_method: "POST"}
+
+ form_token = nil
+ assert_select 'input[name=custom_authenticity_token]' do |elts|
+ form_token = elts.first['value']
+ assert_not_nil form_token
+ end
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
+ end
+ assert_response :success
+ end
+end
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index 168f64ce41..b6efcd6f9a 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -65,4 +65,17 @@ class ParametersRequireTest < ActiveSupport::TestCase
.require([:first_name, :title])
end
end
+
+ test "value params" do
+ params = ActionController::Parameters.new(foo: "bar", dog: "cinco")
+ assert_equal ["bar", "cinco"], params.values
+ assert params.has_value?("cinco")
+ assert params.value?("cinco")
+ end
+
+ test "Deprecated methods are deprecated" do
+ assert_deprecated do
+ ActionController::Parameters.new(foo: "bar").merge!({bar: "foo"})
+ end
+ end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 7dd9d05e62..0edad72fd9 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -897,6 +897,27 @@ class RequestFormat < BaseRequestTest
ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header
end
end
+
+ test "format taken from the path extension" do
+ request = stub_request 'PATH_INFO' => '/foo.xml'
+ assert_called(request, :parameters, times: 1, returns: {}) do
+ assert_equal [Mime[:xml]], request.formats
+ end
+
+ request = stub_request 'PATH_INFO' => '/foo.123'
+ assert_called(request, :parameters, times: 1, returns: {}) do
+ assert_equal [Mime[:html]], request.formats
+ end
+ end
+
+ test "formats from accept headers have higher precedence than path extension" do
+ request = stub_request 'HTTP_ACCEPT' => 'application/json',
+ 'PATH_INFO' => '/foo.xml'
+
+ assert_called(request, :parameters, times: 1, returns: {}) do
+ assert_equal [Mime[:json]], request.formats
+ end
+ end
end
class RequestMimeType < BaseRequestTest
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index c37679bc5f..8b3849cb7a 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -37,6 +37,27 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal "closed stream", e.message
end
+ def test_read_body_during_action
+ @response.body = "Hello, World!"
+
+ # even though there's no explicitly set content-type,
+ assert_equal nil, @response.content_type
+
+ # after the action reads back @response.body,
+ assert_equal "Hello, World!", @response.body
+
+ # the response can be built.
+ status, headers, body = @response.to_a
+ assert_equal 200, status
+ assert_equal({
+ "Content-Type" => "text/html; charset=utf-8"
+ }, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["Hello, World!"], parts
+ end
+
def test_response_body_encoding
body = ["hello".encode(Encoding::UTF_8)]
response = ActionDispatch::Response.new 200, {}, body
@@ -176,11 +197,11 @@ class ResponseTest < ActiveSupport::TestCase
}
resp.to_a
- assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag)
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag)
assert_equal({:public => true}, resp.cache_control)
assert_equal('public', resp.headers['Cache-Control'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
end
test "read charset and content type" do
@@ -367,16 +388,16 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('public', @response.headers['Cache-Control'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
assert_equal({:public => true}, @response.cache_control)
end
test "response cache control from rackish app" do
@app = lambda { |env|
[200,
- {'ETag' => '"202cb962ac59075b964b07152d234b70"',
+ {'ETag' => 'W/"202cb962ac59075b964b07152d234b70"',
'Cache-Control' => 'public'}, ['Hello']]
}
@@ -384,9 +405,9 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('public', @response.headers['Cache-Control'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
assert_equal({:public => true}, @response.cache_control)
end
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index a17d07c40b..7382c267c7 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -7,6 +7,9 @@ class MountedRackApp
end
end
+class Rails::DummyController
+end
+
module ActionDispatch
module Routing
class RoutesInspectorTest < ActiveSupport::TestCase
@@ -331,6 +334,41 @@ module ActionDispatch
" cart GET /cart(.:format) cart#show"
], output
end
+
+ def test_routes_with_undefined_filter
+ output = draw(:filter => 'Rails::MissingController') do
+ get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+ end
+
+ assert_equal [
+ "The controller Rails::MissingController does not exist!",
+ "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ ], output
+ end
+
+ def test_no_routes_matched_filter
+ output = draw(:filter => 'rails/dummy') do
+ get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+ end
+
+ assert_equal [
+ "No routes were found for this controller",
+ "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ ], output
+ end
+
+ def test_no_routes_were_defined
+ output = draw(:filter => 'Rails::DummyController') { }
+
+ assert_equal [
+ "You don't have any routes defined!",
+ "",
+ "Please add some routes in config/routes.rb.",
+ "",
+ "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ ], output
+ end
+
end
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 82222a141c..5ead9357ae 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3578,6 +3578,27 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'HEAD', @response.body
end
+ def test_passing_action_parameters_to_url_helpers_raises_error_if_parameters_are_not_permitted
+ draw do
+ root :to => 'projects#index'
+ end
+ params = ActionController::Parameters.new(id: '1')
+
+ assert_raises ArgumentError do
+ root_path(params)
+ end
+ end
+
+ def test_passing_action_parameters_to_url_helpers_is_allowed_if_parameters_are_permitted
+ draw do
+ root :to => 'projects#index'
+ end
+ params = ActionController::Parameters.new(id: '1')
+ params.permit!
+
+ assert_equal '/?id=1', root_path(params)
+ end
+
private
def draw(&block)
@@ -4633,3 +4654,66 @@ class TestErrorsInController < ActionDispatch::IntegrationTest
assert_equal 404, response.status
end
end
+
+class TestPartialDynamicPathSegments < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get '/songs/song-:song', to: ok
+ get '/songs/:song-song', to: ok
+ get '/:artist/song-:song', to: ok
+ get '/:artist/:song-song', to: ok
+
+ get '/optional/songs(/song-:song)', to: ok
+ get '/optional/songs(/:song-song)', to: ok
+ get '/optional/:artist(/song-:song)', to: ok
+ get '/optional/:artist(/:song-song)', to: ok
+ end
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ def test_paths_with_partial_dynamic_segments_are_recognised
+ get '/david-bowie/changes-song'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+
+ get '/david-bowie/song-changes'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+
+ get '/songs/song-changes'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/songs/changes-song'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/optional/songs/song-changes'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/optional/songs/changes-song'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/optional/david-bowie/changes-song'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+
+ get '/optional/david-bowie/song-changes'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+ end
+
+ private
+
+ def assert_params(params)
+ assert_equal(params, request.path_parameters)
+ end
+end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index 7a5b8393dc..c66a0e6a7a 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -12,25 +12,31 @@ class SSLTest < ActionDispatch::IntegrationTest
end
class RedirectSSLTest < SSLTest
- def assert_not_redirected(url, headers: {})
- self.app = build_app
+
+ def assert_not_redirected(url, headers: {}, redirect: {}, deprecated_host: nil,
+ deprecated_port: nil)
+
+ self.app = build_app ssl_options: { redirect: redirect,
+ host: deprecated_host, port: deprecated_port
+ }
+
get url, headers: headers
assert_response :ok
end
- def assert_redirected(host: nil, port: nil, status: 301, body: [],
- deprecated_host: nil, deprecated_port: nil,
+ def assert_redirected(redirect: {}, deprecated_host: nil, deprecated_port: nil,
from: 'http://a/b?c=d', to: from.sub('http', 'https'))
- self.app = build_app ssl_options: {
- redirect: { host: host, port: port, status: status, body: body },
+ redirect = { status: 301, body: [] }.merge(redirect)
+
+ self.app = build_app ssl_options: { redirect: redirect,
host: deprecated_host, port: deprecated_port
}
get from
- assert_response status
+ assert_response redirect[:status] || 301
assert_redirected_to to
- assert_equal body.join, @response.body
+ assert_equal redirect[:body].join, @response.body
end
test 'https is not redirected' do
@@ -46,31 +52,31 @@ class RedirectSSLTest < SSLTest
end
test 'redirect with non-301 status' do
- assert_redirected status: 307
+ assert_redirected redirect: { status: 307 }
end
test 'redirect with custom body' do
- assert_redirected body: ['foo']
+ assert_redirected redirect: { body: ['foo'] }
end
test 'redirect to specific host' do
- assert_redirected host: 'ssl', to: 'https://ssl/b?c=d'
+ assert_redirected redirect: { host: 'ssl' }, to: 'https://ssl/b?c=d'
end
test 'redirect to default port' do
- assert_redirected port: 443
+ assert_redirected redirect: { port: 443 }
end
test 'redirect to non-default port' do
- assert_redirected port: 8443, to: 'https://a:8443/b?c=d'
+ assert_redirected redirect: { port: 8443 }, to: 'https://a:8443/b?c=d'
end
test 'redirect to different host and non-default port' do
- assert_redirected host: 'ssl', port: 8443, to: 'https://ssl:8443/b?c=d'
+ assert_redirected redirect: { host: 'ssl', port: 8443 }, to: 'https://ssl:8443/b?c=d'
end
test 'redirect to different host including port' do
- assert_redirected host: 'ssl:443', to: 'https://ssl:443/b?c=d'
+ assert_redirected redirect: { host: 'ssl:443' }, to: 'https://ssl:443/b?c=d'
end
test ':host is deprecated, moved within redirect: { host: … }' do
@@ -84,6 +90,10 @@ class RedirectSSLTest < SSLTest
assert_redirected deprecated_port: 1, to: 'https://a:1/b?c=d'
end
end
+
+ test 'no redirect with redirect set to false' do
+ assert_not_redirected 'http://example.org', redirect: false
+ end
end
class StrictTransportSecurityTest < SSLTest
@@ -187,6 +197,11 @@ class SecureCookiesTest < SSLTest
assert_cookies 'problem=def; path=/; Secure; HttpOnly'
end
+ def test_cookies_as_not_secure_with_secure_cookies_disabled
+ get headers: { 'Set-Cookie' => DEFAULT }, ssl_options: { secure_cookies: false }
+ assert_cookies(*DEFAULT.split("\n"))
+ end
+
def test_no_cookies
get
assert_nil response.headers['Set-Cookie']
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 1da57ab50b..ea8b5e904e 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -40,6 +40,10 @@ module StaticTests
assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding('ASCII-8BIT')).body
end
+ def test_handles_urls_with_null_byte
+ assert_equal "Hello, World!", get("/doorkeeper%00").body
+ end
+
def test_sets_cache_control
app = assert_deprecated do
ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index a43d9039db..98ac2c1c22 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,27 @@
+* Create a new `ActiveSupport::SafeBuffer` instance when `content_for` is flushed.
+
+ Fixes #19890
+
+ *Yoong Kang Lim*
+
+* Fix `collection_radio_buttons` hidden_field name and make it appear
+ before the actual input radio tags to make the real value override
+ the hidden when passed.
+
+ Fixes #22773.
+
+ *Santiago Pastorino*
+
+* `ActionView::TestCase::Controller#params` returns an instance of
+ `ActionController::Parameters`.
+
+ *Justin Coyne*
+
+* Fix regression in `submit_tag` when a symbol is used as label argument.
+
+ *Yuuji Yaginuma*
+
+
## Rails 5.0.0.beta1 (December 18, 2015) ##
* `I18n.translate` helper will wrap the missing translation keys
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index c3bbac27fd..0a87500a52 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb
index ba24510e56..bc61920848 100644
--- a/actionview/lib/action_view/flows.rb
+++ b/actionview/lib/action_view/flows.rb
@@ -15,7 +15,7 @@ module ActionView
# Called by each renderer object to set the layout contents.
def set(key, value)
- @content[key] = value
+ @content[key] = ActiveSupport::SafeBuffer.new(value)
end
# Called by content_for
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 20d408741e..23d5319579 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -8,7 +8,7 @@ module ActionView
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 91e934cd64..cc54faa778 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -136,7 +136,7 @@ module ActionView
tag(
"link",
"rel" => tag_options[:rel] || "alternate",
- "type" => tag_options[:type] || Mime[type].to_s,
+ "type" => tag_options[:type] || Template::Types[type].to_s,
"title" => tag_options[:title] || type.to_s.upcase,
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
)
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index bb1cdd0f8d..dba70e284e 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -51,7 +51,7 @@ module ActionView
# * <tt>:language</tt>: Defaults to "en-US".
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
- # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
# 2005 is used (as an "I don't care" value).
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 93c7cba395..df8d0affd0 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -9,8 +9,8 @@ module ActionView
# It provides a method to capture blocks into variables through capture and
# a way to capture a block of markup for use in a layout through content_for.
module CaptureHelper
- # The capture method allows you to extract part of a template into a
- # variable. You can then use this variable anywhere in your templates or layout.
+ # The capture method extracts part of a template as a String object.
+ # You can then use this object anywhere in your templates, layout, or helpers.
#
# The capture method can be used in ERB templates...
#
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 2a367b85af..c1015ffe89 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -765,7 +765,7 @@ module ActionView
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:post, :terms) do
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
+ # raw('Accept <a href="/terms">Terms</a>.')
# end
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(object_name, method, content_or_options = nil, options = nil, &block)
@@ -1675,7 +1675,7 @@ module ActionView
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:terms) do
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
+ # raw('Accept <a href="/terms">Terms</a>.')
# end
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(method, text = nil, options = {}, &block)
@@ -1922,6 +1922,8 @@ module ActionView
@object_name.to_s.humanize
end
+ model = model.downcase
+
defaults = []
defaults << :"helpers.submit.#{object_name}.#{key}"
defaults << :"helpers.submit.#{key}"
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 0191064326..55dac74d00 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -93,22 +93,22 @@ module ActionView
# select_tag "people", options_from_collection_for_select(@people, "id", "name", "1")
# # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
#
- # select_tag "people", "<option>David</option>".html_safe
+ # select_tag "people", raw("<option>David</option>")
# # => <select id="people" name="people"><option>David</option></select>
#
- # select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>".html_safe
+ # select_tag "count", raw("<option>1</option><option>2</option><option>3</option><option>4</option>")
# # => <select id="count" name="count"><option>1</option><option>2</option>
# # <option>3</option><option>4</option></select>
#
- # select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>".html_safe, multiple: true
+ # select_tag "colors", raw("<option>Red</option><option>Green</option><option>Blue</option>"), multiple: true
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
# # <option>Green</option><option>Blue</option></select>
#
- # select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe
+ # select_tag "locations", raw("<option>Home</option><option selected='selected'>Work</option><option>Out</option>")
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
# # <option>Out</option></select>
#
- # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input', id: 'unique_id'
+ # select_tag "access", raw("<option>Read</option><option>Write</option>"), multiple: true, class: 'form_input', id: 'unique_id'
# # => <select class="form_input" id="unique_id" multiple="multiple" name="access[]"><option>Read</option>
# # <option>Write</option></select>
#
@@ -121,7 +121,7 @@ module ActionView
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something"
# # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
#
- # select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>".html_safe, disabled: true
+ # select_tag "destination", raw("<option>NYC</option><option>Paris</option><option>Rome</option>"), disabled: true
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
# # <option>Paris</option><option>Rome</option></select>
#
@@ -447,7 +447,7 @@ module ActionView
unless tag_options["data-disable-with"] == false || (tag_options["data"] && tag_options["data"][:disable_with] == false)
disable_with_text = tag_options["data-disable-with"]
disable_with_text ||= tag_options["data"][:disable_with] if tag_options["data"]
- disable_with_text ||= value.clone
+ disable_with_text ||= value.to_s.clone
tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
else
tag_options["data"].delete(:disable_with) if tag_options["data"]
@@ -870,10 +870,16 @@ module ActionView
''
when /^post$/i, "", nil
html_options["method"] = "post"
- token_tag(authenticity_token)
+ token_tag(authenticity_token, form_options: {
+ action: html_options["action"],
+ method: "post"
+ })
else
html_options["method"] = "post"
- method_tag(method) + token_tag(authenticity_token)
+ method_tag(method) + token_tag(authenticity_token, form_options: {
+ action: html_options["action"],
+ method: method
+ })
end
if html_options.delete("enforce_utf8") { true }
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 0931a2a4c2..161aa031c6 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -269,6 +269,8 @@ module ActionView
# number_to_human_size(1234567) # => 1.18 MB
# number_to_human_size(1234567890) # => 1.15 GB
# number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567890123456) # => 1.1 PB
+ # number_to_human_size(1234567890123456789) # => 1.07 EB
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index 1c2a400245..c0fc3b820f 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -22,10 +22,10 @@ module ActionView #:nodoc:
# the supplied separator, are HTML escaped unless they are HTML
# safe, and the returned string is marked as HTML safe.
#
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
+ # safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
# # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
#
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
+ # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />")
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep=$,)
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 3256d44e18..3dda47a458 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -23,6 +23,10 @@ module ActionView
def render_component(builder)
builder.check_box + builder.label
end
+
+ def hidden_field_name #:nodoc:
+ "#{super}[]"
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index b87b4281d6..1d3b1ecf0b 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -97,16 +97,20 @@ module ActionView
# Append a hidden field to make sure something will be sent back to the
# server if all radio buttons are unchecked.
if options.fetch('include_hidden', true)
- rendered_collection + hidden_field
+ hidden_field + rendered_collection
else
rendered_collection
end
end
def hidden_field #:nodoc:
- hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]"
+ hidden_name = @html_options[:name] || hidden_field_name
@template_object.hidden_field_tag(hidden_name, "", id: nil)
end
+
+ def hidden_field_name #:nodoc:
+ "#{tag_name(false, @options[:index])}"
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 432693bc23..58ce042f12 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -204,12 +204,12 @@ module ActionView
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
# +plural+ is supplied, it will use that when count is > 1, otherwise
- # it will use the Inflector to determine the plural form.
+ # it will use the Inflector to determine the plural form for the given locale,
+ # which defaults to I18n.locale
#
- # If passed an optional +locale:+ parameter, the word will be pluralized
- # using rules defined for that language (you must define your own
- # inflection rules for languages other than English). See
- # ActiveSupport::Inflector.pluralize
+ # The word will be pluralized using rules defined for the locale
+ # (you must define your own inflection rules for languages other than English).
+ # See ActiveSupport::Inflector.pluralize
#
# pluralize(1, 'person')
# # => 1 person
@@ -217,7 +217,7 @@ module ActionView
# pluralize(2, 'person')
# # => 2 people
#
- # pluralize(3, 'person', 'users')
+ # pluralize(3, 'person', plural: 'users')
# # => 3 users
#
# pluralize(0, 'person')
@@ -225,7 +225,14 @@ module ActionView
#
# pluralize(2, 'Person', locale: :de)
# # => 2 Personen
- def pluralize(count, singular, plural = nil, locale: nil)
+ def pluralize(count, singular, deprecated_plural = nil, plural: nil, locale: I18n.locale)
+ if deprecated_plural
+ ActiveSupport::Deprecation.warn("Passing plural as a positional argument " \
+ "is deprecated and will be removed in Rails 5.1. Use e.g. " \
+ "pluralize(1, 'person', plural: 'people') instead.")
+ plural ||= deprecated_plural
+ end
+
word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular
else
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index baebc34b4b..3a4561a083 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -311,7 +311,11 @@ module ActionView
form_options[:action] = url
form_options[:'data-remote'] = true if remote
- request_token_tag = form_method == 'post' ? token_tag : ''
+ request_token_tag = if form_method == 'post'
+ token_tag(nil, form_options: form_options)
+ else
+ ''
+ end
html_options = convert_options_to_data_attributes(options, html_options)
html_options['type'] = 'submit'
@@ -579,9 +583,9 @@ module ActionView
html_options["data-method"] = method
end
- def token_tag(token=nil)
+ def token_tag(token=nil, form_options: {})
if token != false && protect_against_forgery?
- token ||= form_authenticity_token
+ token ||= form_authenticity_token(form_options: form_options)
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
else
''
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index d3935788ef..6a76d80c47 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -67,7 +67,7 @@ module ActionView
def self.get(details)
if details[:formats]
details = details.dup
- details[:formats] &= Mime::SET.symbols
+ details[:formats] &= Template::Types.symbols
end
@details_keys[details] ||= new
end
@@ -123,6 +123,10 @@ module ActionView
end
alias :find_template :find
+ def find_file(name, prefixes = [], partial = false, keys = [], options = {})
+ @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
+ end
+
def find_all(name, prefixes = [], partial = false, keys = [], options = {})
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
end
diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb
index 7a88f6bc50..f68d2a77ed 100644
--- a/actionview/lib/action_view/path_set.rb
+++ b/actionview/lib/action_view/path_set.rb
@@ -46,15 +46,12 @@ module ActionView #:nodoc:
find_all(*args).first || raise(MissingTemplate.new(self, *args))
end
+ def find_file(path, prefixes = [], *args)
+ _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
+ end
+
def find_all(path, prefixes = [], *args)
- prefixes = [prefixes] if String === prefixes
- prefixes.each do |prefix|
- paths.each do |resolver|
- templates = resolver.find_all(path, prefix, *args)
- return templates unless templates.empty?
- end
- end
- []
+ _find_all path, prefixes, args, false
end
def exists?(path, prefixes, *args)
@@ -72,6 +69,21 @@ module ActionView #:nodoc:
private
+ def _find_all(path, prefixes, args, outside_app)
+ prefixes = [prefixes] if String === prefixes
+ prefixes.each do |prefix|
+ paths.each do |resolver|
+ if outside_app
+ templates = resolver.find_all_anywhere(path, prefix, *args)
+ else
+ templates = resolver.find_all(path, prefix, *args)
+ end
+ return templates unless templates.empty?
+ end
+ end
+ []
+ end
+
def typecast(paths)
paths.map do |path|
case path
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index 1f122f9bc6..aa77a77acf 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -15,7 +15,7 @@ module ActionView
# that new object is called in turn. This abstracts the setup and rendering
# into a separate classes for partials and templates.
class AbstractRenderer #:nodoc:
- delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
+ delegate :find_template, :find_file, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 75217e1630..9d15bbfca7 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -29,7 +29,7 @@ module ActionView
elsif options.key?(:html)
Template::HTML.new(options[:html], formats.first)
elsif options.key?(:file)
- with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
+ with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
Template.new(options[:inline], "inline template", handler, :locals => keys)
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 8604637da2..3ca7f9d220 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -84,7 +84,7 @@ module ActionView
end
def rendered_format
- Mime[lookup_context.rendered_format]
+ Template::Types[lookup_context.rendered_format]
end
private
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index 0105e88a49..ad4c353608 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -2,13 +2,15 @@ module ActionView #:nodoc:
# = Action View Template Handlers
class Template
module Handlers #:nodoc:
+ autoload :Raw, 'action_view/template/handlers/raw'
autoload :ERB, 'action_view/template/handlers/erb'
+ autoload :Html, 'action_view/template/handlers/html'
autoload :Builder, 'action_view/template/handlers/builder'
- autoload :Raw, 'action_view/template/handlers/raw'
def self.extended(base)
base.register_default_template_handler :raw, Raw.new
base.register_template_handler :erb, ERB.new
+ base.register_template_handler :html, Html.new
base.register_template_handler :builder, Builder.new
base.register_template_handler :ruby, :source.to_proc
end
diff --git a/actionview/lib/action_view/template/handlers/html.rb b/actionview/lib/action_view/template/handlers/html.rb
new file mode 100644
index 0000000000..ccaa8d1469
--- /dev/null
+++ b/actionview/lib/action_view/template/handlers/html.rb
@@ -0,0 +1,9 @@
+module ActionView
+ module Template::Handlers
+ class Html < Raw
+ def call(template)
+ "ActionView::OutputBuffer.new #{super}"
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb
index b08fb0870f..760f517431 100644
--- a/actionview/lib/action_view/template/handlers/raw.rb
+++ b/actionview/lib/action_view/template/handlers/raw.rb
@@ -2,9 +2,7 @@ module ActionView
module Template::Handlers
class Raw
def call(template)
- escaped = template.source.gsub(':'.freeze, '\:'.freeze)
-
- '%q:' + escaped + ':;'
+ "#{template.source.inspect};"
end
end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 6ddd2b66b3..8a675cd521 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -126,6 +126,12 @@ module ActionView
end
end
+ def find_all_anywhere(name, prefix, partial=false, details={}, key=nil, locals=[])
+ cached(key, [name, prefix, partial], details, locals) do
+ find_templates(name, prefix, partial, details, true)
+ end
+ end
+
def find_all_with_query(query) # :nodoc:
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
end
@@ -187,15 +193,16 @@ module ActionView
private
- def find_templates(name, prefix, partial, details)
+ def find_templates(name, prefix, partial, details, outside_app_allowed = false)
path = Path.build(name, prefix, partial)
- query(path, details, details[:formats])
+ query(path, details, details[:formats], outside_app_allowed)
end
- def query(path, details, formats)
+ def query(path, details, formats, outside_app_allowed)
query = build_query(path, details)
template_paths = find_template_paths(query)
+ template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
template_paths.map do |template|
handler, format, variant = extract_handler_and_format_and_variant(template, formats)
@@ -210,6 +217,10 @@ module ActionView
end
end
+ def reject_files_external_to_app(files)
+ files.reject { |filename| !inside_path?(@path, filename) }
+ end
+
def find_template_paths(query)
Dir[query].reject do |filename|
File.directory?(filename) ||
@@ -218,6 +229,12 @@ module ActionView
end
end
+ def inside_path?(path, filename)
+ filename = File.expand_path(filename)
+ path = File.join(path, '')
+ filename.start_with?(path)
+ end
+
# Helper for building query glob string based on resolver's pattern.
def build_query(path, details)
query = @pattern.dup
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index be45fcf742..c233d06ccb 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -5,19 +5,12 @@ module ActionView
class Template
class Types
class Type
- cattr_accessor :types
- self.types = Set.new
-
- def self.register(*t)
- types.merge(t.map(&:to_s))
- end
-
- register :html, :text, :js, :css, :xml, :json
+ SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
def self.[](type)
- return type if type.is_a?(self)
-
- if type.is_a?(Symbol) || types.member?(type.to_s)
+ if type.is_a?(self)
+ type
+ else
new(type)
end
end
@@ -28,16 +21,18 @@ module ActionView
@symbol = symbol.to_sym
end
- delegate :to_s, :to_sym, :to => :symbol
+ def to_s
+ @symbol.to_s
+ end
alias to_str to_s
def ref
- to_sym || to_s
+ @symbol
end
+ alias to_sym ref
def ==(type)
- return false if type.blank?
- symbol.to_sym == type.to_sym
+ @symbol == type.to_sym unless type.blank?
end
end
@@ -52,6 +47,10 @@ module ActionView
def self.[](type)
type_klass[type]
end
+
+ def self.symbols
+ type_klass::SET.symbols
+ end
end
end
end
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index f6b5696a13..120962b5aa 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -28,7 +28,7 @@ module ActionView
@response = ActionDispatch::TestResponse.new
@request.env.delete('PATH_INFO')
- @params = {}
+ @params = ActionController::Parameters.new
end
end
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 63a60542d4..2664aca991 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -19,7 +19,7 @@ module ActionView #:nodoc:
private
- def query(path, exts, formats)
+ def query(path, exts, formats, _)
query = ""
EXTENSIONS.each_key do |ext|
query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
@@ -44,7 +44,7 @@ module ActionView #:nodoc:
end
class NullResolver < PathResolver
- def query(path, exts, formats)
+ def query(path, exts, formats, _)
handler, format, variant = extract_handler_and_format_and_variant(path, formats)
[ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, :virtual_path => path.virtual, :format => format, :variant => variant)]
end
diff --git a/actionview/test/fixtures/layouts/render_partial_html.erb b/actionview/test/fixtures/layouts/render_partial_html.erb
new file mode 100644
index 0000000000..d4dbb6c76c
--- /dev/null
+++ b/actionview/test/fixtures/layouts/render_partial_html.erb
@@ -0,0 +1,2 @@
+<%= render :partial => 'test/partialhtml' %>
+<%= yield %>
diff --git a/actionview/test/fixtures/test/_partialhtml.html b/actionview/test/fixtures/test/_partialhtml.html
new file mode 100644
index 0000000000..afe39b730a
--- /dev/null
+++ b/actionview/test/fixtures/test/_partialhtml.html
@@ -0,0 +1 @@
+<h1>partial html</h1> \ No newline at end of file
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index 65c68fc34a..a122fe17c9 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -31,6 +31,27 @@ end
class GoodCustomer < Customer
end
+class TicketType < Struct.new(:name)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ extend ActiveModel::Translation
+
+ def initialize(*args)
+ super
+ @persisted = false
+ end
+
+ def persisted=(boolean)
+ @persisted = boolean
+ end
+
+ def persisted?
+ @persisted
+ end
+
+ attr_accessor :name
+end
+
class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
diff --git a/actionview/test/template/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb
index 86bccdfade..55d62cf692 100644
--- a/actionview/test/template/active_model_helper_test.rb
+++ b/actionview/test/template/active_model_helper_test.rb
@@ -85,7 +85,7 @@ class ActiveModelHelperTest < ActionView::TestCase
def test_field_error_proc
old_proc = ActionView::Base.field_error_proc
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
- %(<div class=\"field_with_errors\">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>).html_safe
+ raw(%(<div class=\"field_with_errors\">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>))
end
assert_dom_equal(
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 1e099d482c..ffaf773c53 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -34,7 +34,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_capture_doesnt_escape_twice
- string = @av.capture { '&lt;em&gt;bar&lt;/em&gt;'.html_safe }
+ string = @av.capture { raw('&lt;em&gt;bar&lt;/em&gt;') }
assert_equal '&lt;em&gt;bar&lt;/em&gt;', string
end
@@ -148,6 +148,19 @@ class CaptureHelperTest < ActionView::TestCase
assert ! content_for?(:something_else)
end
+ def test_content_for_should_be_html_safe_after_flush_empty
+ assert ! content_for?(:title)
+ content_for :title do
+ content_tag(:p, 'title')
+ end
+ assert content_for(:title).html_safe?
+ content_for :title, "", flush: true
+ content_for(:title) do
+ content_tag(:p, 'title')
+ end
+ assert content_for(:title).html_safe?
+ end
+
def test_provide
assert !content_for?(:title)
provide :title, "hi"
@@ -158,7 +171,7 @@ class CaptureHelperTest < ActionView::TestCase
@view_flow = ActionView::OutputFlow.new
provide :title, "hi"
- provide :title, "<p>title</p>".html_safe
+ provide :title, raw("<p>title</p>")
assert_equal "hi<p>title</p>", content_for(:title)
end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 92e77599f4..4678998bdc 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -3207,7 +3207,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_time_tag_with_given_block
- assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ '<span>Right now</span>'.html_safe })
+ assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ raw('<span>Right now</span>') })
end
def test_time_tag_with_different_format
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index b59be8e36c..4f7ea88169 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -202,35 +202,35 @@ class FormCollectionsHelperTest < ActionView::TestCase
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name
- assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 1
+ assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 1
end
test 'collection radio buttons generates a hidden field using the given :name in :html_options' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
- with_collection_radio_buttons :user, :category_ids, collection, :id, :name, {}, { name: "user[other_category_ids][]" }
+ with_collection_radio_buttons :user, :category_ids, collection, :id, :name, {}, { name: "user[other_category_ids]" }
- assert_select "input[type=hidden][name='user[other_category_ids][]'][value='']", count: 1
+ assert_select "input[type=hidden][name='user[other_category_ids]'][value='']", count: 1
end
test 'collection radio buttons generates a hidden field with index if it was provided' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, { index: 322 }
- assert_select "input[type=hidden][name='user[322][category_ids][]'][value='']", count: 1
+ assert_select "input[type=hidden][name='user[322][category_ids]'][value='']", count: 1
end
test 'collection radio buttons does not generate a hidden field if include_hidden option is false' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, include_hidden: false
- assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0
+ assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0
end
test 'collection radio buttons does not generate a hidden field if include_hidden option is false with key as string' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_radio_buttons :user, :category_ids, collection, :id, :name, 'include_hidden' => false
- assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0
+ assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0
end
# COLLECTION CHECK BOXES
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 1f7ff3ca7c..034b8a4bf6 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -128,6 +128,8 @@ class FormHelperTest < ActionView::TestCase
@post_delegator.title = 'Hello World'
@car = Car.new("#000FFF")
+
+ @ticket_type = TicketType.new
end
Routes = ActionDispatch::Routing::RouteSet.new
@@ -136,6 +138,8 @@ class FormHelperTest < ActionView::TestCase
resources :comments
end
+ resources :ticket_types
+
namespace :admin do
resources :posts do
resources :comments
@@ -332,7 +336,7 @@ class FormHelperTest < ActionView::TestCase
def test_label_with_block_and_html
assert_dom_equal(
'<label for="post_terms">Accept <a href="/terms">Terms</a>.</label>',
- label(:post, :terms) { 'Accept <a href="/terms">Terms</a>.'.html_safe }
+ label(:post, :terms) { raw('Accept <a href="/terms">Terms</a>.') }
)
end
@@ -347,7 +351,7 @@ class FormHelperTest < ActionView::TestCase
with_locale :label do
assert_dom_equal(
'<label for="post_body"><b>Write entire text here</b></label>',
- label(:post, :body) { |b| "<b>#{b.translation}</b>".html_safe }
+ label(:post, :body) { |b| raw("<b>#{b.translation}</b>") }
)
end
end
@@ -1600,11 +1604,11 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"<label for='post_active_true'>true</label>" +
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
- "<label for='post_active_false'>false</label>" +
- "<input type='hidden' name='post[active][]' value='' />"
+ "<label for='post_active_false'>false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1622,13 +1626,13 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<label for='post_active_true'>"+
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"true</label>" +
"<label for='post_active_false'>"+
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
- "false</label>" +
- "<input type='hidden' name='post[active][]' value='' />"
+ "false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1648,13 +1652,13 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post_1", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<label for='post_active_true'>"+
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"true</label>" +
"<label for='post_active_false'>"+
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
"false</label>"+
- "<input type='hidden' name='post[active][]' value='' />" +
"<input id='post_id' name='post[id]' type='hidden' value='1' />"
end
@@ -1670,11 +1674,11 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
+ "<input type='hidden' name='post[active]' value='' />" +
"<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" +
"<label for='foo_post_active_true'>true</label>" +
"<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" +
- "<label for='foo_post_active_false'>false</label>" +
- "<input type='hidden' name='post[active][]' value='' />"
+ "<label for='foo_post_active_false'>false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1689,11 +1693,11 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input type='hidden' name='post[1][active]' value='' />" +
"<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" +
"<label for='post_1_active_true'>true</label>" +
"<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" +
- "<label for='post_1_active_false'>false</label>" +
- "<input type='hidden' name='post[1][active][]' value='' />"
+ "<label for='post_1_active_false'>false</label>"
end
assert_dom_equal expected, output_buffer
@@ -1708,13 +1712,13 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
"<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"<label for='post_tag_ids_1'>Tag 1</label>" +
"<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
"<label for='post_tag_ids_2'>Tag 2</label>" +
"<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
- "<label for='post_tag_ids_3'>Tag 3</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "<label for='post_tag_ids_3'>Tag 3</label>"
end
assert_dom_equal expected, output_buffer
@@ -1732,6 +1736,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
"<label for='post_tag_ids_1'>" +
"<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"Tag 1</label>" +
@@ -1740,8 +1745,7 @@ class FormHelperTest < ActionView::TestCase
"Tag 2</label>" +
"<label for='post_tag_ids_3'>" +
"<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
- "Tag 3</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "Tag 3</label>"
end
assert_dom_equal expected, output_buffer
@@ -1762,6 +1766,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post_1", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />"+
"<label for='post_tag_ids_1'>" +
"<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"Tag 1</label>" +
@@ -1771,7 +1776,6 @@ class FormHelperTest < ActionView::TestCase
"<label for='post_tag_ids_3'>" +
"<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
"Tag 3</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"+
"<input id='post_id' name='post[id]' type='hidden' value='1' />"
end
@@ -1788,9 +1792,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
"<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "<label for='foo_post_tag_ids_1'>Tag 1</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "<label for='foo_post_tag_ids_1'>Tag 1</label>"
end
assert_dom_equal expected, output_buffer
@@ -1806,9 +1810,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
+ "<input name='post[1][tag_ids][]' type='hidden' value='' />" +
"<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
- "<label for='post_1_tag_ids_1'>Tag 1</label>" +
- "<input name='post[1][tag_ids][]' type='hidden' value='' />"
+ "<label for='post_1_tag_ids_1'>Tag 1</label>"
end
assert_dom_equal expected, output_buffer
@@ -1872,6 +1876,20 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_lowercase_model_name_default_submit_button_value
+ form_for(@ticket_type) do |f|
+ concat f.submit
+ end
+
+ expected =
+ '<form class="new_ticket_type" id="new_ticket_type" action="/ticket_types" accept-charset="UTF-8" method="post">' +
+ hidden_fields +
+ '<input type="submit" name="commit" value="Create ticket type" data-disable-with="Create ticket type" />' +
+ '</form>'
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_symbol_object_name
form_for(@post, as: "other_name", html: { id: "create-post" }) do |f|
concat f.label(:title, class: 'post_title')
@@ -2239,7 +2257,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts', 'new_post', 'new_post') do
- "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />"
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
assert_dom_equal expected, output_buffer
@@ -2254,7 +2272,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
- "<input name='commit' data-disable-with='Confirm Post changes' type='submit' value='Confirm Post changes' />"
+ "<input name='commit' data-disable-with='Confirm post changes' type='submit' value='Confirm post changes' />"
end
assert_dom_equal expected, output_buffer
@@ -2282,7 +2300,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do
- "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />"
+ "<input name='commit' data-disable-with='Update your post' type='submit' value='Update your post' />"
end
assert_dom_equal expected, output_buffer
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 6b97cec34c..c5b63d33f1 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -588,7 +588,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_select_under_fields_for_with_string_and_given_prompt
@post = Post.new
- options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>".html_safe
+ options = raw("<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>")
output_buffer = fields_for :post, @post do |f|
concat f.select(:category, options, :prompt => 'The prompt')
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index de1eb89dc5..07b3fba754 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -216,19 +216,19 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_select_tag
- actual = select_tag "people", "<option>david</option>".html_safe
+ actual = select_tag "people", raw("<option>david</option>")
expected = %(<select id="people" name="people"><option>david</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_multiple
- actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>".html_safe, multiple: true
+ actual = select_tag "colors", raw("<option>Red</option><option>Blue</option><option>Green</option>"), multiple: true
expected = %(<select id="colors" multiple="multiple" name="colors[]"><option>Red</option><option>Blue</option><option>Green</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_disabled
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, disabled: true
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), disabled: true
expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
@@ -239,37 +239,37 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_select_tag_with_include_blank
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => true
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :include_blank => true
expected = %(<select id="places" name="places"><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_include_blank_false
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: false
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: false
expected = %(<select id="places" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_include_blank_string
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: 'Choose'
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: 'Choose'
expected = %(<select id="places" name="places"><option value="">Choose</option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_prompt
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string"
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "string"
expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_escapes_prompt
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "<script>alert(1337)</script>"
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "<script>alert(1337)</script>"
expected = %(<select id="places" name="places"><option value="">&lt;script&gt;alert(1337)&lt;/script&gt;</option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_prompt_and_include_blank
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string", :include_blank => true
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "string", :include_blank => true
expected = %(<select name="places" id="places"><option value="">string</option><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
@@ -433,9 +433,9 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes")
assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil)
assert_dom_equal %(<input type="checkbox" />), tag(:input, :type => "checkbox", :checked => false)
- assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => true)
- assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>".html_safe, :multiple => true)
- assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => nil)
+ assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), :multiple => true)
+ assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", raw("<option>david</option>"), :multiple => true)
+ assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), :multiple => nil)
end
def test_stringify_symbol_keys
@@ -510,6 +510,13 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_submit_tag_with_symbol_value
+ assert_dom_equal(
+ %(<input data-disable-with="Save" name='commit' type="submit" value="Save" />),
+ submit_tag(:Save)
+ )
+ end
+
def test_button_tag
assert_dom_equal(
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index a1bf0e1a5f..8de0ae2f6f 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -18,10 +18,10 @@ class OutputSafetyHelperTest < ActionView::TestCase
end
test "safe_join should html_escape any items, including the separator, if they are not html_safe" do
- joined = safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
+ joined = safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
assert_equal "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;", joined
- joined = safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
+ joined = safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
assert_equal "<p>foo</p><br /><p>bar</p>", joined
end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 994fd44c52..333e0cca11 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -71,7 +71,7 @@ module RenderTestCases
e = assert_raise ActionView::Template::Error do
@view.render(:template => "with_format", :formats => [:json])
end
- assert_includes(e.message, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:raw, :erb, :builder, :ruby]}.")
+ assert_includes(e.message, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby]}.")
end
def test_render_file_with_locale
@@ -148,6 +148,13 @@ module RenderTestCases
assert_equal "only partial", @view.render("test/partial_only")
end
+ def test_render_outside_path
+ assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
+ assert_raises ActionView::MissingTemplate do
+ @view.render(:template => "../\\../test/abstract_unit.rb")
+ end
+ end
+
def test_render_partial
assert_equal "only partial", @view.render(:partial => "test/partial_only")
end
@@ -467,6 +474,11 @@ module RenderTestCases
@view.render(:file => "test/hello_world", :layout => "layouts/yield_with_render_partial_inside")
end
+ def test_render_partial_with_html_only_extension
+ assert_equal %(<h1>partial html</h1>\nHello world!\n),
+ @view.render(:file => "test/hello_world", :layout => "layouts/render_partial_html")
+ end
+
def test_render_layout_with_block_and_yield
assert_equal %(Content from block!\n),
@view.render(:layout => "layouts/yield_only") { "Content from block!" }
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index d037447567..6f7a78ccef 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -143,10 +143,10 @@ class TagHelperTest < ActionView::TestCase
end
def test_tag_honors_html_safe_with_escaped_array_class
- str = tag('p', :class => ['song>', 'play>'.html_safe])
+ str = tag('p', :class => ['song>', raw('play>')])
assert_equal '<p class="song&gt; play>" />', str
- str = tag('p', :class => ['song>'.html_safe, 'play>'])
+ str = tag('p', :class => [raw('song>'), 'play>'])
assert_equal '<p class="song> play&gt;" />', str
end
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index b057d43ee0..d69d5819b6 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -42,6 +42,10 @@ module ActionView
assert_same view, view
end
+ test "exposes params" do
+ assert params.is_a? ActionController::Parameters
+ end
+
test "exposes view as _view for backwards compatibility" do
assert_same _view, view
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index fae1965ffa..fb98ac6330 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -379,24 +379,36 @@ class TextHelperTest < ActionView::TestCase
assert_equal("1.25 counts", pluralize('1.25', "count"))
assert_equal("1.0 count", pluralize('1.0', "count"))
assert_equal("1.00 count", pluralize('1.00', "count"))
- assert_equal("2 counters", pluralize(2, "count", "counters"))
- assert_equal("0 counters", pluralize(nil, "count", "counters"))
+ assert_equal("2 counters", pluralize(2, "count", plural: "counters"))
+ assert_equal("0 counters", pluralize(nil, "count", plural: "counters"))
assert_equal("2 people", pluralize(2, "person"))
assert_equal("10 buffaloes", pluralize(10, "buffalo"))
assert_equal("1 berry", pluralize(1, "berry"))
assert_equal("12 berries", pluralize(12, "berry"))
end
- def test_pluralization_with_locale
- ActiveSupport::Inflector.inflections(:de) do |inflect|
- inflect.plural(/(person)$/i, '\1en')
- inflect.singular(/(person)en$/i, '\1')
- end
+ def test_localized_pluralization
+ old_locale = I18n.locale
+
+ begin
+ I18n.locale = :de
+
+ ActiveSupport::Inflector.inflections(:de) do |inflect|
+ inflect.irregular 'region', 'regionen'
+ end
- assert_equal("2 People", pluralize(2, "Person", locale: :en))
- assert_equal("2 Personen", pluralize(2, "Person", locale: :de))
+ assert_equal("1 region", pluralize(1, "region"))
+ assert_equal("2 regionen", pluralize(2, "region"))
+ assert_equal("2 regions", pluralize(2, "region", locale: :en))
+ ensure
+ I18n.locale = old_locale
+ end
+ end
- ActiveSupport::Inflector.inflections(:de).clear
+ def test_deprecated_plural_as_positional_argument
+ assert_deprecated do
+ pluralize(2, 'count', 'counters')
+ end
end
def test_cycle_class
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 597936d7ae..38b9284767 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -54,7 +54,7 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
end
- def test_returns_missing_tranlation_message_without_span_wrap
+ def test_returns_missing_translation_message_without_span_wrap
old_value = ActionView::Base.debug_missing_translation
ActionView::Base.debug_missing_translation = false
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 784a48ed8d..3010656166 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -78,7 +78,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_button_to_with_path
assert_dom_equal(
%{<form method="post" action="/article/Hello" class="button_to"><input type="submit" value="Hello" /></form>},
- button_to("Hello", article_path("Hello".html_safe))
+ button_to("Hello", article_path("Hello"))
)
end
@@ -106,7 +106,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_button_to_with_html_safe_URL
- assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
+ assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", raw("http://www.example.com/q1=v1&amp;q2=v2"))
end
def test_button_to_with_query_and_no_name
@@ -232,7 +232,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_link_tag_with_img
- link = link_to("<img src='/favicon.jpg' />".html_safe, "/")
+ link = link_to(raw("<img src='/favicon.jpg' />"), "/")
expected = %{<a href="/"><img src='/favicon.jpg' /></a>}
assert_dom_equal expected, link
end
@@ -358,7 +358,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_link_tag_with_html_safe_string
assert_dom_equal(
%{<a href="/article/Gerd_M%C3%BCller">Gerd Müller</a>},
- link_to("Gerd Müller", article_path("Gerd_Müller".html_safe))
+ link_to("Gerd Müller", article_path("Gerd_Müller"))
)
end
@@ -369,7 +369,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_link_tag_does_not_escape_html_safe_content
assert_dom_equal %{<a href="/">Malicious <script>content</script></a>},
- link_to("Malicious <script>content</script>".html_safe, "/")
+ link_to(raw("Malicious <script>content</script>"), "/")
end
def test_link_to_unless
@@ -380,7 +380,7 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal "<strong>Showing</strong>",
link_to_unless(true, "Showing", url_hash) { |name|
- "<strong>#{name}</strong>".html_safe
+ raw "<strong>#{name}</strong>"
}
assert_equal "test",
@@ -390,8 +390,8 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal %{&lt;b&gt;Showing&lt;/b&gt;}, link_to_unless(true, "<b>Showing</b>", url_hash)
assert_equal %{<a href="/">&lt;b&gt;Showing&lt;/b&gt;</a>}, link_to_unless(false, "<b>Showing</b>", url_hash)
- assert_equal %{<b>Showing</b>}, link_to_unless(true, "<b>Showing</b>".html_safe, url_hash)
- assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, "<b>Showing</b>".html_safe, url_hash)
+ assert_equal %{<b>Showing</b>}, link_to_unless(true, raw("<b>Showing</b>"), url_hash)
+ assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, raw("<b>Showing</b>"), url_hash)
end
def test_link_to_if
@@ -541,13 +541,13 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_to_with_img
assert_dom_equal %{<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>},
- mail_to('feedback@example.com', '<img src="/feedback.png" />'.html_safe)
+ mail_to('feedback@example.com', raw('<img src="/feedback.png" />'))
end
def test_mail_to_with_html_safe_string
assert_dom_equal(
%{<a href="mailto:david@loudthinking.com">david@loudthinking.com</a>},
- mail_to("david@loudthinking.com".html_safe)
+ mail_to(raw("david@loudthinking.com"))
)
end
@@ -582,7 +582,7 @@ class UrlHelperTest < ActiveSupport::TestCase
self.request_forgery
end
- def form_authenticity_token
+ def form_authenticity_token(*args)
"secret"
end
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
index 0cef8cdda0..a3ffb46b3f 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2015 David Heinemeier Hansson
+Copyright (c) 2014-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index eb8091a805..f7e05f7cd3 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2014-2015 David Heinemeier Hansson
+# Copyright (c) 2014-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index e56bc79328..33bd5b4eb3 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -25,7 +25,7 @@ module ActiveJob
# Raised when an unsupported argument type is set as a job argument. We
# currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
- # Bignum and objects that can be represented as GlobalIDs (ex: Active Record).
+ # Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record).
# Raised if you set the key for a Hash something else than a string or
# a symbol. Also raised when trying to serialize an object which can't be
# identified with a Global ID - such as an unpersisted Active Record model.
@@ -34,7 +34,7 @@ module ActiveJob
module Arguments
extend self
# :nodoc:
- TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
+ TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum, BigDecimal ]
# Serializes a set of arguments. Whitelisted types are returned
# as-is. Arrays/Hashes are serialized element by element.
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 35356405b5..0bdeca76a8 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveJob
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index 933972a52b..eb8ad185aa 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -10,7 +10,7 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
[ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
- 'a', true, false,
+ 'a', true, false, BigDecimal.new(5),
[ 1, 'a' ],
{ 'a' => 1 }
].each do |arg|
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index 3ec7a617cf..8573eb1225 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 8d00b3aa27..53206580f0 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -19,6 +19,4 @@ Gem::Specification.new do |s|
s.require_path = 'lib'
s.add_dependency 'activesupport', version
-
- s.add_dependency 'builder', '~> 3.1'
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 4e1b3f7495..b95c174d6e 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index d74a3912d6..4563f860c3 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveModel
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index b66dbf1afe..b64a8299e6 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,7 +10,7 @@ module ActiveModel
included do
extend ActiveModel::Naming
- class_attribute :include_root_in_json
+ class_attribute :include_root_in_json, instance_writer: false
self.include_root_in_json = false
end
diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb
index fe09f63a87..34e09f0aba 100644
--- a/activemodel/lib/active_model/type/time.rb
+++ b/activemodel/lib/active_model/type/time.rb
@@ -38,7 +38,7 @@ module ActiveModel
fast_string_to_time(dummy_time_value) || begin
time_hash = ::Date._parse(dummy_time_value)
return if time_hash[:hour].nil?
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
end
end
end
diff --git a/activemodel/lib/active_model/type/value.rb b/activemodel/lib/active_model/type/value.rb
index 9d1f267b41..0d2d6873a8 100644
--- a/activemodel/lib/active_model/type/value.rb
+++ b/activemodel/lib/active_model/type/value.rb
@@ -84,6 +84,10 @@ module ActiveModel
false
end
+ def map(value) # :nodoc:
+ yield value
+ end
+
def ==(other)
self.class == other.class &&
precision == other.precision &&
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index f23c920d87..8159b9b1d3 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -47,9 +47,10 @@ module ActiveModel
include HelperMethods
attr_accessor :validation_context
+ private :validation_context=
define_callbacks :validate, scope: :name
- class_attribute :_validators
+ class_attribute :_validators, instance_writer: false
self._validators = Hash.new { |h,k| h[k] = [] }
end
diff --git a/activemodel/test/cases/types_test.rb b/activemodel/test/cases/types_test.rb
index f937208580..558c56f157 100644
--- a/activemodel/test/cases/types_test.rb
+++ b/activemodel/test/cases/types_test.rb
@@ -64,6 +64,9 @@ module ActiveModel
time_string = Time.now.utc.strftime("%T")
assert_equal time_string, type.cast(time_string).strftime("%T")
+
+ assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast('2015-06-13T19:45:54+03:00')
+ assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast('06:07:08+09:00')
end
def test_type_cast_datetime_and_timestamp
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 2551841aaf..f3dc26ddb3 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,82 @@
+* Using `references` or `belongs_to` in migrations will always add index
+ for the referenced column by default, without adding `index: true` option
+ to generated migration file. Users can opt out of this by passing
+ `index: false`.
+
+ Fixes #18146.
+
+ *Matthew Draper*, *Prathamesh Sonpatki*
+
+* Run `type` attributes through attributes API type-casting before
+ instantiating the corresponding subclass. This makes it possible to define
+ custom STI mappings.
+
+ Fixes #21986.
+
+ *Yves Senn*
+
+* Don't try to quote functions or expressions passed to `:default` option if
+ they are passed as procs.
+
+ This will generate proper query with the passed function or expression for
+ the default option, instead of trying to quote it in incorrect fashion.
+
+ Example:
+
+ create_table :posts do |t|
+ t.datetime :published_at, default: -> { 'NOW()' }
+ end
+
+ *Ryuta Kamizono*
+
+* Fix regression when loading fixture files with symbol keys.
+
+ Fixes #22584.
+
+ *Yves Senn*
+
+* Use `version` column as primary key for schema_migrations table because
+ `schema_migrations` versions are guaranteed to be unique.
+
+ This makes it possible to use `update_attributes` on models that do
+ not have a primary key.
+
+ *Richard Schneeman*
+
+* Add short-hand methods for text and blob types in MySQL.
+
+ In Pg and Sqlite3, `:text` and `:binary` have variable unlimited length.
+ But in MySQL, these have limited length for each types (ref #21591, #21619).
+ This change adds short-hand methods for each text and blob types.
+
+ Example:
+
+ create_table :foos do |t|
+ t.tinyblob :tiny_blob
+ t.mediumblob :medium_blob
+ t.longblob :long_blob
+ t.tinytext :tiny_text
+ t.mediumtext :medium_text
+ t.longtext :long_text
+ end
+
+ *Ryuta Kamizono*
+
+* Take into account UTC offset when assigning string representation of
+ timestamp with offset specified to attribute of time type.
+
+ *Andrey Novikov*
+
+* When calling `first` with a `limit` argument, return directly from the
+ `loaded?` records if available.
+
+ *Ben Woosley*
+
+* Deprecate sending the `offset` argument to `find_nth`. Please use the
+ `offset` method on relation instead.
+
+ *Ben Woosley*
+
## Rails 5.0.0.beta1 (December 18, 2015) ##
* Order the result of `find(ids)` to match the passed array, if the relation
@@ -43,6 +122,14 @@
defaults without breaking existing migrations, or forcing them to be
rewritten through a deprecation cycle.
+ New migrations specify the Rails version they were written for:
+
+ class AddStatusToOrders < ActiveRecord::Migration[5.0]
+ def change
+ # ...
+ end
+ end
+
*Matthew Draper*, *Ravil Bayramgalin*
* Use bind params for `limit` and `offset`. This will generate significantly
@@ -568,7 +655,7 @@
* Add `ActiveRecord::Relation#in_batches` to work with records and relations
in batches.
- Available options are `of` (batch size), `load`, `begin_at`, and `end_at`.
+ Available options are `of` (batch size), `load`, `start`, and `finish`.
Examples:
@@ -1216,7 +1303,7 @@
*Yves Senn*
-* `find_in_batches` now accepts an `:end_at` parameter that complements the `:start`
+* `find_in_batches` now accepts an `:finish` parameter that complements the `:start`
parameter to specify where to stop batch processing.
*Vipul A M*
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 7c2197229d..1f496cf280 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 264f869c68..ab3846ae65 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -40,6 +40,7 @@ module ActiveRecord
autoload :CounterCache
autoload :DynamicMatchers
autoload :Enum
+ autoload :InternalMetadata
autoload :Explain
autoload :Inheritance
autoload :Integration
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 462b3066ab..f6d8e8a342 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1164,6 +1164,7 @@ module ActiveRecord
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
# Note that this operation instantly fires update SQL without waiting for the save or update call on the
# parent object, unless the parent object is a new record.
+ # This will also run validations and callbacks of associated object(s).
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index f02d146e89..346329c610 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 + [:foreign_type, :polymorphic, :touch, :counter_cache, :optional]
+ super + [:polymorphic, :touch, :counter_cache, :optional]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 9d64ae877b..4de846d12b 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- valid = super + [:as, :foreign_type]
+ valid = super + [:as]
valid += [:through, :source, :source_type] if options[:through]
valid
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 58a9c8ff24..bb96202a22 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -3,7 +3,7 @@
module ActiveRecord::Associations::Builder # :nodoc:
class SingularAssociation < Association #:nodoc:
def self.valid_options(options)
- super + [:dependent, :primary_key, :inverse_of, :required]
+ super + [:foreign_type, :dependent, :primary_key, :inverse_of, :required]
end
def self.define_accessors(model, reflection)
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index deb0f8c9f5..36fc381343 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -66,6 +66,11 @@ module ActiveRecord
through_record = through_association.build(*options_for_through_record)
through_record.send("#{source_reflection.name}=", record)
+
+ if options[:source_type]
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
+ end
+
through_record
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index a6ad09a38a..be65cf318c 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -74,9 +74,8 @@ module ActiveRecord
value = foreign_klass.base_class.name
column = klass.columns_hash[reflection.type.to_s]
- substitute = klass.connection.substitute_at(column)
binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
- constraint = constraint.and table[reflection.type].eq substitute
+ constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new)
end
joins << table.create_join(table, table.create_on(constraint), join_type)
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 45d2c855a5..061628725d 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -9,9 +9,9 @@ module ActiveRecord
end
def cast(value)
- if value.is_a?(Array)
- value.map { |v| cast(v) }
- elsif value.is_a?(Hash)
+ return if value.nil?
+
+ if value.is_a?(Hash)
set_time_zone_without_conversion(super)
elsif value.respond_to?(:in_time_zone)
begin
@@ -19,18 +19,22 @@ module ActiveRecord
rescue ArgumentError
nil
end
+ else
+ map(super) { |t| cast(t) }
end
end
private
def convert_time_to_time_zone(value)
- if value.is_a?(Array)
- value.map { |v| convert_time_to_time_zone(v) }
- elsif value.acts_like?(:time)
+ return if value.nil?
+
+ if value.acts_like?(:time)
value.in_time_zone
- else
+ elsif value.is_a?(::Float)
value
+ else
+ map(value) { |v| convert_time_to_time_zone(v) }
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index fc12c3f45a..bac5a38a5d 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -331,15 +331,30 @@ module ActiveRecord
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
+ indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
+
record.errors.each do |attribute, message|
- if index.nil? || (!reflection.options[:index_errors] && !ActiveRecord::Base.index_nested_attribute_errors)
- attribute = "#{reflection.name}.#{attribute}"
- else
+ if indexed_attribute
attribute = "#{reflection.name}[#{index}].#{attribute}"
+ else
+ attribute = "#{reflection.name}.#{attribute}"
end
errors[attribute] << message
errors[attribute].uniq!
end
+
+ record.errors.details.each_key do |attribute|
+ if indexed_attribute
+ reflection_attribute = "#{reflection.name}[#{index}].#{attribute}"
+ else
+ reflection_attribute = "#{reflection.name}.#{attribute}"
+ end
+
+ record.errors.details[attribute].each do |error|
+ errors.details[reflection_attribute] << error
+ errors.details[reflection_attribute].uniq!
+ end
+ end
else
errors.add(reflection.name)
end
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 3c4ca3d116..5dcc98424a 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -7,18 +7,27 @@ module ActiveRecord
if collection.loaded?
size = collection.size
- timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
+ if size > 0
+ timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
+ end
else
column_type = type_for_attribute(timestamp_column.to_s)
column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
query = collection
+ .unscope(:select)
.select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
.unscope(:order)
result = connection.select_one(query)
- size = result["size"]
- timestamp = column_type.deserialize(result["timestamp"])
+ if result.blank?
+ size = 0
+ timestamp = nil
+ else
+ size = result["size"]
+ timestamp = column_type.deserialize(result["timestamp"])
+ end
+
end
if timestamp
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index d3bc378bea..7a2a1a0e33 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -58,8 +58,8 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
- def select_values(arel, name = nil)
- arel, binds = binds_from_relation arel, []
+ def select_values(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
select_rows(to_sql(arel, binds), name, binds).map(&:first)
end
@@ -106,7 +106,7 @@ module ActiveRecord
exec_query(sql, name, binds)
end
- # Returns the last auto-generated ID from the affected table.
+ # Executes an INSERT query and returns the new record's ID
#
# +id_value+ will be returned unless the value is nil, in
# which case the database will attempt to calculate the last inserted
@@ -115,20 +115,24 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
- value = exec_insert(sql, name, binds, pk, sequence_name)
+ sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
+ value = exec_insert(sql, name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end
+ alias create insert
+ alias insert_sql insert
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
exec_update(to_sql(arel, binds), name, binds)
end
+ alias update_sql update
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
exec_delete(to_sql(arel, binds), name, binds)
end
+ alias delete_sql delete
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
@@ -296,14 +300,15 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- columns = schema_cache.columns_hash(table_name)
+ fixture = fixture.stringify_keys
+ columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
if column = columns[name]
type = lookup_cast_type_from_column(column)
Relation::QueryAttribute.new(name, value, type)
else
- raise Fixture::FixtureError, %(table "#{table_name}" has no column named "#{name}".)
+ raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
end
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
@@ -369,24 +374,8 @@ module ActiveRecord
exec_query(sql, name, binds, prepare: true)
end
- # Returns the last auto-generated ID from the affected table.
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- execute(sql, name)
- id_value
- end
-
- # Executes the update statement and returns the number of rows affected.
- def update_sql(sql, name = nil)
- execute(sql, name)
- end
-
- # Executes the delete statement and returns the number of rows affected.
- def delete_sql(sql, name = nil)
- update_sql(sql, name)
- end
-
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
- [sql, binds]
+ [sql, binds, pk, sequence_name]
end
def last_inserted_id(result)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index bcc41acaa1..7e3760d34b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -102,9 +102,13 @@ module ActiveRecord
quote_table_name("#{table}.#{attr}")
end
- def quote_default_expression(value, column) #:nodoc:
- value = lookup_cast_type(column.sql_type).serialize(value)
- quote(value)
+ def quote_default_expression(value, column) # :nodoc:
+ if value.is_a?(Proc)
+ value.call
+ else
+ value = lookup_cast_type(column.sql_type).serialize(value)
+ quote(value)
+ end
end
def quoted_true
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 1cda23dc1d..690e0ba957 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -69,7 +69,7 @@ module ActiveRecord
def initialize(
name,
polymorphic: false,
- index: false,
+ index: true,
foreign_key: false,
type: :integer,
**options
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 797662d07c..a95109fdae 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -76,11 +76,17 @@ module ActiveRecord
def schema_default(column)
type = lookup_cast_type_from_column(column)
default = type.deserialize(column.default)
- unless default.nil?
+ if default.nil?
+ schema_expression(column)
+ else
type.type_cast_for_schema(default)
end
end
+ def schema_expression(column)
+ "-> { #{column.default_function.inspect} }" if column.default_function
+ end
+
def schema_collation(column)
column.collation.inspect if column.collation
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 7bf548fcba..70868ebd03 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -968,6 +968,10 @@ module ActiveRecord
ActiveRecord::SchemaMigration.create_table
end
+ def initialize_internal_metadata_table
+ ActiveRecord::InternalMetadata.create_table
+ end
+
def assume_migrated_upto_version(version, migrations_paths)
migrations_paths = Array(migrations_paths)
version = version.to_i
@@ -1028,11 +1032,12 @@ module ActiveRecord
end
# Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
+ # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
# require the order columns appear in the SELECT.
#
# columns_for_distinct("posts.id", ["posts.created_at desc"])
- def columns_for_distinct(columns, orders) #:nodoc:
+ #
+ def columns_for_distinct(columns, orders) # :nodoc:
columns
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 295a7bed87..14d04a6388 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -33,6 +33,7 @@ module ActiveRecord
class NullTransaction #:nodoc:
def initialize; end
+ def state; end
def closed?; true; end
def open?; false; end
def joinable?; false; end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 3b8d1c1a9f..d9b42d4283 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -23,6 +23,7 @@ module ActiveRecord
autoload :TableDefinition
autoload :Table
autoload :AlterTable
+ autoload :ReferenceDefinition
end
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
@@ -310,12 +311,6 @@ module ActiveRecord
{}
end
- # Returns a bind substitution value given a bind +column+
- # NOTE: The column param is currently being used by the sqlserver-adapter
- def substitute_at(column, _unused = 0)
- Arel::Nodes::BindParam.new
- end
-
# REFERENTIAL INTEGRITY ====================================
# Override to turn off referential integrity while executing <tt>&block</tt>.
@@ -391,19 +386,17 @@ module ActiveRecord
def release_savepoint(name = nil)
end
- def case_sensitive_modifier(node, table_attribute)
- node
- end
-
def case_sensitive_comparison(table, attribute, column, value)
- table_attr = table[attribute]
- value = case_sensitive_modifier(value, table_attr) unless value.nil?
- table_attr.eq(value)
+ if value.nil?
+ table[attribute].eq(value)
+ else
+ table[attribute].eq(Arel::Nodes::BindParam.new)
+ end
end
def case_insensitive_comparison(table, attribute, column, value)
if can_perform_case_insensitive_comparison_for?(column)
- table[attribute].lower.eq(table.lower(value))
+ table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
else
case_sensitive_comparison(table, attribute, column, value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 5ebd8a8dcb..3e84786be0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,7 +1,9 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/mysql/column'
require 'active_record/connection_adapters/mysql/schema_creation'
require 'active_record/connection_adapters/mysql/schema_definitions'
require 'active_record/connection_adapters/mysql/schema_dumper'
+require 'active_record/connection_adapters/mysql/type_metadata'
require 'active_support/core_ext/string/strip'
@@ -19,91 +21,6 @@ module ActiveRecord
MySQL::SchemaCreation.new(self)
end
- class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
-
- def initialize(*)
- super
- assert_valid_default(default)
- extract_default
- end
-
- def extract_default
- if blob_or_text_column?
- @default = null || strict ? nil : ''
- elsif missing_default_forged_as_empty_string?(default)
- @default = nil
- end
- end
-
- def has_default?
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
- super
- end
-
- def blob_or_text_column?
- sql_type =~ /blob/i || type == :text
- end
-
- def unsigned?
- /unsigned/ === sql_type
- end
-
- def case_sensitive?
- collation && !collation.match(/_ci$/)
- end
-
- def auto_increment?
- extra == 'auto_increment'
- end
-
- private
-
- # MySQL misreports NOT NULL column default when none is given.
- # We can't detect this for columns which may have a legitimate ''
- # default (string) but we can for others (integer, datetime, boolean,
- # and the rest).
- #
- # Test whether the column has default '', is not null, and is not
- # a type allowing default ''.
- def missing_default_forged_as_empty_string?(default)
- type != :string && !null && default == ''
- end
-
- def assert_valid_default(default)
- if blob_or_text_column? && default.present?
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- end
- end
-
- class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :extra, :strict
-
- def initialize(type_metadata, extra: "", strict: false)
- super(type_metadata)
- @type_metadata = type_metadata
- @extra = extra
- @strict = strict
- end
-
- def ==(other)
- other.is_a?(MysqlTypeMetadata) &&
- attributes_for_hash == other.attributes_for_hash
- end
- alias eql? ==
-
- def hash
- attributes_for_hash.hash
- end
-
- protected
-
- def attributes_for_hash
- [self.class, @type_metadata, extra, strict]
- end
- end
-
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -247,7 +164,7 @@ module ActiveRecord
end
def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- Column.new(field, default, sql_type_metadata, null, default_function, collation)
+ MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -406,11 +323,6 @@ module ActiveRecord
yield execute(sql, name)
end
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.affected_rows
- end
-
def begin_db_transaction
execute "BEGIN"
end
@@ -520,6 +432,7 @@ module ActiveRecord
end
def table_exists?(table_name)
+ # Update lib/active_record/internal_metadata.rb when this gets removed
ActiveSupport::Deprecation.warn(<<-MSG.squish)
#table_exists? currently checks both tables and views.
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
@@ -582,12 +495,16 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name)#:nodoc:
+ def columns(table_name) # :nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
+ else
+ new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ end
end
end
end
@@ -636,6 +553,7 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
+ create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
@@ -764,16 +682,11 @@ module ActiveRecord
SQL
end
- def case_sensitive_modifier(node, table_attribute)
- node = Arel::Nodes.build_quoted node, table_attribute
- Arel::Nodes::Bin.new(node)
- end
-
def case_sensitive_comparison(table, attribute, column, value)
- if column.case_sensitive?
- table[attribute].eq(value)
- else
+ if value.nil? || column.case_sensitive?
super
+ else
+ table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
end
end
@@ -781,10 +694,25 @@ module ActiveRecord
if column.case_sensitive?
super
else
- table[attribute].eq(value)
+ table[attribute].eq(Arel::Nodes::BindParam.new)
end
end
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
+ # distinct queries, and requires that the ORDER BY include the distinct column.
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
+ def columns_for_distinct(columns, orders) # :nodoc:
+ order_columns = orders.reject(&:blank?).map { |s|
+ # Convert Arel node to string
+ s = s.to_sql unless s.is_a?(String)
+ # Remove any ASC/DESC modifiers
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
+
+ [super, *order_columns].join(', ')
+ end
+
def strict_mode?
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
@@ -837,7 +765,7 @@ module ActiveRecord
def register_integer_type(mapping, key, options) # :nodoc:
mapping.register_type(key) do |sql_type|
- if /unsigned/i =~ sql_type
+ if /\bunsigned\z/ === sql_type
Type::UnsignedInteger.new(options)
else
Type::Integer.new(options)
@@ -854,7 +782,7 @@ module ActiveRecord
end
def fetch_type_metadata(sql_type, extra = "")
- MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
end
def add_index_length(option_strings, column_names, options = {})
@@ -964,11 +892,13 @@ module ActiveRecord
subsubselect = select.clone
subsubselect.projections = [key]
+ # Materialize subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subsubselect.distinct unless select.limit || select.offset || select.orders.any?
+
subselect = Arel::SelectManager.new(select.engine)
subselect.project Arel.sql(key.name)
- # Materialized subquery by adding distinct
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subselect.from subsubselect.distinct.as('__active_record_temp')
+ subselect.from subsubselect.as('__active_record_temp')
end
def mariadb?
@@ -1031,9 +961,12 @@ module ActiveRecord
end
end
+ def create_table_info_cache # :nodoc:
+ @create_table_info_cache ||= {}
+ end
+
def create_table_info(table_name) # :nodoc:
- @create_table_info_cache = {}
- @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
new file mode 100644
index 0000000000..9c45fdd44a
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ class Column < ConnectionAdapters::Column # :nodoc:
+ delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
+
+ def initialize(*)
+ super
+ assert_valid_default
+ extract_default
+ end
+
+ def has_default?
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
+ super
+ end
+
+ def blob_or_text_column?
+ /\A(?:tiny|medium|long)?blob\b/ === sql_type || type == :text
+ end
+
+ def unsigned?
+ /\bunsigned\z/ === sql_type
+ end
+
+ def case_sensitive?
+ collation && collation !~ /_ci\z/
+ end
+
+ def auto_increment?
+ extra == 'auto_increment'
+ end
+
+ private
+
+ def extract_default
+ if blob_or_text_column?
+ @default = null || strict ? nil : ''
+ end
+ end
+
+ def assert_valid_default
+ if blob_or_text_column? && default.present?
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index ca7dfda80d..157e75dbf7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -11,6 +11,30 @@ module ActiveRecord
args.each { |name| column(name, :blob, options) }
end
+ def tinyblob(*args, **options)
+ args.each { |name| column(name, :tinyblob, options) }
+ end
+
+ def mediumblob(*args, **options)
+ args.each { |name| column(name, :mediumblob, options) }
+ end
+
+ def longblob(*args, **options)
+ args.each { |name| column(name, :longblob, options) }
+ end
+
+ def tinytext(*args, **options)
+ args.each { |name| column(name, :tinytext, options) }
+ end
+
+ def mediumtext(*args, **options)
+ args.each { |name| column(name, :mediumtext, options) }
+ end
+
+ def longtext(*args, **options)
+ args.each { |name| column(name, :longtext, options) }
+ end
+
def json(*args, **options)
args.each { |name| column(name, :json, options) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
new file mode 100644
index 0000000000..e1e3f7b472
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
+ attr_reader :extra, :strict
+
+ def initialize(type_metadata, extra: "", strict: false)
+ super(type_metadata)
+ @type_metadata = type_metadata
+ @extra = extra
+ @strict = strict
+ end
+
+ def ==(other)
+ other.is_a?(MySQL::TypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
+
+ def attributes_for_hash
+ [self.class, @type_metadata, extra, strict]
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index f4686b680c..c3c5b660fd 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -99,33 +99,15 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
#++
- # FIXME: re-enable the following once a "better" query_cache solution is in core
- #
- # The overrides below perform much better than the originals in AbstractAdapter
- # because we're able to take advantage of mysql2's lazy-loading capabilities
- #
- # # Returns a record hash with the column names as keys and column values
- # # as values.
- # def select_one(sql, name = nil)
- # result = execute(sql, name)
- # result.each(as: :hash) do |r|
- # return r
- # end
- # end
- #
- # # Returns a single value from a record
- # def select_value(sql, name = nil)
- # result = execute(sql, name)
- # if first = result.first
- # first.first
- # end
- # end
- #
- # # Returns an array of the values of the first column in a select:
- # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
- # def select_values(sql, name = nil)
- # execute(sql, name).map { |row| row.first }
- # end
+ # Returns a record hash with the column names as keys and column values
+ # as values.
+ def select_one(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation(arel, binds)
+ execute(to_sql(arel, binds), name).each(as: :hash) do |row|
+ @connection.next_result while @connection.more_results?
+ return row
+ end
+ end
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
@@ -154,12 +136,6 @@ module ActiveRecord
alias exec_without_stmt exec_query
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- super
- id_value || @connection.last_id
- end
- alias :create :insert_sql
-
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
execute to_sql(sql, binds), name
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 0e0c0e993a..6c15facf3b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -52,8 +52,8 @@ module ActiveRecord
end
end
- def select_values(arel, name = nil)
- arel, binds = binds_from_relation arel, []
+ def select_values(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
execute_and_clear(sql, name, binds) do |result|
if result.nfields > 0
@@ -72,28 +72,6 @@ module ActiveRecord
end
end
- # Executes an INSERT query and returns the new record's ID
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- unless pk
- # Extract the table from the insert sql. Yuck.
- table_ref = extract_table_ref_from_insert_sql(sql)
- pk = primary_key(table_ref) if table_ref
- end
-
- if pk && use_insert_returning?
- select_value("#{sql} RETURNING #{quote_column_name(pk)}")
- elsif pk
- super
- last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
- else
- super
- end
- end
-
- def create
- super.insert
- end
-
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# The internal PostgreSQL identifier of the BYTEA data type.
@@ -174,7 +152,7 @@ module ActiveRecord
end
alias :exec_update :exec_delete
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
unless pk
# Extract the table from the insert sql. Yuck.
table_ref = extract_table_ref_from_insert_sql(sql)
@@ -185,7 +163,7 @@ module ActiveRecord
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
end
- [sql, binds]
+ super
end
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
@@ -202,11 +180,6 @@ module ActiveRecord
end
end
- # Executes an UPDATE query and returns the number of affected tuples.
- def update_sql(sql, name = nil)
- super.cmd_tuples
- end
-
# Begins a transaction.
def begin_db_transaction
execute "BEGIN"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index 25961a9869..87593ef704 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -50,6 +50,10 @@ module ActiveRecord
"[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
end
+ def map(value, &block)
+ value.map(&block)
+ end
+
private
def type_cast_array(value, method)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index fc201f8fb9..a8d2310035 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -6,6 +6,7 @@ module ActiveRecord
module OID # :nodoc:
class Range < Type::Value # :nodoc:
attr_reader :subtype, :type
+ delegate :user_input_in_time_zone, to: :subtype
def initialize(subtype, type = :range)
@subtype = subtype
@@ -18,7 +19,7 @@ module ActiveRecord
def cast_value(value)
return if value == 'empty'
- return value if value.is_a?(::Range)
+ return value unless value.is_a?(::String)
extracted = extract_bounds(value)
from = type_cast_single extracted[:from]
@@ -46,6 +47,12 @@ module ActiveRecord
other.type == type
end
+ def map(value) # :nodoc:
+ new_begin = yield(value.begin)
+ new_end = yield(value.end)
+ ::Range.new(new_begin, new_end, value.exclude_end?)
+ end
+
private
def type_cast_single(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index d5879ea7df..c1c77a967e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -55,10 +55,11 @@ module ActiveRecord
end
end
- # Does not quote function default values for UUID columns
- def quote_default_expression(value, column) #:nodoc:
- if column.type == :uuid && value =~ /\(\)/
- value
+ def quote_default_expression(value, column) # :nodoc:
+ if value.is_a?(Proc)
+ value.call
+ elsif column.type == :uuid && value =~ /\(\)/
+ value # Does not quote function default values for UUID columns
elsif column.respond_to?(:array?)
value = type_cast_from_column(column, value)
quote(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index a4f0742516..cc7721ddd8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -9,7 +9,7 @@ module ActiveRecord
spec[:id] = ':bigserial'
elsif column.type == :uuid
spec[:id] = ':uuid'
- spec[:default] = column.default_function.inspect
+ spec[:default] = schema_default(column) || 'nil'
else
spec[:id] = column.type.inspect
spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
@@ -41,12 +41,8 @@ module ActiveRecord
end
end
- def schema_default(column)
- if column.default_function
- column.default_function.inspect unless column.serial?
- else
- super
- end
+ def schema_expression(column)
+ super unless column.serial?
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index a1ec570042..2de6fbfaf0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -390,12 +390,12 @@ module ActiveRecord
"average" => "avg",
}
- protected
+ # Returns the version of the connected PostgreSQL server.
+ def postgresql_version
+ @connection.server_version
+ end
- # Returns the version of the connected PostgreSQL server.
- def postgresql_version
- @connection.server_version
- end
+ protected
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
FOREIGN_KEY_VIOLATION = "23503"
@@ -512,8 +512,13 @@ module ActiveRecord
def extract_value_from_default(default) # :nodoc:
case default
# Quoted types
- when /\A[\(B]?'(.*)'::/m
- $1.gsub("''".freeze, "'".freeze)
+ when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
+ # The default 'now'::date is CURRENT_DATE
+ if $1 == "now".freeze && $2 == "date".freeze
+ nil
+ else
+ $1.gsub("''".freeze, "'".freeze)
+ end
# Boolean types
when 'true'.freeze, 'false'.freeze
default
@@ -535,7 +540,7 @@ module ActiveRecord
end
def has_default_function?(default_value, default) # :nodoc:
- !default_value && (%r{\w+\(.*\)} === default)
+ !default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default)
end
def load_additional_types(type_map, oids = nil) # :nodoc:
@@ -690,15 +695,7 @@ module ActiveRecord
end
# Returns the current ID of a table's sequence.
- def last_insert_id(sequence_name) #:nodoc:
- Integer(last_insert_id_value(sequence_name))
- end
-
- def last_insert_id_value(sequence_name)
- last_insert_id_result(sequence_name).rows.first.first
- end
-
- def last_insert_id_result(sequence_name) #:nodoc:
+ def last_insert_id_result(sequence_name) # :nodoc:
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 163cbb875f..d1893f35f5 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -280,22 +280,6 @@ module ActiveRecord
log(sql, name) { @connection.execute(sql) }
end
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.changes
- end
-
- def delete_sql(sql, name = nil) #:nodoc:
- sql += " WHERE 1=1" unless sql =~ /WHERE/i
- super sql, name
- end
-
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- super
- id_value || @connection.last_insert_row_id
- end
- alias :create :insert_sql
-
def select_rows(sql, name = nil, binds = [])
exec_query(sql, name, binds).rows
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1250f8a3c3..475a298467 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -275,7 +275,7 @@ module ActiveRecord
def relation # :nodoc:
relation = Relation.create(self, arel_table, predicate_builder)
- if finder_needs_type_condition?
+ if finder_needs_type_condition? && !ignore_default_scope?
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
else
relation
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 9e7d391c70..1b6817554d 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -97,8 +97,8 @@ module ActiveRecord
#
# ==== Examples
#
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
+ # # Increment the posts_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:posts_count, 5)
def increment_counter(counter_name, id)
update_counters(id, counter_name => 1)
end
@@ -115,8 +115,8 @@ module ActiveRecord
#
# ==== Examples
#
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
+ # # Decrement the posts_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:posts_count, 5)
def decrement_counter(counter_name, id)
update_counters(id, counter_name => -1)
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 7ded96f8fb..903c63a7db 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -95,7 +95,7 @@ module ActiveRecord
module Enum
def self.extended(base) # :nodoc:
- base.class_attribute(:defined_enums)
+ base.class_attribute(:defined_enums, instance_writer: false)
base.defined_enums = {}
end
@@ -105,9 +105,10 @@ module ActiveRecord
end
class EnumType < Type::Value # :nodoc:
- def initialize(name, mapping)
+ def initialize(name, mapping, subtype)
@name = name
@mapping = mapping
+ @subtype = subtype
end
def cast(value)
@@ -124,7 +125,7 @@ module ActiveRecord
def deserialize(value)
return if value.nil?
- mapping.key(value.to_i)
+ mapping.key(subtype.deserialize(value))
end
def serialize(value)
@@ -139,7 +140,7 @@ module ActiveRecord
protected
- attr_reader :name, :mapping
+ attr_reader :name, :mapping, :subtype
end
def enum(definitions)
@@ -158,7 +159,9 @@ module ActiveRecord
detect_enum_conflict!(name, name)
detect_enum_conflict!(name, "#{name}=")
- attribute name, EnumType.new(name, enum_values)
+ decorate_attribute_type(name, :enum) do |subtype|
+ EnumType.new(name, enum_values, subtype)
+ end
_enum_methods_module.module_eval do
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
@@ -187,7 +190,7 @@ module ActiveRecord
# scope :active, -> { where status: 0 }
klass.send(:detect_enum_conflict!, name, value_method_name, true)
- klass.scope value_method_name, -> { klass.where name => value }
+ klass.scope value_method_name, -> { where(name => value) }
end
end
defined_enums[name.to_s] = enum_values
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index ecf4046bff..b4f2f66e1c 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveRecord
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 6259c4cd33..3a17f74b1d 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -167,6 +167,7 @@ module ActiveRecord
end
def find_sti_class(type_name)
+ type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
subclass = begin
if store_full_sti_class
ActiveSupport::Dependencies.constantize(type_name)
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
new file mode 100644
index 0000000000..10fee4dca2
--- /dev/null
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -0,0 +1,51 @@
+require 'active_record/scoping/default'
+require 'active_record/scoping/named'
+
+module ActiveRecord
+ # This class is used to create a table that keeps track of values and keys such
+ # as which environment migrations were run in.
+ class InternalMetadata < ActiveRecord::Base # :nodoc:
+ # Keys in mysql are limited to 191 characters, due to this no adapter can
+ # use a longer key
+ KEY_LIMIT = 191
+
+ class << self
+ def primary_key
+ "key"
+ end
+
+ def table_name
+ "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
+ end
+
+ def index_name
+ "#{table_name_prefix}unique_#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
+ end
+
+ def []=(key, value)
+ first_or_initialize(key: key).update_attributes!(value: value)
+ end
+
+ def [](key)
+ where(key: key).pluck(:value).first
+ end
+
+ def table_exists?
+ ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
+ end
+
+ # Creates an internal metadata table with columns +key+ and +value+
+ def create_table
+ unless table_exists?
+ connection.create_table(table_name, id: false) do |t|
+ t.column :key, :string, null: false, limit: KEY_LIMIT
+ t.column :value, :string
+ t.index :key, unique: true, name: index_name
+
+ t.timestamps
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index ba238ea142..4419a7b1e7 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -126,9 +126,9 @@ module ActiveRecord
class PendingMigrationError < MigrationError#:nodoc:
def initialize(message = nil)
if !message && defined?(Rails.env)
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}")
elsif !message
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate")
else
super
end
@@ -143,6 +143,40 @@ module ActiveRecord
end
end
+ class NoEnvironmentInSchemaError < MigrationError #:nodoc:
+ def initialize
+ msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rails db:environment:set"
+ if defined?(Rails.env)
+ super("#{msg} RAILS_ENV=#{::Rails.env}")
+ else
+ super(msg)
+ end
+ end
+ end
+
+ class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
+ def initialize(env = "production")
+ msg = "You are attempting to run a destructive action against your '#{env}' database\n"
+ msg << "If you are sure you want to continue, run the same command with the environment variable\n"
+ msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
+ super(msg)
+ end
+ end
+
+ class EnvironmentMismatchError < ActiveRecordError
+ def initialize(current: nil, stored: nil)
+ msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
+ msg << "You are running in `#{ current }` environment."
+ msg << "If you are sure you want to continue, first set the environment using:\n\n"
+ msg << "\tbin/rails db:environment:set"
+ if defined?(Rails.env)
+ super("#{msg} RAILS_ENV=#{::Rails.env}")
+ else
+ super(msg)
+ end
+ end
+ end
+
# = Active Record Migrations
#
# Migrations can manage the evolution of a schema used by several physical
@@ -1078,6 +1112,7 @@ module ActiveRecord
validate(@migrations)
Base.connection.initialize_schema_migrations_table
+ Base.connection.initialize_internal_metadata_table
end
def current_version
@@ -1135,45 +1170,58 @@ module ActiveRecord
private
+ # Used for running a specific migration.
def run_without_lock
migration = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
- unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
- raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
- end
- end
+ execute_migration_in_transaction(migration, @direction)
+
+ record_environment
end
+ # Used for running multiple migrations up to or down to a certain value.
def migrate_without_lock
- if !target && @target_version && @target_version > 0
+ if invalid_target?
raise UnknownMigrationVersionError.new(@target_version)
end
runnable.each do |migration|
- Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
-
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? "this and " : ""
- raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
- end
+ execute_migration_in_transaction(migration, @direction)
end
+
+ record_environment
+ end
+
+ # Stores the current environment in the database.
+ def record_environment
+ return if down?
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
def ran?(migration)
migrated.include?(migration.version.to_i)
end
+ # Return true if a valid version is not provided.
+ def invalid_target?
+ !target && @target_version && @target_version > 0
+ end
+
def execute_migration_in_transaction(migration, direction)
+ return if down? && !migrated.include?(migration.version.to_i)
+ return if up? && migrated.include?(migration.version.to_i)
+
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
+
ddl_transaction(migration) do
migration.migrate(direction)
record_version_state_after_migrating(migration.version)
end
+ rescue => e
+ msg = "An error has occurred, "
+ msg << "this and " if use_transaction?(migration)
+ msg << "all later migrations canceled:\n\n#{e}"
+ raise StandardError, msg, e.backtrace
end
def target
@@ -1202,10 +1250,27 @@ module ActiveRecord
ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
else
migrated << version
- ActiveRecord::SchemaMigration.create!(:version => version.to_s)
+ ActiveRecord::SchemaMigration.create!(version: version.to_s)
end
end
+ def self.last_stored_environment
+ return nil if current_version == 0
+ raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
+
+ environment = ActiveRecord::InternalMetadata[:environment]
+ raise NoEnvironmentInSchemaError unless environment
+ environment
+ end
+
+ def self.current_environment
+ ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ end
+
+ def self.protected_environment?
+ ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
+ end
+
def up?
@direction == :up
end
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 831bfa2df3..5d742b523b 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -5,6 +5,12 @@ module ActiveRecord
module FourTwoShared
module TableDefinition
+ def references(*, **options)
+ options[:index] ||= false
+ super
+ end
+ alias :belongs_to :references
+
def timestamps(*, **options)
options[:null] = true if options[:null].nil?
super
@@ -24,6 +30,12 @@ module ActiveRecord
end
end
+ def add_reference(*, **options)
+ options[:index] ||= false
+ super
+ end
+ alias :add_belongs_to :add_reference
+
def add_timestamps(*, **options)
options[:null] = true if options[:null].nil?
super
@@ -41,8 +53,9 @@ module ActiveRecord
end
def remove_index(table_name, options = {})
- index_name = index_name_for_remove(table_name, options)
- execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
+ options = { column: options } unless options.is_a?(Hash)
+ options[:name] = index_name_for_remove(table_name, options)
+ super(table_name, options)
end
private
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index a6a68f3d4b..722d7b5fce 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -44,6 +44,19 @@ module ActiveRecord
##
# :singleton-method:
+ # Accessor for the name of the internal metadata table. By default, the value is "active_record_internal_metadatas"
+ class_attribute :internal_metadata_table_name, instance_accessor: false
+ self.internal_metadata_table_name = "active_record_internal_metadatas"
+
+ ##
+ # :singleton-method:
+ # Accessor for an array of names of environments where destructive actions should be prohibited. By default,
+ # the value is ["production"]
+ class_attribute :protected_environments, instance_accessor: false
+ self.protected_environments = ["production"]
+
+ ##
+ # :singleton-method:
# Indicates whether table names should be the pluralized versions of the corresponding class names.
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
@@ -242,7 +255,18 @@ module ActiveRecord
@attribute_types ||= Hash.new(Type::Value.new)
end
- def type_for_attribute(attr_name) # :nodoc:
+ # Returns the type of the attribute with the given name, after applying
+ # all modifiers. This method is the only valid source of information for
+ # anything related to the types of a model's attributes. This method will
+ # access the database and load the model's schema if it is required.
+ #
+ # The return value of this method will implement the interface described
+ # by ActiveModel::Type::Value (though the object itself may not subclass
+ # it).
+ #
+ # +attr_name+ The name of the attribute to retrieve the type for. Must be
+ # a string
+ def type_for_attribute(attr_name)
attribute_types[attr_name]
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index c5a1488588..0d5a8e6f25 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -542,7 +542,7 @@ module ActiveRecord
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
- has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
+ will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
end
# Determines if a record with the particular +attributes+ should be
@@ -551,7 +551,8 @@ module ActiveRecord
#
# Returns false if there is a +destroy_flag+ on the attributes.
def call_reject_if(association_name, attributes)
- return false if has_destroy_flag?(attributes)
+ return false if will_be_destroyed?(association_name, attributes)
+
case callback = self.nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
@@ -560,6 +561,15 @@ module ActiveRecord
end
end
+ # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
+ def will_be_destroyed?(association_name, attributes)
+ allow_destroy?(association_name) && has_destroy_flag?(attributes)
+ end
+
+ def allow_destroy?(association_name)
+ self.nested_attributes_options[association_name][:allow_destroy]
+ end
+
def raise_nested_attributes_record_not_found!(association_name, record_id)
model = self.class._reflect_on_association(association_name).klass.name
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index f5e69ec4fb..38916f7376 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -57,8 +57,10 @@ module ActiveRecord
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
require "active_record/base"
- console = ActiveSupport::Logger.new(STDERR)
- Rails.logger.extend ActiveSupport::Logger.broadcast console
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
+ console = ActiveSupport::Logger.new(STDERR)
+ Rails.logger.extend ActiveSupport::Logger.broadcast console
+ end
end
runner do
@@ -69,6 +71,7 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
+ self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 9b59ee995a..69a7838001 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -1,6 +1,16 @@
require 'active_record'
db_namespace = namespace :db do
+ desc "Set the environment value for the database"
+ task "environment:set" => [:environment, :load_config] do
+ ActiveRecord::InternalMetadata.create_table
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ end
+
+ task :check_protected_environments => [:environment, :load_config] do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+
task :load_config do
ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
@@ -18,24 +28,28 @@ db_namespace = namespace :db do
end
namespace :drop do
- task :all => :load_config do
+ task :all => [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.'
- task :drop => [:load_config] do
+ task :drop => [:load_config, :check_protected_environments] do
+ db_namespace["drop:_unsafe"].invoke
+ end
+
+ task "drop:_unsafe" => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.drop_current
end
namespace :purge do
- task :all => :load_config do
+ task :all => [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.purge_all
end
end
# desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
- task :purge => [:load_config] do
+ task :purge => [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.purge_current
end
@@ -288,7 +302,7 @@ db_namespace = namespace :db do
end
desc "Recreates the databases from the structure.sql file"
- task :load => [:load_config] do
+ task :load => [:environment, :load_config] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
@@ -351,7 +365,7 @@ db_namespace = namespace :db do
task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure)
# desc "Empty the test database"
- task :purge => %w(environment load_config) do
+ task :purge => %w(environment load_config check_protected_environments) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a549b28f16..320ced5afa 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -7,8 +7,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :_reflections
- class_attribute :aggregate_reflections
+ class_attribute :_reflections, instance_writer: false
+ class_attribute :aggregate_reflections, instance_writer: false
self._reflections = {}
self.aggregate_reflections = {}
end
@@ -545,7 +545,7 @@ module ActiveRecord
end
end
- # returns either nil or the inverse association name that it finds.
+ # returns either false or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 316b0d6308..032b8d4c5d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -99,7 +99,7 @@ module ActiveRecord
end
substitutes = values.map do |(arel_attr, _)|
- [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])]
+ [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
@@ -132,7 +132,7 @@ module ActiveRecord
# ==== Examples
#
# users = User.where(name: 'Oscar')
- # users.create # => #<User id: 3, name: "oscar", ...>
+ # users.create # => #<User id: 3, name: "Oscar", ...>
#
# users.create(name: 'fxn')
# users.create # => #<User id: 4, name: "fxn", ...>
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 221bc73680..54587ae18e 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -29,15 +29,15 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
- # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:begin_at+ and +:end_at+ option on each worker).
+ # (by setting the +:start+ and +:finish+ option on each worker).
#
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
- # Person.find_each(begin_at: 2000, batch_size: 2000) do |person|
+ # Person.find_each(start: 2000, batch_size: 2000) do |person|
# person.party_all_night!
# end
#
@@ -48,22 +48,15 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
- if start
- begin_at = start
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing `start` value to find_each is deprecated, and will be removed in Rails 5.1.
- Please pass `begin_at` instead.
- MSG
- end
+ def find_each(start: nil, finish: nil, batch_size: 1000)
if block_given?
- find_in_batches(begin_at: begin_at, end_at: end_at, batch_size: batch_size) do |records|
+ find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records|
records.each { |record| yield record }
end
else
- enum_for(:find_each, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do
relation = self
- apply_limits(relation, begin_at, end_at).size
+ apply_limits(relation, start, finish).size
end
end
end
@@ -88,15 +81,15 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
- # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:begin_at+ and +:end_at+ option on each worker).
+ # (by setting the +:start+ and +:finish+ option on each worker).
#
# # Let's process the next 2000 records
- # Person.find_in_batches(begin_at: 2000, batch_size: 2000) do |group|
+ # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
# group.each { |person| person.party_all_night! }
# end
#
@@ -107,24 +100,16 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
- if start
- begin_at = start
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing `start` value to find_in_batches is deprecated, and will be removed in Rails 5.1.
- Please pass `begin_at` instead.
- MSG
- end
-
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000)
relation = self
unless block_given?
- return to_enum(:find_in_batches, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
- total = apply_limits(relation, begin_at, end_at).size
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do
+ total = apply_limits(relation, start, finish).size
(total - 1).div(batch_size) + 1
end
end
- in_batches(of: batch_size, begin_at: begin_at, end_at: end_at, load: true) do |batch|
+ in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch|
yield batch.to_a
end
end
@@ -153,18 +138,18 @@ module ActiveRecord
# ==== Options
# * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
- # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
- # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
#
# This is especially useful if you want to work with the
# ActiveRecord::Relation object instead of the array of records, or if
# you want multiple workers dealing with the same processing queue. You can
# make worker 1 handle all the records between id 0 and 10,000 and worker 2
- # handle from 10,000 and beyond (by setting the +:begin_at+ and +:end_at+
+ # handle from 10,000 and beyond (by setting the +:start+ and +:finish+
# option on each worker).
#
# # Let's process the next 2000 records
- # Person.in_batches(of: 2000, begin_at: 2000).update_all(awesome: true)
+ # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
#
# An example of calling where query method on the relation:
#
@@ -186,10 +171,10 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control the batch
# sizes.
- def in_batches(of: 1000, begin_at: nil, end_at: nil, load: false)
+ def in_batches(of: 1000, start: nil, finish: nil, load: false)
relation = self
unless block_given?
- return BatchEnumerator.new(of: of, begin_at: begin_at, end_at: end_at, relation: self)
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
if logger && (arel.orders.present? || arel.taken.present?)
@@ -197,7 +182,7 @@ module ActiveRecord
end
relation = relation.reorder(batch_order).limit(of)
- relation = apply_limits(relation, begin_at, end_at)
+ relation = apply_limits(relation, start, finish)
batch_relation = relation
loop do
@@ -225,9 +210,9 @@ module ActiveRecord
private
- def apply_limits(relation, begin_at, end_at)
- relation = relation.where(table[primary_key].gteq(begin_at)) if begin_at
- relation = relation.where(table[primary_key].lteq(end_at)) if end_at
+ def apply_limits(relation, start, finish)
+ relation = relation.where(table[primary_key].gteq(start)) if start
+ relation = relation.where(table[primary_key].lteq(finish)) if finish
relation
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
index 153aae9584..c6e39814dd 100644
--- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -3,11 +3,11 @@ module ActiveRecord
class BatchEnumerator
include Enumerable
- def initialize(of: 1000, begin_at: nil, end_at: nil, relation:) #:nodoc:
+ def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
@of = of
@relation = relation
- @begin_at = begin_at
- @end_at = end_at
+ @start = start
+ @finish = finish
end
# Looping through a collection of records from the database (using the
@@ -34,7 +34,7 @@ module ActiveRecord
def each_record
return to_enum(:each_record) unless block_given?
- @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: true).each do |relation|
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
relation.to_a.each { |record| yield record }
end
end
@@ -46,7 +46,7 @@ module ActiveRecord
# People.in_batches.update_all('age = age + 1')
[:delete_all, :update_all, :destroy_all].each do |method|
define_method(method) do |*args, &block|
- @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false).each do |relation|
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
relation.send(method, *args, &block)
end
end
@@ -58,7 +58,7 @@ module ActiveRecord
# relation.update_all(awesome: true)
# end
def each
- enum = @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false)
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
return enum.each { |relation| yield relation } if block_given?
enum
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 19244bcf95..3cbb12a09d 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -117,9 +117,9 @@ module ActiveRecord
#
def first(limit = nil)
if limit
- find_nth_with_limit(offset_index, limit)
+ find_nth_with_limit_and_offset(0, limit, offset: offset_index)
else
- find_nth(0, offset_index)
+ find_nth 0
end
end
@@ -169,7 +169,7 @@ module ActiveRecord
# Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
# Person.where(["user_name = :u", { u: user_name }]).second
def second
- find_nth(1, offset_index)
+ find_nth 1
end
# Same as #second but raises ActiveRecord::RecordNotFound if no record
@@ -185,7 +185,7 @@ module ActiveRecord
# Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
# Person.where(["user_name = :u", { u: user_name }]).third
def third
- find_nth(2, offset_index)
+ find_nth 2
end
# Same as #third but raises ActiveRecord::RecordNotFound if no record
@@ -201,7 +201,7 @@ module ActiveRecord
# Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
# Person.where(["user_name = :u", { u: user_name }]).fourth
def fourth
- find_nth(3, offset_index)
+ find_nth 3
end
# Same as #fourth but raises ActiveRecord::RecordNotFound if no record
@@ -217,7 +217,7 @@ module ActiveRecord
# Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
# Person.where(["user_name = :u", { u: user_name }]).fifth
def fifth
- find_nth(4, offset_index)
+ find_nth 4
end
# Same as #fifth but raises ActiveRecord::RecordNotFound if no record
@@ -233,7 +233,7 @@ module ActiveRecord
# Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
# Person.where(["user_name = :u", { u: user_name }]).forty_two
def forty_two
- find_nth(41, offset_index)
+ find_nth 41
end
# Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
@@ -488,27 +488,39 @@ module ActiveRecord
end
end
- def find_nth(index, offset)
+ def find_nth(index, offset = nil)
if loaded?
@records[index]
else
- offset += index
- @offsets[offset] ||= find_nth_with_limit(offset, 1).first
+ # TODO: once the offset argument is removed we rely on offset_index
+ # within find_nth_with_limit, rather than pass it in via
+ # find_nth_with_limit_and_offset
+ if offset
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing an offset argument to find_nth is deprecated,
+ please use Relation#offset instead.
+ MSG
+ else
+ offset = offset_index
+ end
+ @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
end
end
def find_nth!(index)
- find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
- def find_nth_with_limit(offset, limit)
+ def find_nth_with_limit(index, limit)
+ # TODO: once the offset argument is removed from find_nth,
+ # find_nth_with_limit_and_offset can be merged into this method
relation = if order_values.empty? && primary_key
order(arel_table[primary_key].asc)
else
self
end
- relation = relation.offset(offset) unless offset.zero?
+ relation = relation.offset(index) unless index.zero?
relation.limit(limit).to_a
end
@@ -524,5 +536,16 @@ module ActiveRecord
end
end
end
+
+ private
+
+ def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
+ if loaded?
+ @records[index, limit]
+ else
+ index += offset
+ find_nth_with_limit(index, limit)
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb
index 92340216ed..8945cb0cc5 100644
--- a/activerecord/lib/active_record/relation/from_clause.rb
+++ b/activerecord/lib/active_record/relation/from_clause.rb
@@ -25,7 +25,7 @@ module ActiveRecord
end
def self.empty
- new(nil, nil)
+ @empty ||= new(nil, nil)
end
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index cb971eb255..396638d74d 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -141,6 +141,9 @@ module ActiveRecord
end
def merge_single_values
+ if relation.from_clause.empty?
+ relation.from_clause = other.from_clause
+ end
relation.lock_value ||= other.lock_value
unless other.create_with_value.blank?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 39e7b42629..0f88791d92 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -18,6 +18,7 @@ module ActiveRecord
register_handler(Class, ClassHandler.new(self))
register_handler(Base, BaseHandler.new(self))
register_handler(Range, RangeHandler.new(self))
+ register_handler(RangeHandler::RangeWithBinds, RangeHandler.new(self))
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
@@ -105,10 +106,23 @@ module ActiveRecord
binds += bvs
when Relation
binds += value.bound_attributes
+ when Range
+ first = value.begin
+ last = value.end
+ unless first.respond_to?(:infinite?) && first.infinite?
+ binds << build_bind_param(column_name, first)
+ first = Arel::Nodes::BindParam.new
+ end
+ unless last.respond_to?(:infinite?) && last.infinite?
+ binds << build_bind_param(column_name, last)
+ last = Arel::Nodes::BindParam.new
+ end
+
+ result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
else
if can_be_bound?(column_name, value)
result[column_name] = Arel::Nodes::BindParam.new
- binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+ binds << build_bind_param(column_name, value)
end
end
end
@@ -145,5 +159,9 @@ module ActiveRecord
handler_for(value).is_a?(BasicObjectHandler) &&
!table.associated_with?(column_name)
end
+
+ def build_bind_param(column_name, value)
+ Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
index 1b3849e3ad..306d4694ae 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -1,12 +1,28 @@
module ActiveRecord
class PredicateBuilder
class RangeHandler # :nodoc:
+ RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
+
def initialize(predicate_builder)
@predicate_builder = predicate_builder
end
def call(attribute, value)
- attribute.between(value)
+ if value.begin.respond_to?(:infinite?) && value.begin.infinite?
+ if value.end.respond_to?(:infinite?) && value.end.infinite?
+ attribute.not_in([])
+ elsif value.exclude_end?
+ attribute.lt(value.end)
+ else
+ attribute.lteq(value.end)
+ end
+ elsif value.end.respond_to?(:infinite?) && value.end.infinite?
+ attribute.gteq(value.begin)
+ elsif value.exclude_end?
+ attribute.gteq(value.begin).and(attribute.lt(value.end))
+ else
+ attribute.between(value)
+ end
end
protected
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 983bf019bc..716b1e8505 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -54,16 +54,17 @@ module ActiveRecord
end
end
+ FROZEN_EMPTY_ARRAY = [].freeze
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- assert_mutability! # assert_mutability!
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values
+ @values[:#{name}] || FROZEN_EMPTY_ARRAY
+ end
+
+ def #{name}_values=(values)
+ assert_mutability!
+ @values[:#{name}] = values
+ end
CODE
end
@@ -116,8 +117,9 @@ module ActiveRecord
result
end
+ FROZEN_EMPTY_HASH = {}.freeze
def create_with_value # :nodoc:
- @values[:create_with] || {}
+ @values[:create_with] || FROZEN_EMPTY_HASH
end
alias extensions extending_values
@@ -649,16 +651,18 @@ module ActiveRecord
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
# present). Neither relation may have a #limit, #offset, or #distinct set.
#
- # Post.where("id = 1").or(Post.where("id = 2"))
- # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
+ # Post.where("id = 1").or(Post.where("author_id = 3"))
+ # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3'))
#
def or(other)
spawn.or!(other)
end
def or!(other) # :nodoc:
- unless structurally_compatible_for_or?(other)
- raise ArgumentError, 'Relation passed to #or must be structurally compatible'
+ incompatible_values = structurally_incompatible_values_for_or(other)
+
+ unless incompatible_values.empty?
+ raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
end
self.where_clause = self.where_clause.or(other.where_clause)
@@ -1187,10 +1191,10 @@ module ActiveRecord
end
end
- def structurally_compatible_for_or?(other)
- Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } &&
- (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } &&
- (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") != other.send("#{m}_clause") }
+ def structurally_incompatible_values_for_or(other)
+ Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
+ (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
+ (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end
def new_where_clause
diff --git a/activerecord/lib/active_record/relation/record_fetch_warning.rb b/activerecord/lib/active_record/relation/record_fetch_warning.rb
index 0a1814b3dd..dbd08811fa 100644
--- a/activerecord/lib/active_record/relation/record_fetch_warning.rb
+++ b/activerecord/lib/active_record/relation/record_fetch_warning.rb
@@ -24,9 +24,7 @@ module ActiveRecord
end
# :stopdoc:
- ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
- payload = args.last
-
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
QueryRegistry.queries << payload[:sql]
end
# :startdoc:
@@ -34,14 +32,14 @@ module ActiveRecord
class QueryRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
- attr_accessor :queries
+ attr_reader :queries
def initialize
- reset
+ @queries = []
end
def reset
- @queries = []
+ @queries.clear
end
end
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 1f000b3f0f..2c2d6cfa47 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -81,7 +81,7 @@ module ActiveRecord
end
def self.empty
- new([], [])
+ @empty ||= new([], [])
end
protected
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index a81ff98e49..dbf172a577 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -22,6 +22,7 @@ module ActiveRecord
parts = predicate_builder.build_from_hash(attributes)
when Arel::Nodes::Node
parts = [opts]
+ binds = other
else
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 4e89ba4dd1..2bfc5ff7ae 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -213,7 +213,7 @@ module ActiveRecord
end
# TODO: Deprecate this
- def quoted_id
+ def quoted_id # :nodoc:
self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
end
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index fdf9965a82..784a02d2c3 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -51,6 +51,9 @@ module ActiveRecord
initialize_schema_migrations_table
connection.assume_migrated_upto_version(info[:version], migrations_paths)
end
+
+ ActiveRecord::InternalMetadata.create_table
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
private
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2362dae9fc..65005bd44b 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -254,7 +254,7 @@ HEADER
end
def ignored?(table_name)
- [ActiveRecord::Base.schema_migrations_table_name, ignore_tables].flatten.any? do |ignored|
+ [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored|
ignored === remove_prefix_and_suffix(table_name)
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 51b9b17395..ee4c71f304 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -9,7 +9,7 @@ module ActiveRecord
class SchemaMigration < ActiveRecord::Base # :nodoc:
class << self
def primary_key
- nil
+ "version"
end
def table_name
@@ -31,8 +31,8 @@ module ActiveRecord
connection.create_table(table_name, id: false) do |t|
t.column :version, :string, version_options
+ t.index :version, unique: true, name: index_name
end
- connection.add_index table_name, :version, unique: true, name: index_name
end
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index e395970dc6..7794af8ca4 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -11,11 +11,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- ScopeRegistry.value_for(:current_scope, self.to_s)
+ ScopeRegistry.value_for(:current_scope, self)
end
def current_scope=(scope) #:nodoc:
- ScopeRegistry.set_value_for(:current_scope, self.to_s, scope)
+ ScopeRegistry.set_value_for(:current_scope, self, scope)
end
# Collects attributes from scopes that should be applied when creating
@@ -53,18 +53,18 @@ module ActiveRecord
# following code:
#
# registry = ActiveRecord::Scoping::ScopeRegistry
- # registry.set_value_for(:current_scope, "Board", some_new_scope)
+ # registry.set_value_for(:current_scope, Board, some_new_scope)
#
# Now when you run:
#
- # registry.value_for(:current_scope, "Board")
+ # registry.value_for(:current_scope, Board)
#
# You will obtain whatever was defined in +some_new_scope+. The #value_for
# and #set_value_for methods are delegated to the current ScopeRegistry
# object, so the above example code can also be called as:
#
# ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
- # "Board", some_new_scope)
+ # Board, some_new_scope)
class ScopeRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
@@ -74,16 +74,22 @@ module ActiveRecord
@registry = Hash.new { |hash, key| hash[key] = {} }
end
- # Obtains the value for a given +scope_name+ and +variable_name+.
- def value_for(scope_type, variable_name)
+ # Obtains the value for a given +scope_type+ and +model+.
+ def value_for(scope_type, model)
raise_invalid_scope_type!(scope_type)
- @registry[scope_type][variable_name]
+ klass = model
+ base = model.base_class
+ while klass <= base
+ value = @registry[scope_type][klass.name]
+ return value if value
+ klass = klass.superclass
+ end
end
- # Sets the +value+ for a given +scope_type+ and +variable_name+.
- def set_value_for(scope_type, variable_name, value)
+ # Sets the +value+ for a given +scope_type+ and +model+.
+ def set_value_for(scope_type, model, value)
raise_invalid_scope_type!(scope_type)
- @registry[scope_type][variable_name] = value
+ @registry[scope_type][model.name] = value
end
private
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index cdcb73382f..f6b6768ce3 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -6,7 +6,7 @@ module ActiveRecord
included do
# Stores the default scope for the class.
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
- class_attribute :default_scope_override, instance_predicate: false
+ class_attribute :default_scope_override, instance_writer: false, instance_predicate: false
self.default_scopes = []
self.default_scope_override = nil
@@ -122,11 +122,11 @@ module ActiveRecord
end
def ignore_default_scope? # :nodoc:
- ScopeRegistry.value_for(:ignore_default_scope, self)
+ ScopeRegistry.value_for(:ignore_default_scope, base_class)
end
def ignore_default_scope=(ignore) # :nodoc:
- ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
+ ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index b6fba0cf79..8f52e9068a 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -42,6 +42,22 @@ module ActiveRecord
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
+ def check_protected_environments!
+ unless ENV['DISABLE_DATABASE_ENVIRONMENT_CHECK']
+ current = ActiveRecord::Migrator.current_environment
+ stored = ActiveRecord::Migrator.last_stored_environment
+
+ if ActiveRecord::Migrator.protected_environment?
+ raise ActiveRecord::ProtectedEnvironmentError.new(stored)
+ end
+
+ if stored && stored != current
+ raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
+ end
+ end
+ rescue ActiveRecord::NoDatabaseError
+ end
+
def register_task(pattern, task)
@tasks ||= {}
@tasks[pattern] = task
@@ -204,6 +220,8 @@ module ActiveRecord
else
raise ArgumentError, "unknown format #{format.inspect}"
end
+ ActiveRecord::InternalMetadata.create_table
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
def load_schema_for(*args)
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index a572c109d8..d9c18a5e38 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -16,7 +16,7 @@ module ActiveRecord
# == Time Zone aware attributes
#
# Active Record keeps all the <tt>datetime</tt> and <tt>time</tt> columns
- # time-zone aware. By default, these values are stored in the database as UTC
+ # timezone aware. By default, these values are stored in the database as UTC
# and converted back to the current <tt>Time.zone</tt> when pulled from the database.
#
# This feature can be turned off completely by setting:
@@ -28,6 +28,10 @@ module ActiveRecord
#
# ActiveRecord::Base.time_zone_aware_types = [:datetime]
#
+ # You can also add database specific timezone aware types. For example, for PostgreSQL:
+ #
+ # ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange]
+ #
# Finally, you can indicate specific attributes of a model for which time zone
# conversion should not applied, for instance by setting:
#
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 38ab1f3fc6..77c2845d88 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -233,19 +233,19 @@ module ActiveRecord
set_callback(:commit, :after, *args, &block)
end
- # Shortcut for +after_commit :hook, on: :create+.
+ # Shortcut for <tt>after_commit :hook, on: :create</tt>.
def after_create_commit(*args, &block)
set_options_for_callbacks!(args, on: :create)
set_callback(:commit, :after, *args, &block)
end
- # Shortcut for +after_commit :hook, on: :update+.
+ # Shortcut for <tt>after_commit :hook, on: :update</tt>.
def after_update_commit(*args, &block)
set_options_for_callbacks!(args, on: :update)
set_callback(:commit, :after, *args, &block)
end
- # Shortcut for +after_commit :hook, on: :destroy+.
+ # Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
def after_destroy_commit(*args, &block)
set_options_for_callbacks!(args, on: :destroy)
set_callback(:commit, :after, *args, &block)
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index edc1325b25..a376e2a17f 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -73,15 +73,18 @@ module ActiveRecord
value = value.to_s[0, column.limit]
end
- value = Arel::Nodes::Quoted.new(value)
-
comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
- klass.unscoped.where(comparison)
+ if value.nil?
+ klass.unscoped.where(comparison)
+ else
+ bind = Relation::QueryAttribute.new(attribute.to_s, value, Type::Value.new)
+ klass.unscoped.where(comparison, bind)
+ end
rescue RangeError
klass.none
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 15aecf28ca..7395839fca 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -45,7 +45,13 @@ module ActiveRecord
def determine_default_parent_class
application_record = nil
- in_root { application_record = File.exist?('app/models/application_record.rb') }
+ in_root do
+ application_record = if mountable_engine?
+ File.exist?("app/models/#{namespaced_path}/application_record.rb")
+ else
+ File.exist?('app/models/application_record.rb')
+ end
+ end
if application_record
"ApplicationRecord"
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index bd732b5eca..bb89e893e0 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -5,6 +5,17 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
end
def test_enum_limit
- assert_equal 6, EnumTest.columns.first.limit
+ column = EnumTest.columns_hash['enum_column']
+ assert_equal 8, column.limit
+ end
+
+ def test_should_not_be_blob_or_text_column
+ column = EnumTest.columns_hash['enum_column']
+ assert_not column.blob_or_text_column?
+ end
+
+ def test_should_not_be_unsigned
+ column = EnumTest.columns_hash['enum_column']
+ assert_not column.unsigned?
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
new file mode 100644
index 0000000000..4efd728754
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -0,0 +1,44 @@
+require "cases/helper"
+
+class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_columns_for_distinct_zero_orders
+ assert_equal "posts.id",
+ @conn.columns_for_distinct("posts.id", [])
+ end
+
+ def test_columns_for_distinct_one_order
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @conn.columns_for_distinct("posts.id", ["posts.created_at desc"])
+ end
+
+ def test_columns_for_distinct_few_orders
+ assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
+ end
+
+ def test_columns_for_distinct_with_case
+ assert_equal(
+ 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0',
+ @conn.columns_for_distinct('posts.id',
+ ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
+ )
+ end
+
+ def test_columns_for_distinct_blank_not_nil_orders
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
+ end
+
+ def test_columns_for_distinct_with_arel_order
+ order = Object.new
+ def order.to_sql
+ "posts.created_at desc"
+ end
+ assert_equal "posts.id, posts.created_at AS alias_0",
+ @conn.columns_for_distinct("posts.id", [order])
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 396f235e77..9a9a4fed42 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -12,9 +12,35 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
end
def test_initializes_schema_migrations_for_encoding_utf8mb4
- smtn = ActiveRecord::Migrator.schema_migrations_table_name
- connection.drop_table smtn, if_exists: true
+ with_encoding_utf8mb4 do
+ table_name = ActiveRecord::SchemaMigration.table_name
+ connection.drop_table table_name, if_exists: true
+ connection.initialize_schema_migrations_table
+
+ assert connection.column_exists?(table_name, :version, :string, collation: 'utf8_general_ci')
+ end
+ end
+
+ def test_initializes_internal_metadata_for_encoding_utf8mb4
+ with_encoding_utf8mb4 do
+ table_name = ActiveRecord::InternalMetadata.table_name
+ connection.drop_table table_name, if_exists: true
+
+ connection.initialize_internal_metadata_table
+
+ assert connection.column_exists?(table_name, :key, :string, collation: 'utf8_general_ci')
+ end
+ end
+
+ def test_key_limit_max_matches_max
+ assert_equal ActiveRecord::InternalMetadata::KEY_LIMIT ,ActiveRecord::ConnectionAdapters::Mysql2Adapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN
+ end
+
+
+ private
+
+ def with_encoding_utf8mb4
database_name = connection.current_database
database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'")
@@ -23,15 +49,11 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4")
- connection.initialize_schema_migrations_table
-
- limit = ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN
- assert connection.column_exists?(smtn, :version, :string, limit: limit)
+ yield
ensure
execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}")
end
- private
def connection
@connection ||= ActiveRecord::Base.connection
end
diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb
index cdaa2cca44..4197ba45f1 100644
--- a/activerecord/test/cases/adapters/mysql2/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb
@@ -22,6 +22,12 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'"
end
+ def test_multi_results_from_select_one
+ row = @connection.select_one('CALL topics(1);')
+ assert_equal 'David', row['author_name']
+ assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_one'"
+ end
+
def test_multi_results_from_find_by_sql
topics = Topic.find_by_sql 'CALL topics(3);'
assert_equal 3, topics.size
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index e361521155..f1995b243a 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -139,8 +139,8 @@ module ActiveRecord
def test_sql_for_insert_with_returning_disabled
connection = connection_without_insert_returning
- result = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
- assert_equal ['sql', 'binds'], result
+ sql, binds = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
+ assert_equal ['sql', 'binds'], [sql, binds]
end
def test_serial_sequence
@@ -322,11 +322,6 @@ module ActiveRecord
end
end
- def test_substitute_at
- bind = @connection.substitute_at(nil)
- assert_equal Arel.sql('$1'), bind.to_sql
- end
-
def test_partial_index
with_example_table do
@connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 02b1083430..0edfa4ed9d 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -4,11 +4,13 @@ require 'support/connection_helper'
if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord::Base.connection.supports_ranges?
class PostgresqlRange < ActiveRecord::Base
self.table_name = "postgresql_ranges"
+ self.time_zone_aware_types += [:tsrange, :tstzrange]
end
class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
include ConnectionHelper
+ include InTimeZone
def setup
@connection = PostgresqlRange.connection
@@ -160,6 +162,26 @@ _SQL
assert_nil @empty_range.float_range
end
+ def test_timezone_awareness_tzrange
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
+
+ record = PostgresqlRange.new(tstz_range: time_string..time_string)
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
+ end
+ end
+
def test_create_tstzrange
tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT')
round_trip(@new_range, :tstz_range, tstzrange)
@@ -188,6 +210,26 @@ _SQL
Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0))
end
+ def test_timezone_awareness_tsrange
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
+
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ end
+ end
+
def test_create_numrange
assert_equal_round_trip(@new_range, :num_range,
BigDecimal.new('0.5')...BigDecimal.new('1'))
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 049ed1732e..7628075ad2 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -146,8 +146,6 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
end
setup do
- enable_extension!('uuid-ossp', connection)
-
connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
t.string 'name'
t.uuid 'other_uuid', default: 'uuid_generate_v4()'
@@ -172,7 +170,6 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
drop_table "pg_uuids"
drop_table 'pg_uuids_2'
connection.execute 'DROP FUNCTION IF EXISTS my_uuid_generator();'
- disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -200,14 +197,14 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
def test_schema_dumper_for_uuid_primary_key
schema = dump_table_schema "pg_uuids"
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema)
- assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema)
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
end
def test_schema_dumper_for_uuid_primary_key_with_custom_default
schema = dump_table_schema "pg_uuids_2"
- assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema)
- assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema)
+ assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
end
end
end
@@ -217,8 +214,6 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
setup do
- enable_extension!('uuid-ossp', connection)
-
connection.create_table('pg_uuids', id: false) do |t|
t.primary_key :id, :uuid, default: nil
t.string 'name'
@@ -227,7 +222,6 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
teardown do
drop_table "pg_uuids"
- disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -260,8 +254,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
end
setup do
- enable_extension!('uuid-ossp', connection)
-
connection.transaction do
connection.create_table('pg_uuid_posts', id: :uuid) do |t|
t.string 'title'
@@ -276,7 +268,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
teardown do
drop_table "pg_uuid_comments"
drop_table "pg_uuid_posts"
- disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index a2fd1177a6..038d9e2d0f 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -130,11 +130,6 @@ module ActiveRecord
assert_equal 'UTF-8', @conn.encoding
end
- def test_bind_value_substitute
- bind_param = @conn.substitute_at('foo')
- assert_equal Arel.sql('?'), bind_param.to_sql
- end
-
def test_exec_no_binds
with_example_table 'id int, data string' do
result = @conn.exec_query('SELECT id, data FROM ex')
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 9d5327bf35..1f32c48b95 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -21,10 +21,10 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Migration.verbose = @original_verbose
end
- def test_has_no_primary_key
+ def test_has_has_primary_key
old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
- assert_nil ActiveRecord::SchemaMigration.primary_key
+ assert_equal "version", ActiveRecord::SchemaMigration.primary_key
ActiveRecord::SchemaMigration.create_table
assert_difference "ActiveRecord::SchemaMigration.count", 1 do
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 0df8f1f798..3608063b01 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -433,6 +433,53 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
end
+ def test_errors_details_should_be_set
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.valid?
+ assert_equal [{error: :blank}], molecule.errors.details["electrons.name"]
+ end
+
+ def test_errors_details_should_be_indexed_when_passed_as_array
+ guitar = Guitar.new
+ tuning_peg_valid = TuningPeg.new
+ tuning_peg_valid.pitch = 440.0
+ tuning_peg_invalid = TuningPeg.new
+
+ guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
+
+ assert_not tuning_peg_invalid.valid?
+ assert tuning_peg_valid.valid?
+ assert_not guitar.valid?
+ assert_equal [{error: :not_a_number, value: nil}] , guitar.errors.details["tuning_pegs[1].pitch"]
+ assert_equal [], guitar.errors.details["tuning_pegs.pitch"]
+ end
+
+ def test_errors_details_should_be_indexed_when_global_flag_is_set
+ old_attribute_config = ActiveRecord::Base.index_nested_attribute_errors
+ ActiveRecord::Base.index_nested_attribute_errors = true
+
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.valid?
+ assert_equal [{error: :blank}], molecule.errors.details["electrons[1].name"]
+ assert_equal [], molecule.errors.details["electrons.name"]
+ ensure
+ ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
+ end
+
def test_valid_adding_with_nested_attributes
molecule = Molecule.new
valid_electron = Electron.new(name: 'electron')
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ba3e16bdb2..ecdf508e3e 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1275,9 +1275,10 @@ class BasicsTest < ActiveRecord::TestCase
UnloadablePost.send(:current_scope=, UnloadablePost.all)
UnloadablePost.unloadable
- assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
+ klass = UnloadablePost
+ assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
+ assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index da65336305..3602ee7ba2 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -38,7 +38,7 @@ class EachTest < ActiveRecord::TestCase
if Enumerator.method_defined? :size
def test_each_should_return_a_sized_enumerator
assert_equal 11, Post.find_each(batch_size: 1).size
- assert_equal 5, Post.find_each(batch_size: 2, begin_at: 7).size
+ assert_equal 5, Post.find_each(batch_size: 2, start: 7).size
assert_equal 11, Post.find_each(batch_size: 10_000).size
end
end
@@ -101,16 +101,16 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_start_from_the_start_option
assert_queries(@total) do
- Post.find_in_batches(batch_size: 1, begin_at: 2) do |batch|
+ Post.find_in_batches(batch_size: 1, start: 2) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
end
end
end
- def test_find_in_batches_should_end_at_the_end_option
+ def test_find_in_batches_should_finish_the_end_option
assert_queries(6) do
- Post.find_in_batches(batch_size: 1, end_at: 5) do |batch|
+ Post.find_in_batches(batch_size: 1, finish: 5) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
end
@@ -175,7 +175,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.find_in_batches({ batch_size: 42, begin_at: 1 }.freeze){}
+ Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){}
end
end
@@ -184,7 +184,7 @@ class EachTest < ActiveRecord::TestCase
start_nick = nick_order_subscribers.second.nick
subscribers = []
- Subscriber.find_in_batches(batch_size: 1, begin_at: start_nick) do |batch|
+ Subscriber.find_in_batches(batch_size: 1, start: start_nick) do |batch|
subscribers.concat(batch)
end
@@ -311,15 +311,15 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_start_from_the_start_option
post = Post.order('id ASC').where('id >= ?', 2).first
assert_queries(2) do
- relation = Post.in_batches(of: 1, begin_at: 2).first
+ relation = Post.in_batches(of: 1, start: 2).first
assert_equal post, relation.first
end
end
- def test_in_batches_should_end_at_the_end_option
+ def test_in_batches_should_finish_the_end_option
post = Post.order('id DESC').where('id <= ?', 5).first
assert_queries(7) do
- relation = Post.in_batches(of: 1, end_at: 5, load: true).reverse_each.first
+ relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first
assert_equal post, relation.last
end
end
@@ -371,7 +371,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.in_batches({ of: 42, begin_at: 1 }.freeze){}
+ Post.in_batches({ of: 42, start: 1 }.freeze){}
end
end
@@ -380,7 +380,7 @@ class EachTest < ActiveRecord::TestCase
start_nick = nick_order_subscribers.second.nick
subscribers = []
- Subscriber.in_batches(of: 1, begin_at: start_nick) do |relation|
+ Subscriber.in_batches(of: 1, start: start_nick) do |relation|
subscribers.concat(relation)
end
@@ -441,32 +441,11 @@ class EachTest < ActiveRecord::TestCase
assert_equal 2, person.reload.author_id # incremented only once
end
- def test_find_in_batches_start_deprecated
- assert_deprecated do
- assert_queries(@total) do
- Post.find_in_batches(batch_size: 1, start: 2) do |batch|
- assert_kind_of Array, batch
- assert_kind_of Post, batch.first
- end
- end
- end
- end
-
- def test_find_each_start_deprecated
- assert_deprecated do
- assert_queries(@total) do
- Post.find_each(batch_size: 1, start: 2) do |post|
- assert_kind_of Post, post
- end
- end
- end
- end
-
if Enumerator.method_defined? :size
def test_find_in_batches_should_return_a_sized_enumerator
assert_equal 11, Post.find_in_batches(:batch_size => 1).size
assert_equal 6, Post.find_in_batches(:batch_size => 2).size
- assert_equal 4, Post.find_in_batches(batch_size: 2, begin_at: 4).size
+ assert_equal 4, Post.find_in_batches(batch_size: 2, start: 4).size
assert_equal 4, Post.find_in_batches(:batch_size => 3).size
assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 1e38b97c4a..cd9c76f1f0 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def test_binds_are_logged
- sub = @connection.substitute_at(@pk)
+ sub = Arel::Nodes::BindParam.new
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index 53058c5a4a..a2874438c1 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -66,5 +66,24 @@ module ActiveRecord
developers = projects(:active_record).developers
assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
end
+
+ test "cache_key for loaded collection with zero size" do
+ Comment.delete_all
+ posts = Post.includes(:comments)
+ empty_loaded_collection = posts.first.comments
+
+ assert_match(/\Acomments\/query-(\h+)-0\Z/, empty_loaded_collection.cache_key)
+ end
+
+ test "cache_key for queries with offset which return 0 rows" do
+ developers = Developer.offset(20)
+ assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key)
+ end
+
+ test "cache_key with a relation having selected columns" do
+ developers = Developer.select(:salary)
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ end
end
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index da0d7f5195..81162b7e98 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -41,39 +41,49 @@ module ActiveRecord
if current_adapter?(:Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types
type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
- binary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
+ binary_column = MySQL::Column.new("title", "a", type)
assert_equal "a", binary_column.default
type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
- varbinary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
+ varbinary_column = MySQL::Column.new("title", "a", type)
assert_equal "a", varbinary_column.default
end
+ def test_should_be_empty_string_default_for_mysql_binary_data_types
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
+ binary_column = MySQL::Column.new("title", "", type, false)
+ assert_equal "", binary_column.default
+
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
+ varbinary_column = MySQL::Column.new("title", "", type, false)
+ assert_equal "", varbinary_column.default
+ end
+
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- AbstractMysqlAdapter::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob"))
+ MySQL::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob"))
end
- text_type = AbstractMysqlAdapter::MysqlTypeMetadata.new(
+ text_type = MySQL::TypeMetadata.new(
SqlTypeMetadata.new(type: :text))
assert_raise ArgumentError do
- AbstractMysqlAdapter::Column.new("title", "Hello", text_type)
+ MySQL::Column.new("title", "Hello", text_type)
end
- text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
+ text_column = MySQL::Column.new("title", nil, text_type)
assert_equal nil, text_column.default
- not_null_text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type, false)
+ not_null_text_column = MySQL::Column.new("title", nil, text_type, false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
binary_type = SqlTypeMetadata.new(sql_type: "blob")
- blob_column = AbstractMysqlAdapter::Column.new("title", nil, binary_type)
+ blob_column = MySQL::Column.new("title", nil, binary_type)
assert !blob_column.has_default?
text_type = SqlTypeMetadata.new(type: :text)
- text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
+ text_column = MySQL::Column.new("title", nil, text_type)
assert !text_column.has_default?
end
end
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index c689e97d83..ba085991e0 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -6,14 +6,23 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
end
def test_insert_should_return_the_inserted_id
+ assert_not_nil return_the_inserted_id(method: :insert)
+ end
+
+ def test_create_should_return_the_inserted_id
+ assert_not_nil return_the_inserted_id(method: :create)
+ end
+
+ private
+
+ def return_the_inserted_id(method:)
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if current_adapter?(:OracleAdapter)
sequence_name = "accounts_seq"
id_value = @connection.next_sequence_value(sequence_name)
- id = @connection.insert("INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name)
+ @connection.send(method, "INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name)
else
- id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
+ @connection.send(method, "INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
end
- assert_not_nil id
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index fb2d3bd497..69b0487dd8 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
require 'models/default'
require 'models/entrant'
@@ -80,7 +81,32 @@ class DefaultStringsTest < ActiveRecord::TestCase
end
end
+if current_adapter?(:PostgreSQLAdapter)
+ class PostgresqlDefaultExpressionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ test "schema dump includes default expression" do
+ output = dump_table_schema("defaults")
+ assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
+ assert_match %r/t\.date\s+"modified_date_function",\s+default: -> { "now\(\)" }/, output
+ assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
+ assert_match %r/t\.datetime\s+"modified_time_function",\s+default: -> { "now\(\)" }/, output
+ end
+ end
+end
+
if current_adapter?(:Mysql2Adapter)
+ class MysqlDefaultExpressionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ if ActiveRecord::Base.connection.version >= '5.6.0'
+ test "schema dump includes default expression" do
+ output = dump_table_schema("datetime_defaults")
+ assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
+ end
+ end
+ end
+
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional tests mode, this will
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 7c930de97b..babacd1ee9 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -411,4 +411,14 @@ class EnumTest < ActiveRecord::TestCase
assert book.proposed?, "expected fixture to default to proposed status"
assert book.in_english?, "expected fixture to default to english language"
end
+
+ test "uses default value from database on initialization" do
+ book = Book.new
+ assert book.proposed?
+ end
+
+ test "uses default value from database on initialization when using custom mapping" do
+ book = Book.new
+ assert book.hard?
+ end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 9f90ab79a1..75a74c052d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -19,7 +19,7 @@ require 'models/car'
require 'models/tyre'
class FinderTest < ActiveRecord::TestCase
- fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations, :cars
+ fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
def test_find_by_id_with_hash
assert_raises(ActiveRecord::StatementInvalid) do
@@ -993,10 +993,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
- assert_equal 2, Post.includes(authors: :author_address).order('author_addresses.id DESC ').limit(2).to_a.size
+ assert_equal 2, Post.includes(authors: :author_address).
+ where.not(author_addresses: { id: nil }).
+ order('author_addresses.id DESC').limit(2).to_a.size
assert_equal 3, Post.includes(author: :author_address, authors: :author_address).
- order('author_addresses_authors.id DESC ').limit(3).to_a.size
+ where.not(author_addresses_authors: { id: nil }).
+ order('author_addresses_authors.id DESC').limit(3).to_a.size
end
def test_find_with_nil_inside_set_passed_for_one_attribute
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index c73958900b..da934ab8fe 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -223,6 +223,10 @@ class FixturesTest < ActiveRecord::TestCase
assert_equal(%(table "parrots" has no column named "arrr".), e.message)
end
+ def test_yaml_file_with_symbol_columns
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "trees")
+ end
+
def test_omap_fixtures
assert_nothing_raised do
fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered")
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 03bce547da..c870247a4a 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -7,6 +7,7 @@ require 'models/project'
require 'models/subscriber'
require 'models/vegetables'
require 'models/shop'
+require 'models/sponsor'
module InheritanceTestHelper
def with_store_full_sti_class(&block)
@@ -524,3 +525,78 @@ class InheritanceAttributeTest < ActiveRecord::TestCase
assert_instance_of Empire, empire
end
end
+
+class InheritanceAttributeMappingTest < ActiveRecord::TestCase
+ setup do
+ @old_registry = ActiveRecord::Type.registry
+ ActiveRecord::Type.registry = ActiveRecord::Type::AdapterSpecificRegistry.new
+ ActiveRecord::Type.register :omg_sti, InheritanceAttributeMappingTest::OmgStiType
+ Company.delete_all
+ Sponsor.delete_all
+ end
+
+ teardown do
+ ActiveRecord::Type.registry = @old_registry
+ end
+
+ class OmgStiType < ActiveRecord::Type::String
+ def cast_value(value)
+ if value =~ /\Aomg_(.+)\z/
+ $1.classify
+ else
+ value
+ end
+ end
+
+ def serialize(value)
+ if value
+ "omg_%s" % value.underscore
+ end
+ end
+ end
+
+ class Company < ActiveRecord::Base
+ self.table_name = 'companies'
+ attribute :type, :omg_sti
+ end
+
+ class Startup < Company; end
+ class Empire < Company; end
+
+ class Sponsor < ActiveRecord::Base
+ self.table_name = 'sponsors'
+ attribute :sponsorable_type, :omg_sti
+
+ belongs_to :sponsorable, polymorphic: true
+ end
+
+ def test_sti_with_custom_type
+ Startup.create! name: 'a Startup'
+ Empire.create! name: 'an Empire'
+
+ assert_equal [["a Startup", "omg_inheritance_attribute_mapping_test/startup"],
+ ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows('SELECT name, type FROM companies').sort
+ assert_equal [["a Startup", "InheritanceAttributeMappingTest::Startup"],
+ ["an Empire", "InheritanceAttributeMappingTest::Empire"]], Company.all.map { |a| [a.name, a.type] }.sort
+
+ startup = Startup.first
+ startup.becomes! Empire
+ startup.save!
+
+ assert_equal [["a Startup", "omg_inheritance_attribute_mapping_test/empire"],
+ ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows('SELECT name, type FROM companies').sort
+
+ assert_equal [["a Startup", "InheritanceAttributeMappingTest::Empire"],
+ ["an Empire", "InheritanceAttributeMappingTest::Empire"]], Company.all.map { |a| [a.name, a.type] }.sort
+ end
+
+ def test_polymorphic_associations_custom_type
+ startup = Startup.create! name: 'a Startup'
+ sponsor = Sponsor.create! sponsorable: startup
+
+ assert_equal ["omg_inheritance_attribute_mapping_test/company"], ActiveRecord::Base.connection.select_values('SELECT sponsorable_type FROM sponsors')
+
+ sponsor = Sponsor.first
+ assert_equal startup, sponsor.sponsorable
+ end
+end
diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb
index c26623e3ca..a16b52751a 100644
--- a/activerecord/test/cases/invalid_connection_test.rb
+++ b/activerecord/test/cases/invalid_connection_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
+if current_adapter?(:Mysql2Adapter)
class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
self.use_transactional_tests = false
@@ -20,3 +21,4 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
assert_equal "#{Bird.name} (call '#{Bird.name}.connection' to establish a connection)", Bird.inspect
end
end
+end
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 267d2fcccc..6a9cdd9d29 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -21,6 +21,7 @@ module ActiveRecord
teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::SchemaMigration.delete_all
end
def test_migration_doesnt_remove_named_index
@@ -37,6 +38,39 @@ module ActiveRecord
assert_raise(StandardError) { ActiveRecord::Migrator.new(:up, [migration]).migrate }
assert connection.index_exists?(:testings, :foo, name: "custom_index_name")
end
+
+ def test_migration_does_remove_unnamed_index
+ connection.add_index :testings, :bar
+
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
+ def version; 101 end
+ def migrate(x)
+ remove_index :testings, :bar
+ end
+ }.new
+
+ assert connection.index_exists?(:testings, :bar)
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+ assert_not connection.index_exists?(:testings, :bar)
+ end
+
+ def test_references_does_not_add_index_by_default
+ migration = Class.new(ActiveRecord::Migration) {
+ def migrate(x)
+ create_table :more_testings do |t|
+ t.references :foo
+ t.belongs_to :bar, index: false
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert_not connection.index_exists?(:more_testings, :foo_id)
+ assert_not connection.index_exists?(:more_testings, :bar_id)
+ ensure
+ connection.drop_table :more_testings rescue nil
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index edbc8abe4d..b01415afb2 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -32,10 +32,10 @@ module ActiveRecord
assert_equal [], @connection.foreign_keys("testings")
end
- test "foreign keys can be created in one query" do
+ test "foreign keys can be created in one query when index is not added" do
assert_queries(1) do
@connection.create_table :testings do |t|
- t.references :testing_parent, foreign_key: true
+ t.references :testing_parent, foreign_key: true, index: false
end
end
end
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
index ad6b828d0b..a9a7f0f4c4 100644
--- a/activerecord/test/cases/migration/references_index_test.rb
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -23,12 +23,12 @@ module ActiveRecord
assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
- def test_does_not_create_index
+ def test_creates_index_by_default_even_if_index_option_is_not_passed
connection.create_table table_name do |t|
t.references :foo
end
- assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index_explicit
@@ -68,13 +68,13 @@ module ActiveRecord
assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
- def test_does_not_create_index_for_existing_table
+ def test_creates_index_for_existing_table_even_if_index_option_is_not_passed
connection.create_table table_name
connection.change_table table_name do |t|
t.references :foo
end
- assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index_for_existing_table_explicit
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index f613fd66c3..b9ce6bbc55 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -30,14 +30,14 @@ module ActiveRecord
assert column_exists?(table_name, :taggable_type, :string)
end
- def test_creates_reference_id_index
- add_reference table_name, :user, index: true
- assert index_exists?(table_name, :user_id)
+ def test_does_not_create_reference_id_index_if_index_is_false
+ add_reference table_name, :user, index: false
+ assert_not index_exists?(table_name, :user_id)
end
- def test_does_not_create_reference_id_index
+ def test_create_reference_id_index_even_if_index_option_is_passed
add_reference table_name, :user
- assert_not index_exists?(table_name, :user_id)
+ assert index_exists?(table_name, :user_id)
end
def test_creates_polymorphic_index
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 8eb027d53f..b926a92849 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -80,12 +80,10 @@ module ActiveRecord
end
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
- enable_extension!('uuid-ossp', connection)
connection.create_table :cats, id: :uuid
assert_nothing_raised { rename_table :cats, :felines }
ActiveSupport::Deprecation.silence { assert connection.table_exists? :felines }
ensure
- disable_extension!('uuid-ossp', connection)
connection.drop_table :cats, if_exists: true
connection.drop_table :felines, if_exists: true
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index b5b241ad1a..f51e366b1d 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -301,7 +301,7 @@ class MigrationTest < ActiveRecord::TestCase
e = assert_raise(StandardError) { migrator.run }
- assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message
+ assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
assert_no_column Person, :last_name,
"On error, the Migrator should revert schema changes but it did not."
@@ -354,6 +354,78 @@ class MigrationTest < ActiveRecord::TestCase
Reminder.reset_table_name
end
+ def test_internal_metadata_table_name
+ original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name
+
+ assert_equal "active_record_internal_metadatas", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_active_record_internal_metadatas_suffix", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.internal_metadata_table_name = "changed"
+ Reminder.reset_table_name
+ assert_equal "prefix_changed_suffix", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ assert_equal "changed", ActiveRecord::InternalMetadata.table_name
+ ensure
+ ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
+ Reminder.reset_table_name
+ end
+
+ def test_internal_metadata_stores_environment
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ old_path = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
+
+ original_rails_env = ENV["RAILS_ENV"]
+ original_rack_env = ENV["RACK_ENV"]
+ ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
+ new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+
+ refute_equal current_env, new_env
+
+ sleep 1 # mysql by default does not store fractional seconds in the database
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
+ ensure
+ ActiveRecord::Migrator.migrations_paths = old_path
+ ENV["RAILS_ENV"] = original_rails_env
+ ENV["RACK_ENV"] = original_rack_env
+ end
+
+
+ def test_migration_sets_internal_metadata_even_when_fully_migrated
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ old_path = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
+
+ original_rails_env = ENV["RAILS_ENV"]
+ original_rack_env = ENV["RACK_ENV"]
+ ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
+ new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+
+ refute_equal current_env, new_env
+
+ sleep 1 # mysql by default does not store fractional seconds in the database
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
+ ensure
+ ActiveRecord::Migrator.migrations_paths = old_path
+ ENV["RAILS_ENV"] = original_rails_env
+ ENV["RACK_ENV"] = original_rack_env
+ end
+
def test_proper_table_name_on_migration
reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 0b700afcb4..6fbc6196cc 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -146,6 +146,19 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert man.reload.interests.empty?
end
+ def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false
+ Pirate.accepts_nested_attributes_for :ship, reject_if: ->(a) { a[:name] == "The Golden Hind" }, allow_destroy: false
+
+ pirate = Pirate.create!(catchphrase: "Stop wastin' me time", ship_attributes: { name: "White Pearl", _destroy: "1" })
+ assert_equal "White Pearl", pirate.reload.ship.name
+
+ pirate.update!(ship_attributes: { id: pirate.ship.id, name: "The Golden Hind", _destroy: "1" })
+ assert_equal "White Pearl", pirate.reload.ship.name
+
+ pirate.update!(ship_attributes: { id: pirate.ship.id, name: "Black Pearl", _destroy: "1" })
+ assert_equal "Black Pearl", pirate.reload.ship.name
+ end
+
def test_has_many_association_updating_a_single_record
Man.accepts_nested_attributes_for(:interests)
man = Man.create(name: 'John')
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 9c04a41e69..710c86b151 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -23,6 +23,7 @@ require 'models/chef'
require 'models/department'
require 'models/cake_designer'
require 'models/drink_designer'
+require 'models/mocktail_designer'
require 'models/recipe'
class ReflectionTest < ActiveRecord::TestCase
@@ -278,6 +279,15 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal 2, @hotel.chefs.size
end
+ def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti
+ @hotel = Hotel.create!
+ @hotel.mocktail_designers << MocktailDesigner.create!
+
+ assert_equal 1, @hotel.mocktail_designers.size
+ assert_equal 1, @hotel.mocktail_designers.count
+ assert_equal 1, @hotel.chef_lists.size
+ end
+
def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations
hotel = Hotel.create!
department = hotel.departments.create!
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 0a2e874e4f..60a806c05a 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -104,6 +104,13 @@ class RelationMergingTest < ActiveRecord::TestCase
post = PostThatLoadsCommentsInAnAfterSaveHook.create!(title: "First Post", body: "Blah blah blah.")
assert_equal "First comment!", post.comments.where(body: "First comment!").first_or_create.body
end
+
+ def test_merging_with_from_clause
+ relation = Post.all
+ assert relation.from_clause.empty?
+ relation = relation.merge(Post.from("posts"))
+ refute relation.from_clause.empty?
+ end
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 2006fc9611..28a0862f91 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -52,9 +52,11 @@ module ActiveRecord
end
def test_or_with_incompatible_relations
- assert_raises ArgumentError do
+ error = assert_raises ArgumentError do
Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a
end
+
+ assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
end
def test_or_when_grouping
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index f46d414b95..03583344a8 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -43,13 +43,17 @@ module ActiveRecord
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
- assert_equal({}, relation.create_with_value)
+ value = relation.create_with_value
+ assert_equal({}, value)
+ assert_predicate value, :frozen?
end
def test_multi_value_initialize
relation = Relation.new(FakeKlass, :b, nil)
Relation::MULTI_VALUE_METHODS.each do |method|
- assert_equal [], relation.send("#{method}_values"), method.to_s
+ values = relation.send("#{method}_values")
+ assert_equal [], values, method.to_s
+ assert_predicate values, :frozen?, method.to_s
end
end
@@ -77,7 +81,6 @@ module ActiveRecord
def test_tree_is_not_traversed
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
@@ -104,7 +107,6 @@ module ActiveRecord
def test_create_with_value_with_wheres
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
@@ -115,7 +117,6 @@ module ActiveRecord
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
assert_equal({}, relation.scope_for_create)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 7149c7d072..0638edacbd 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -111,15 +111,38 @@ class RelationTest < ActiveRecord::TestCase
def test_loaded_first
topics = Topic.all.order('id ASC')
+ topics.to_a # force load
- assert_queries(1) do
- topics.to_a # force load
- 2.times { assert_equal "The First Topic", topics.first.title }
+ assert_no_queries do
+ assert_equal "The First Topic", topics.first.title
end
assert topics.loaded?
end
+ def test_loaded_first_with_limit
+ topics = Topic.all.order('id ASC')
+ topics.to_a # force load
+
+ assert_no_queries do
+ assert_equal ["The First Topic",
+ "The Second Topic of the day"], topics.first(2).map(&:title)
+ end
+
+ assert topics.loaded?
+ end
+
+ def test_first_get_more_than_available
+ topics = Topic.all.order('id ASC')
+ unloaded_first = topics.first(10)
+ topics.to_a # force load
+
+ assert_no_queries do
+ loaded_first = topics.first(10)
+ assert_equal unloaded_first, loaded_first
+ end
+ end
+
def test_reload
topics = Topic.all
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index a7735a2c7e..7b93d20e05 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -38,6 +38,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
end
def test_schema_dump_uses_force_cascade_on_create_table
@@ -158,6 +159,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
end
def test_schema_dump_with_regexp_ignored_table
@@ -165,6 +167,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
end
def test_schema_dumps_index_columns_in_right_order
@@ -342,6 +345,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "foo_.+_bar"}, output
assert_no_match %r{add_index "foo_.+_bar"}, output
assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "active_record_internal_metadatas"}, output
if ActiveRecord::Base.connection.supports_foreign_keys?
assert_no_match %r{add_foreign_key "foo_.+_bar"}, output
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 86316ab476..ad5ca70f36 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -459,4 +459,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
scope = Bus.all
assert_equal scope.where_clause.ast.children.length, 1
end
+
+ def test_sti_conditions_are_not_carried_in_default_scope
+ ConditionalStiPost.create! body: ''
+ SubConditionalStiPost.create! body: ''
+ SubConditionalStiPost.create! title: 'Hello world', body: ''
+
+ assert_equal 2, ConditionalStiPost.count
+ assert_equal 2, ConditionalStiPost.all.to_a.size
+ assert_equal 3, ConditionalStiPost.unscope(where: :title).to_a.size
+
+ assert_equal 1, SubConditionalStiPost.count
+ assert_equal 1, SubConditionalStiPost.all.to_a.size
+ assert_equal 2, SubConditionalStiPost.unscope(where: :title).to_a.size
+ end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 4bfffbe9c6..c15d57460b 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -209,9 +209,23 @@ class RelationScopingTest < ActiveRecord::TestCase
assert_not_equal [], Developer.all
end
- def test_current_scope_does_not_pollute_other_subclasses
- Post.none.scoping do
- assert StiPost.all.any?
+ def test_current_scope_does_not_pollute_sibling_subclasses
+ Comment.none.scoping do
+ assert_not SpecialComment.all.any?
+ assert_not VerySpecialComment.all.any?
+ assert_not SubSpecialComment.all.any?
+ end
+
+ SpecialComment.none.scoping do
+ assert Comment.all.any?
+ assert VerySpecialComment.all.any?
+ assert_not SubSpecialComment.all.any?
+ end
+
+ SubSpecialComment.none.scoping do
+ assert Comment.all.any?
+ assert VerySpecialComment.all.any?
+ assert SpecialComment.all.any?
end
end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 15d250f7e3..49df6628eb 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -17,6 +17,34 @@ module ActiveRecord
sqlite3: :sqlite_tasks
}
+ class DatabaseTasksUtilsTask< ActiveRecord::TestCase
+ def test_raises_an_error_when_called_with_protected_environment
+ ActiveRecord::Migrator.stubs(:current_version).returns(1)
+
+ protected_environments = ActiveRecord::Base.protected_environments.dup
+ current_env = ActiveRecord::Migrator.current_environment
+ assert !protected_environments.include?(current_env)
+ # Assert no error
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments << current_env
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+ ensure
+ ActiveRecord::Base.protected_environments = protected_environments
+ end
+
+ def test_raises_an_error_if_no_migrations_have_been_made
+ ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false)
+ ActiveRecord::Migrator.stubs(:current_version).returns(1)
+
+ assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+ end
+ end
+
class DatabaseTasksRegisterTask < ActiveRecord::TestCase
def test_register_task
klazz = Class.new do
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index ec5bdfd725..791b895d02 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -58,6 +58,11 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_add_to_null_transaction
+ topic = Topic.new
+ topic.add_to_transaction
+ end
+
def test_successful_with_return
committed = false
diff --git a/activerecord/test/fixtures/author_addresses.yml b/activerecord/test/fixtures/author_addresses.yml
index 7b90572187..cf75e5998d 100644
--- a/activerecord/test/fixtures/author_addresses.yml
+++ b/activerecord/test/fixtures/author_addresses.yml
@@ -3,3 +3,9 @@ david_address:
david_address_extra:
id: 2
+
+mary_address:
+ id: 3
+
+bob_address:
+ id: 4
diff --git a/activerecord/test/fixtures/authors.yml b/activerecord/test/fixtures/authors.yml
index 832236a486..41c124179e 100644
--- a/activerecord/test/fixtures/authors.yml
+++ b/activerecord/test/fixtures/authors.yml
@@ -9,7 +9,9 @@ david:
mary:
id: 2
name: Mary
+ author_address_id: 3
bob:
id: 3
name: Bob
+ author_address_id: 4
diff --git a/activerecord/test/fixtures/naked/yml/trees.yml b/activerecord/test/fixtures/naked/yml/trees.yml
new file mode 100644
index 0000000000..d163b98f21
--- /dev/null
+++ b/activerecord/test/fixtures/naked/yml/trees.yml
@@ -0,0 +1,3 @@
+root:
+ :id: 1
+ :name: The Root
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index 1927191393..e43e5c3901 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -14,6 +14,7 @@ class Book < ActiveRecord::Base
enum author_visibility: [:visible, :invisible], _prefix: true
enum illustrator_visibility: [:visible, :invisible], _prefix: true
enum font_size: [:small, :medium, :large], _prefix: :with, _suffix: true
+ enum cover: { hard: 'hard', soft: 'soft' }
def published!
super
diff --git a/activerecord/test/models/chef.rb b/activerecord/test/models/chef.rb
index 698a52e045..9d3dd01016 100644
--- a/activerecord/test/models/chef.rb
+++ b/activerecord/test/models/chef.rb
@@ -2,3 +2,7 @@ class Chef < ActiveRecord::Base
belongs_to :employable, polymorphic: true
has_many :recipes
end
+
+class ChefList < Chef
+ belongs_to :employable_list, polymorphic: true
+end
diff --git a/activerecord/test/models/hotel.rb b/activerecord/test/models/hotel.rb
index 491f8dfde3..9c90ffcff4 100644
--- a/activerecord/test/models/hotel.rb
+++ b/activerecord/test/models/hotel.rb
@@ -3,5 +3,9 @@ class Hotel < ActiveRecord::Base
has_many :chefs, through: :departments
has_many :cake_designers, source_type: 'CakeDesigner', source: :employable, through: :chefs
has_many :drink_designers, source_type: 'DrinkDesigner', source: :employable, through: :chefs
+
+ has_many :chef_lists, as: :employable_list
+ has_many :mocktail_designers, through: :chef_lists, source: :employable, :source_type => "MocktailDesigner"
+
has_many :recipes, through: :chefs
end
diff --git a/activerecord/test/models/mocktail_designer.rb b/activerecord/test/models/mocktail_designer.rb
new file mode 100644
index 0000000000..77b44651a3
--- /dev/null
+++ b/activerecord/test/models/mocktail_designer.rb
@@ -0,0 +1,2 @@
+class MocktailDesigner < DrinkDesigner
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 23cebe2602..bf3079a1df 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -263,3 +263,10 @@ end
class SerializedPost < ActiveRecord::Base
serialize :title
end
+
+class ConditionalStiPost < Post
+ default_scope { where(title: 'Untitled') }
+end
+
+class SubConditionalStiPost < ConditionalStiPost
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 92e0b197a7..701e6f45b3 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -1,18 +1,25 @@
ActiveRecord::Schema.define do
+
+ if ActiveRecord::Base.connection.version >= '5.6.0'
+ create_table :datetime_defaults, force: true do |t|
+ t.datetime :modified_datetime, default: -> { 'CURRENT_TIMESTAMP' }
+ end
+ end
+
create_table :binary_fields, force: true do |t|
t.binary :var_binary, limit: 255
t.binary :var_binary_large, limit: 4095
- t.blob :tiny_blob, limit: 255
- t.binary :normal_blob, limit: 65535
- t.binary :medium_blob, limit: 16777215
- t.binary :long_blob, limit: 2147483647
- t.text :tiny_text, limit: 255
- t.text :normal_text, limit: 65535
- t.text :medium_text, limit: 16777215
- t.text :long_text, limit: 2147483647
- end
+ t.tinyblob :tiny_blob
+ t.blob :normal_blob
+ t.mediumblob :medium_blob
+ t.longblob :long_blob
+ t.tinytext :tiny_text
+ t.text :normal_text
+ t.mediumtext :medium_text
+ t.longtext :long_text
- add_index :binary_fields, :var_binary
+ t.index :var_binary
+ end
create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
t.string :awesome
@@ -55,7 +62,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('text','blob','tiny','medium','long')
+ enum_column ENUM('text','blob','tiny','medium','long','unsigned')
)
SQL
end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index df0362573b..3a5d73a0ed 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -11,7 +11,23 @@ ActiveRecord::Schema.define do
t.uuid :uuid_parent_id
end
- %w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
+ create_table :defaults, force: true do |t|
+ t.date :modified_date, default: -> { 'CURRENT_DATE' }
+ t.date :modified_date_function, default: -> { 'now()' }
+ t.date :fixed_date, default: '2004-01-01'
+ t.datetime :modified_time, default: -> { 'CURRENT_TIMESTAMP' }
+ t.datetime :modified_time_function, default: -> { 'now()' }
+ t.datetime :fixed_time, default: '2004-01-01 00:00:00.000000-00'
+ t.column :char1, 'char(1)', default: 'Y'
+ t.string :char2, limit: 50, default: 'a varchar field'
+ t.text :char3, default: 'a text field'
+ t.bigint :bigint_default, default: -> { '0::bigint' }
+ t.text :multiline_default, default: '--- []
+
+'
+ end
+
+ %w(postgresql_times postgresql_oids postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
drop_table table_name, if_exists: true
end
@@ -28,25 +44,6 @@ ActiveRecord::Schema.define do
end
execute <<_SQL
- CREATE TABLE defaults (
- id serial primary key,
- modified_date date default CURRENT_DATE,
- modified_date_function date default now(),
- fixed_date date default '2004-01-01',
- modified_time timestamp default CURRENT_TIMESTAMP,
- modified_time_function timestamp default now(),
- fixed_time timestamp default '2004-01-01 00:00:00.000000-00',
- char1 char(1) default 'Y',
- char2 character varying(50) default 'a varchar field',
- char3 text default 'a text field',
- bigint_default bigint default 0::bigint,
- multiline_default text DEFAULT '--- []
-
-'::text
-);
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_times (
id SERIAL PRIMARY KEY,
time_interval INTERVAL,
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 025184f63a..fcc4eee79c 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -104,6 +104,7 @@ ActiveRecord::Schema.define do
t.column :author_visibility, :integer, default: 0
t.column :illustrator_visibility, :integer, default: 0
t.column :font_size, :integer, default: 0
+ t.column :cover, :string, default: 'hard'
end
create_table :booleans, force: true do |t|
@@ -975,6 +976,8 @@ ActiveRecord::Schema.define do
t.integer :employable_id
t.string :employable_type
t.integer :department_id
+ t.string :employable_list_type
+ t.integer :employable_list_id
end
create_table :recipes, force: true do |t|
t.integer :chef_id
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 1ba52444c1..21c79949ca 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,23 @@
+* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`.
+
+ *David Cornu*
+
+* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter`
+ payload when an exception is raised.
+
+ Adds new key/value pair to payload when an exception is raised:
+ e.g. `:exception_object => #<RuntimeError: FAIL>`.
+
+ *Ryan T. Hosford*
+
+* Support extended grapheme clusters and UAX 29.
+
+ *Adam Roben*
+
+* Add petabyte and exabyte numeric conversion.
+
+ *Akshay Vishnoi*
+
## Rails 5.0.0.beta1 (December 18, 2015) ##
* Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread.
@@ -14,7 +34,7 @@
def self.reset() self.account = self.user = nil end
end
- class ApplicationController < ActiveController::Base
+ class ApplicationController < ActionController::Base
before_action :set_current
after_action { Current.reset }
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 7bffebb076..40235833ba 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2015 David Heinemeier Hansson
+Copyright (c) 2005-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 2019afeb00..94fe893149 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2015 David Heinemeier Hansson
+# Copyright (c) 2005-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index dff2443bc8..99c55b1aa4 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -17,6 +17,7 @@ module ActiveSupport
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
EXCLUDED_DIRS = ['.', '..'].freeze
+ GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
def initialize(cache_path, options = nil)
super(options)
@@ -24,10 +25,10 @@ module ActiveSupport
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
+ root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
rescue Errno::ENOENT
end
@@ -154,7 +155,7 @@ module ActiveSupport
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if File.realpath(dir) == File.realpath(cache_path)
- if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty?
+ if exclude_from(dir, EXCLUDED_DIRS).empty?
Dir.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
@@ -193,6 +194,11 @@ module ActiveSupport
end
end
end
+
+ # Exclude entries from source directory
+ def exclude_from(source, excludes)
+ Dir.entries(source).reject { |f| excludes.include?(f) }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index bf560ec1fa..e6baddf5db 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -71,7 +71,7 @@ module ActiveSupport
# halt the entire callback chain and display a deprecation message.
# If false, callback chains will only be halted by calling +throw :abort+.
# Defaults to +true+.
- mattr_accessor(:halt_and_display_warning_on_return_false) { true }
+ mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true }
# Runs the callbacks for the given event.
#
@@ -742,7 +742,7 @@ module ActiveSupport
options = names.extract_options!
names.each do |name|
- class_attribute "_#{name}_callbacks"
+ class_attribute "_#{name}_callbacks", instance_writer: false
set_callbacks name, CallbackChain.new(name, options)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 8594d9bf2e..6741e732f0 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -138,6 +138,8 @@ end
module ActiveSupport
class XMLConverter # :nodoc:
+ # Raised if the XML contains attributes with type="yaml" or
+ # type="symbol". Read Hash#from_xml for more details.
class DisallowedType < StandardError
def initialize(type)
super "Disallowed type attribute: #{type.inspect}"
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 9a3651f29a..9d832897ed 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/number_helper'
+require 'active_support/core_ext/module/deprecation'
module ActiveSupport::NumericWithFormat
@@ -75,6 +76,8 @@ module ActiveSupport::NumericWithFormat
# 1234567.to_s(:human_size) # => 1.18 MB
# 1234567890.to_s(:human_size) # => 1.15 GB
# 1234567890123.to_s(:human_size) # => 1.12 TB
+ # 1234567890123456.to_s(:human_size) # => 1.1 PB
+ # 1234567890123456789.to_s(:human_size) # => 1.07 EB
# 1234567.to_s(:human_size, precision: 2) # => 1.2 MB
# 483989.to_s(:human_size, precision: 2) # => 470 KB
# 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB
@@ -117,7 +120,11 @@ module ActiveSupport::NumericWithFormat
when :human_size
return ActiveSupport::NumberHelper.number_to_human_size(self, options)
else
- super
+ if is_a?(Float) || format.is_a?(Symbol)
+ super()
+ else
+ super
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 04ed8e7cd8..6251f34daf 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -141,6 +141,7 @@ module ActiveSupport #:nodoc:
alias_method :original_concat, :concat
private :original_concat
+ # Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
class SafeConcatError < StandardError
def initialize
super 'Could not concatenate to the buffer because it is not html safe.'
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 877dc84ec8..7a60f94996 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -40,7 +40,23 @@ class Time
Thread.current[:time_zone] = find_zone!(time_zone)
end
- # Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done.
+ # Allows override of <tt>Time.zone</tt> locally inside supplied block;
+ # resets <tt>Time.zone</tt> to existing value when done.
+ #
+ # class ApplicationController < ActionController::Base
+ # around_action :set_time_zone
+ #
+ # private
+ #
+ # def set_time_zone
+ # Time.use_zone(current_user.timezone) { yield }
+ # end
+ # end
+ #
+ # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
+ # objects that have already been created, e.g. any model timestamp
+ # attributes that have been read before the block will remain in
+ # the application's default timezone.
def use_zone(time_zone)
new_zone = find_zone!(time_zone)
begin
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 28d2d78643..0de891f1a2 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,6 +1,8 @@
require "active_support/notifications"
module ActiveSupport
+ # Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
+ # You would set <tt>:raise</tt>, as a behaviour to raise errors and proactively report exceptions from deprecations.
class DeprecationException < StandardError
end
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 32fe8025fe..f5ea6669ce 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -21,15 +21,15 @@ module ActiveSupport
# # => [:aaa, :bbb, :ccc]
#
# Fred.aaa
- # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10)
+ # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
# # => nil
#
# Fred.bbb
- # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.0 (use zzz instead). (called from irb_binding at (irb):11)
+ # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
# # => nil
#
# Fred.ccc
- # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12)
+ # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
# # => nil
#
# Passing in a custom deprecator:
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 7790a9b2c0..3104d0e00f 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveSupport
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4ff35a45a1..b878f31e75 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -68,12 +68,10 @@ module ActiveSupport
end
end
- def default(key = nil)
- if key.is_a?(Symbol) && include?(key = key.to_s)
- self[key]
- else
- super
- end
+ def default(*args)
+ key = args.first
+ args[0] = key.to_s if key.is_a?(Symbol)
+ super(*args)
end
def self.new_from_hash_copying_default(hash)
@@ -159,6 +157,20 @@ module ActiveSupport
alias_method :has_key?, :key?
alias_method :member?, :key?
+
+ # Same as <tt>Hash#[]</tt> where the key passed as argument can be
+ # either a string or a symbol:
+ #
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
+ # counters[:foo] = 1
+ #
+ # counters['foo'] # => 1
+ # counters[:foo] # => 1
+ # counters[:zoo] # => nil
+ def [](key)
+ super(convert_key(key))
+ end
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
# either a string or a symbol:
#
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index a4563ace8f..c64b7598ee 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -106,6 +106,8 @@ en:
mb: "MB"
gb: "GB"
tb: "TB"
+ pb: "PB"
+ eb: "EB"
# Used in NumberHelper.number_to_human()
decimal_units:
format: "%n %u"
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 520268b244..7626b28108 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -5,18 +5,27 @@ module ActiveSupport
class Logger < ::Logger
include LoggerSilence
- attr_accessor :broadcast_messages
+ # Returns true if the logger destination matches one of the sources
+ #
+ # logger = Logger.new(STDOUT)
+ # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
+ # # => true
+ def self.logger_outputs_to?(logger, *sources)
+ logdev = logger.instance_variable_get("@logdev")
+ logger_source = logdev.dev if logdev.respond_to?(:dev)
+ sources.any? { |source| source == logger_source }
+ end
# Broadcasts logs to multiple loggers.
def self.broadcast(logger) # :nodoc:
Module.new do
define_method(:add) do |*args, &block|
- logger.add(*args, &block) if broadcast_messages
+ logger.add(*args, &block)
super(*args, &block)
end
define_method(:<<) do |x|
- logger << x if broadcast_messages
+ logger << x
super(x)
end
@@ -45,7 +54,6 @@ module ActiveSupport
def initialize(*args)
super
@formatter = SimpleFormatter.new
- @broadcast_messages = true
after_initialize if respond_to? :after_initialize
end
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index 690e5596f7..125d81d973 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -42,4 +42,4 @@ module LoggerSilence
yield self
end
end
-end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 586002b03b..72b20fff06 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -87,19 +87,44 @@ module ActiveSupport
pos += 1
previous = codepoints[pos-1]
current = codepoints[pos]
- if (
- # CR X LF
- ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or
- # L X (L|V|LV|LVT)
- ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
- # (LV|V) X (V|T)
- ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
- # (LVT|T) X (T)
- ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or
- # X Extend
- (database.boundary[:extend] === current)
- )
- else
+
+ should_break =
+ # GB3. CR X LF
+ if previous == database.boundary[:cr] and current == database.boundary[:lf]
+ false
+ # GB4. (Control|CR|LF) ÷
+ elsif previous and in_char_class?(previous, [:control,:cr,:lf])
+ true
+ # GB5. ÷ (Control|CR|LF)
+ elsif in_char_class?(current, [:control,:cr,:lf])
+ true
+ # GB6. L X (L|V|LV|LVT)
+ elsif database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt])
+ false
+ # GB7. (LV|V) X (V|T)
+ elsif in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t])
+ false
+ # GB8. (LVT|T) X (T)
+ elsif in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current
+ false
+ # GB8a. Regional_Indicator X Regional_Indicator
+ elsif database.boundary[:regional_indicator] === previous and database.boundary[:regional_indicator] === current
+ false
+ # GB9. X Extend
+ elsif database.boundary[:extend] === current
+ false
+ # GB9a. X SpacingMark
+ elsif database.boundary[:spacingmark] === current
+ false
+ # GB9b. Prepend X
+ elsif database.boundary[:prepend] === previous
+ false
+ # GB10. Any ÷ Any
+ else
+ true
+ end
+
+ if should_break
unpacked << codepoints[marker..pos-1]
marker = pos
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 67f2ee1a7f..91f94cb2d7 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -21,6 +21,7 @@ module ActiveSupport
yield payload
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
+ payload[:exception_object] = e
raise e
ensure
finish_with_state listeners_state, name, payload
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 92a453be48..64d9e71f37 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -243,6 +243,8 @@ module ActiveSupport
# number_to_human_size(1234567) # => 1.18 MB
# number_to_human_size(1234567890) # => 1.15 GB
# number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567890123456) # => 1.1 PB
+ # number_to_human_size(1234567890123456789) # => 1.07 EB
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index a4a8690bcd..a83b368b7f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -1,7 +1,7 @@
module ActiveSupport
module NumberHelper
class NumberToHumanSizeConverter < NumberConverter #:nodoc:
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
self.namespace = :human
self.validate_float = true
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 64c4801179..9be8613ada 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -1,3 +1,5 @@
+require 'digest'
+
module ActiveSupport
module SecurityUtils
# Constant time string comparison.
@@ -16,5 +18,10 @@ module ActiveSupport
res == 0
end
module_function :secure_compare
+
+ def variable_size_secure_compare(a, b) # :nodoc:
+ secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
+ end
+ module_function :variable_size_secure_compare
end
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index ae6f00b861..d9a668c0ea 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -9,7 +9,6 @@ require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
require 'active_support/testing/file_fixtures'
-require 'active_support/testing/composite_filter'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
@@ -39,15 +38,6 @@ module ActiveSupport
def test_order
ActiveSupport.test_order ||= :random
end
-
- def run(reporter, options = {})
- if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
- options[:filter] = \
- Testing::CompositeFilter.new(self, options[:filter], options[:patterns])
- end
-
- super
- end
end
alias_method :method_name, :name
diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb
deleted file mode 100644
index bde723e30b..0000000000
--- a/activesupport/lib/active_support/testing/composite_filter.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'method_source'
-
-module ActiveSupport
- module Testing
- class CompositeFilter # :nodoc:
- def initialize(runnable, filter, patterns)
- @runnable = runnable
- @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
- end
-
- def ===(method)
- @filters.any? { |filter| filter === method }
- end
-
- private
- def derive_regexp(filter)
- filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
- end
-
- def derive_line_filters(patterns)
- patterns.map do |file_and_line|
- file, line = file_and_line.split(':')
- Filter.new(@runnable, file, line) if file
- end
- end
-
- class Filter # :nodoc:
- def initialize(runnable, file, line)
- @runnable, @file = runnable, File.expand_path(file)
- @line = line.to_i if line
- end
-
- def ===(method)
- return unless @runnable.method_defined?(method)
-
- if @line
- test_file, test_range = definition_for(@runnable.instance_method(method))
- test_file == @file && test_range.include?(@line)
- else
- @runnable.instance_method(method).source_location.first == @file
- end
- end
-
- private
- def definition_for(method)
- file, start_line = method.source_location
- end_line = method.source.count("\n") + start_line - 1
-
- return file, start_line..end_line
- end
- end
- end
- end
-end
diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb
index e7d56c80c3..6d4e3b74f7 100644
--- a/activesupport/test/broadcast_logger_test.rb
+++ b/activesupport/test/broadcast_logger_test.rb
@@ -2,69 +2,56 @@ require 'abstract_unit'
module ActiveSupport
class BroadcastLoggerTest < TestCase
- attr_reader :logger, :receiving_logger
+ attr_reader :logger, :log1, :log2
def setup
- @logger = FakeLogger.new
- @receiving_logger = FakeLogger.new
- @logger.extend Logger.broadcast @receiving_logger
+ @log1 = FakeLogger.new
+ @log2 = FakeLogger.new
+ @log1.extend Logger.broadcast @log2
+ @logger = @log1
end
def test_debug
logger.debug "foo"
- assert_equal 'foo', logger.adds.first[2]
- assert_equal 'foo', receiving_logger.adds.first[2]
- end
-
- def test_debug_without_message_broadcasts
- logger.broadcast_messages = false
- logger.debug "foo"
- assert_equal 'foo', logger.adds.first[2]
- assert_equal [], receiving_logger.adds
+ assert_equal 'foo', log1.adds.first[2]
+ assert_equal 'foo', log2.adds.first[2]
end
def test_close
logger.close
- assert logger.closed, 'should be closed'
- assert receiving_logger.closed, 'should be closed'
+ assert log1.closed, 'should be closed'
+ assert log2.closed, 'should be closed'
end
def test_chevrons
logger << "foo"
- assert_equal %w{ foo }, logger.chevrons
- assert_equal %w{ foo }, receiving_logger.chevrons
- end
-
- def test_chevrons_without_message_broadcasts
- logger.broadcast_messages = false
- logger << "foo"
- assert_equal %w{ foo }, logger.chevrons
- assert_equal [], receiving_logger.chevrons
+ assert_equal %w{ foo }, log1.chevrons
+ assert_equal %w{ foo }, log2.chevrons
end
def test_level
assert_nil logger.level
logger.level = 10
- assert_equal 10, logger.level
- assert_equal 10, receiving_logger.level
+ assert_equal 10, log1.level
+ assert_equal 10, log2.level
end
def test_progname
assert_nil logger.progname
logger.progname = 10
- assert_equal 10, logger.progname
- assert_equal 10, receiving_logger.progname
+ assert_equal 10, log1.progname
+ assert_equal 10, log2.progname
end
def test_formatter
assert_nil logger.formatter
logger.formatter = 10
- assert_equal 10, logger.formatter
- assert_equal 10, receiving_logger.formatter
+ assert_equal 10, log1.formatter
+ assert_equal 10, log2.formatter
end
class FakeLogger
attr_reader :adds, :closed, :chevrons
- attr_accessor :level, :progname, :formatter, :broadcast_messages
+ attr_accessor :level, :progname, :formatter
def initialize
@adds = []
@@ -73,7 +60,6 @@ module ActiveSupport
@level = nil
@progname = nil
@formatter = nil
- @broadcast_messages = true
end
def debug msg, &block
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 2701dc2fe9..4a299429f3 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -780,10 +780,12 @@ class FileStoreTest < ActiveSupport::TestCase
include AutoloadingCacheBehavior
def test_clear
- filepath = File.join(cache_dir, ".gitkeep")
- FileUtils.touch(filepath)
+ gitkeep = File.join(cache_dir, ".gitkeep")
+ keep = File.join(cache_dir, ".keep")
+ FileUtils.touch([gitkeep, keep])
@cache.clear
- assert File.exist?(filepath)
+ assert File.exist?(gitkeep)
+ assert File.exist?(keep)
end
def test_clear_without_cache_dir
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 2119352df0..1b66f784e4 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -1587,9 +1587,9 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal 3, hash_wia[:new_key]
end
- def test_should_use_default_proc_if_no_key_is_supplied
+ def test_should_return_nil_if_no_key_is_supplied
hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal 3, hash_wia.default
+ assert_equal nil, hash_wia.default
end
def test_should_use_default_value_for_unknown_key
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 0ff8f0f89b..5654aeb4f8 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -143,6 +143,14 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
gigabytes(number) * 1024
end
+ def petabytes(number)
+ terabytes(number) * 1024
+ end
+
+ def exabytes(number)
+ petabytes(number) * 1024
+ end
+
def test_to_s__phone
assert_equal("555-1234", 5551234.to_s(:phone))
assert_equal("800-555-1212", 8005551212.to_s(:phone))
@@ -266,7 +274,9 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1.18 MB', 1234567.to_s(:human_size)
assert_equal '1.15 GB', 1234567890.to_s(:human_size)
assert_equal '1.12 TB', 1234567890123.to_s(:human_size)
- assert_equal '1030 TB', terabytes(1026).to_s(:human_size)
+ assert_equal '1.1 PB', 1234567890123456.to_s(:human_size)
+ assert_equal '1.07 EB', 1234567890123456789.to_s(:human_size)
+ assert_equal '1030 EB', exabytes(1026).to_s(:human_size)
assert_equal '444 KB', kilobytes(444).to_s(:human_size)
assert_equal '1020 MB', megabytes(1023).to_s(:human_size)
assert_equal '3 TB', terabytes(3).to_s(:human_size)
@@ -289,6 +299,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 PB', 1234567890123456.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 EB', 1234567890123456789.to_s(:human_size, :prefix => :si)
end
end
@@ -388,6 +400,32 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
+ def test_to_formatted_s_is_deprecated
+ assert_deprecated do
+ 5551234.to_formatted_s(:phone)
+ end
+ end
+
+ def test_to_s_with_invalid_formatter
+ assert_equal '123', 123.to_s(:invalid)
+ assert_equal '2.5', 2.5.to_s(:invalid)
+ assert_equal '100000000000000000000', (100**10).to_s(:invalid)
+ assert_equal '1000010.0', BigDecimal("1000010").to_s(:invalid)
+ end
+
+ def test_default_to_s
+ assert_equal '123', 123.to_s
+ assert_equal '1111011', 123.to_s(2)
+
+ assert_equal '2.5', 2.5.to_s
+
+ assert_equal '100000000000000000000', (100**10).to_s
+ assert_equal '1010110101111000111010111100010110101100011000100000000000000000000', (100**10).to_s(2)
+
+ assert_equal '1000010.0', BigDecimal("1000010").to_s
+ assert_equal '10000 10.0', BigDecimal("1000010").to_s('5F')
+ end
+
def test_in_milliseconds
assert_equal 10_000, 10.seconds.in_milliseconds
end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index f096328cee..f28cebda3d 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/time'
+require 'active_support/core_ext/numeric'
require 'active_support/core_ext/range'
class RangeTest < ActiveSupport::TestCase
@@ -13,6 +14,11 @@ class RangeTest < ActiveSupport::TestCase
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
+ def test_to_s_with_numeric
+ number_range = (1..100)
+ assert_equal "BETWEEN '1' AND '100'", number_range.to_s(:db)
+ end
+
def test_date_range
assert_instance_of Range, DateTime.new..DateTime.new
assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index a57dc7a241..317e09b7f2 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -17,6 +17,14 @@ class LoggerTest < ActiveSupport::TestCase
@logger = Logger.new(@output)
end
+ def test_log_outputs_to
+ assert Logger.logger_outputs_to?(@logger, @output), "Expected logger_outputs_to? @output to return true but was false"
+ assert Logger.logger_outputs_to?(@logger, @output, STDOUT), "Expected logger_outputs_to? @output or STDOUT to return true but was false"
+
+ assert_not Logger.logger_outputs_to?(@logger, STDOUT), "Expected logger_outputs_to? to STDOUT to return false, but was true"
+ assert_not Logger.logger_outputs_to?(@logger, STDOUT, STDERR), "Expected logger_outputs_to? to STDOUT or STDERR to return false, but was true"
+ end
+
def test_write_binary_data_to_existing_file
t = Tempfile.new ['development', 'log']
t.binmode
@@ -65,7 +73,7 @@ class LoggerTest < ActiveSupport::TestCase
def test_should_not_log_debug_messages_when_log_level_is_info
@logger.level = Logger::INFO
@logger.add(Logger::DEBUG, @message)
- assert ! @output.string.include?(@message)
+ assert_not @output.string.include?(@message)
end
def test_should_add_message_passed_as_block_when_using_add
@@ -129,7 +137,7 @@ class LoggerTest < ActiveSupport::TestCase
@logger.error "THIS IS HERE"
end
- assert !@output.string.include?("NOT THERE")
+ assert_not @output.string.include?("NOT THERE")
assert @output.string.include?("THIS IS HERE")
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 8d4d9d736c..c1e0b19248 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -612,28 +612,54 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
['abc', 3],
['こにちわ', 4],
[[0x0924, 0x094D, 0x0930].pack('U*'), 2],
+ # GB3
[%w(cr lf), 1],
+ # GB4
+ [%w(cr n), 2],
+ [%w(lf n), 2],
+ [%w(control n), 2],
+ [%w(cr extend), 2],
+ [%w(lf extend), 2],
+ [%w(control extend), 2],
+ # GB 5
+ [%w(n cr), 2],
+ [%w(n lf), 2],
+ [%w(n control), 2],
+ [%w(extend cr), 2],
+ [%w(extend lf), 2],
+ [%w(extend control), 2],
+ # GB 6
[%w(l l), 1],
[%w(l v), 1],
[%w(l lv), 1],
[%w(l lvt), 1],
+ # GB7
[%w(lv v), 1],
[%w(lv t), 1],
[%w(v v), 1],
[%w(v t), 1],
+ # GB8
[%w(lvt t), 1],
[%w(t t), 1],
+ # GB8a
+ [%w(r r), 1],
+ # GB9
[%w(n extend), 1],
+ # GB9a
+ [%w(n spacingmark), 1],
+ # GB10
[%w(n n), 2],
+ # Other
[%w(n cr lf n), 3],
- [%w(n l v t), 2]
+ [%w(n l v t), 2],
+ [%w(cr extend n), 3],
].each do |input, expected_length|
if input.kind_of?(Array)
str = string_from_classes(input)
else
str = input
end
- assert_equal expected_length, chars(str).grapheme_length
+ assert_equal expected_length, chars(str).grapheme_length, input.inspect
end
end
@@ -698,7 +724,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
# Characters from the character classes as described in UAX #29
character_from_class = {
:l => 0x1100, :v => 0x1160, :t => 0x11A8, :lv => 0xAC00, :lvt => 0xAC01, :cr => 0x000D, :lf => 0x000A,
- :extend => 0x094D, :n => 0x64
+ :extend => 0x094D, :n => 0x64, :spacingmark => 0x0903, :r => 0x1F1E6, :control => 0x0001
}
classes.collect do |k|
character_from_class[k.intern]
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index d493a48fe4..5df8f32e46 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -5,25 +5,25 @@ require 'fileutils'
require 'open-uri'
require 'tmpdir'
-class Downloader
- def self.download(from, to)
- unless File.exist?(to)
- unless File.exist?(File.dirname(to))
- system "mkdir -p #{File.dirname(to)}"
- end
- open(from) do |source|
- File.open(to, 'w') do |target|
- source.each_line do |l|
- target.write l
+class MultibyteConformanceTest < ActiveSupport::TestCase
+ class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
end
end
end
+ true
end
- true
end
-end
-class MultibyteConformanceTest < ActiveSupport::TestCase
include MultibyteTestHelpers
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
new file mode 100644
index 0000000000..229f24990e
--- /dev/null
+++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
@@ -0,0 +1,76 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
+ class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ $stderr.puts "Downloading #{from} to #{to}"
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
+ end
+ end
+ end
+ end
+ end
+
+ TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary"
+ TEST_DATA_FILE = '/GraphemeBreakTest.txt'
+ CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+
+ def setup
+ FileUtils.mkdir_p(CACHE_DIR)
+ Downloader.download(TEST_DATA_URL + TEST_DATA_FILE, CACHE_DIR + TEST_DATA_FILE)
+ end
+
+ def test_breaks
+ each_line_of_break_tests do |*cols|
+ *clusters, comment = *cols
+ packed = ActiveSupport::Multibyte::Unicode.pack_graphemes(clusters)
+ assert_equal clusters, ActiveSupport::Multibyte::Unicode.unpack_graphemes(packed), comment
+ end
+ end
+
+ protected
+ def each_line_of_break_tests(&block)
+ lines = 0
+ max_test_lines = 0 # Don't limit below 21, because that's the header of the testfile
+ File.open(File.join(CACHE_DIR, TEST_DATA_FILE), 'r') do | f |
+ until f.eof? || (max_test_lines > 21 and lines > max_test_lines)
+ lines += 1
+ line = f.gets.chomp!
+ next if (line.empty? || line =~ /^\#/)
+
+ cols, comment = line.split("#")
+ # Cluster breaks are represented by ÷
+ clusters = cols.split("÷").map{|e| e.strip}.reject{|e| e.empty? }
+ clusters = clusters.map do |cluster|
+ # Codepoints within each cluster are separated by ×
+ codepoints = cluster.split("×").map{|e| e.strip}.reject{|e| e.empty? }
+ # codepoints are in hex in the test suite, pack wants them as integers
+ codepoints.map{|codepoint| codepoint.to_i(16)}
+ end
+
+ # The tests contain a solitary U+D800 <Non Private Use High
+ # Surrogate, First> character, which Ruby does not allow to stand
+ # alone in a UTF-8 string. So we'll just skip it.
+ next if clusters.flatten.include?(0xd800)
+
+ clusters << comment.strip
+
+ yield(*clusters)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb
new file mode 100644
index 0000000000..8bc91ef708
--- /dev/null
+++ b/activesupport/test/multibyte_normalization_conformance_test.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+require 'multibyte_test_helpers'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
+ class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ $stderr.puts "Downloading #{from} to #{to}"
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
+ end
+ end
+ end
+ end
+ end
+
+ include MultibyteTestHelpers
+
+ UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
+ UNIDATA_FILE = '/NormalizationTest.txt'
+ CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+
+ def setup
+ FileUtils.mkdir_p(CACHE_DIR)
+ Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
+ @proxy = ActiveSupport::Multibyte::Chars
+ end
+
+ def test_normalizations_C
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+
+ # CONFORMANCE:
+ # 1. The following invariants must be true for all conformant implementations
+ #
+ # NFC
+ # c2 == NFC(c1) == NFC(c2) == NFC(c3)
+ assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
+ #
+ # c4 == NFC(c4) == NFC(c5)
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_D
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFD
+ # c3 == NFD(c1) == NFD(c2) == NFD(c3)
+ assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
+ # c5 == NFD(c4) == NFD(c5)
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_KC
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKC
+ # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
+ assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_KD
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKD
+ # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
+ assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
+ end
+ end
+
+ protected
+ def each_line_of_norm_tests(&block)
+ lines = 0
+ max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile
+ File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f |
+ until f.eof? || (max_test_lines > 38 and lines > max_test_lines)
+ lines += 1
+ line = f.gets.chomp!
+ next if (line.empty? || line =~ /^\#/)
+
+ cols, comment = line.split("#")
+ cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? }
+ next unless cols.length == 5
+
+ # codepoints are in hex in the test suite, pack wants them as integers
+ cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") }
+ cols << comment
+
+ yield(*cols)
+ end
+ end
+ end
+
+ def inspect_codepoints(str)
+ str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ')
+ end
+end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index d9cc392ac9..1cb17e6197 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -232,7 +232,7 @@ module Notifications
assert_equal 1, @events.size
assert_equal Hash[:payload => "notifications",
- :exception => ["RuntimeError", "FAIL"]], @events.last.payload
+ :exception => ["RuntimeError", "FAIL"], :exception_object => e], @events.last.payload
end
def test_event_is_pushed_even_without_block
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 7f62d7c0b3..b3464462c8 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -34,6 +34,14 @@ module ActiveSupport
gigabytes(number) * 1024
end
+ def petabytes(number)
+ terabytes(number) * 1024
+ end
+
+ def exabytes(number)
+ petabytes(number) * 1024
+ end
+
def test_number_to_phone
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert_equal("555-1234", number_helper.number_to_phone(5551234))
@@ -219,7 +227,9 @@ module ActiveSupport
assert_equal '1.18 MB', number_helper.number_to_human_size(1234567)
assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890)
assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123)
- assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026))
+ assert_equal '1.1 PB', number_helper.number_to_human_size(1234567890123456)
+ assert_equal '1.07 EB', number_helper.number_to_human_size(1234567890123456789)
+ assert_equal '1030 EB', number_helper.number_to_human_size(exabytes(1026))
assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444))
assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023))
assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3))
@@ -245,6 +255,8 @@ module ActiveSupport
assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ assert_equal '1.23 PB', number_helper.number_to_human_size(1234567890123456, :prefix => :si)
+ assert_equal '1.23 EB', number_helper.number_to_human_size(1234567890123456789, :prefix => :si)
end
end
end
diff --git a/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png
index 43c963ffa8..077d237e4e 100644
--- a/guides/assets/images/belongs_to.png
+++ b/guides/assets/images/belongs_to.png
Binary files differ
diff --git a/guides/assets/images/has_many.png b/guides/assets/images/has_many.png
index e7589e3b75..79da2613d7 100644
--- a/guides/assets/images/has_many.png
+++ b/guides/assets/images/has_many.png
Binary files differ
diff --git a/guides/assets/images/rails_guides_logo.gif b/guides/assets/images/rails_guides_logo.gif
index 9b0ad5af28..f7149a0415 100644
--- a/guides/assets/images/rails_guides_logo.gif
+++ b/guides/assets/images/rails_guides_logo.gif
Binary files differ
diff --git a/guides/assets/javascripts/responsive-tables.js b/guides/assets/javascripts/responsive-tables.js
index 8554a1343b..8554a1343b 100755..100644
--- a/guides/assets/javascripts/responsive-tables.js
+++ b/guides/assets/javascripts/responsive-tables.js
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 8c2e43d0f8..2650384df3 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -82,6 +82,9 @@ Please refer to the [Changelog][railties] for detailed changes.
* Removed the documentation tasks `doc:app`, `doc:rails`, and `doc:guides`.
([commit](https://github.com/rails/rails/commit/cd7cc5254b090ccbb84dcee4408a5acede25ef2a))
+* Removed `Rack::ContentLength` middleware from the default
+ stack. ([Commit](https://github.com/rails/rails/commit/56903585a099ab67a7acfaaef0a02db8fe80c450))
+
### Deprecations
* Deprecated `config.static_cache_control` in favor of
@@ -107,9 +110,6 @@ Please refer to the [Changelog][railties] for detailed changes.
the order they are invoked by Rails.
([Pull Request](https://github.com/rails/rails/pull/19323))
-* Removed `Rack::ContentLength` middleware from the default
- stack. ([Commit](https://github.com/rails/rails/commit/56903585a099ab67a7acfaaef0a02db8fe80c450))
-
* Added `bin/rails dev:cache` to enable or disable caching in development mode.
([Pull Request](https://github.com/rails/rails/pull/20961))
@@ -431,6 +431,10 @@ Please refer to the [Changelog][active-record] for detailed changes.
`#table_exists?` will check only tables in the future.
([Pull Request](https://github.com/rails/rails/pull/21601))
+* Deprecate sending the `offset` argument to `find_nth`. Please use the
+ `offset` method on relation instead.
+ ([Pull Request](https://github.com/rails/rails/pull/22053))
+
### Notable changes
* Added a `foreign_key` option to `references` while creating the table.
@@ -490,9 +494,6 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Added `config.active_record.warn_on_records_fetched_greater_than` option.
([Pull Request](https://github.com/rails/rails/pull/18846))
-* Added `cache_key` to `ActiveRecord::Relation`.
- ([Pull Request](https://github.com/rails/rails/pull/20884))
-
* Added a native JSON data type support in MySQL.
([Pull Request](https://github.com/rails/rails/pull/21110))
@@ -646,7 +647,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/16938))
* Removed deprecated `ThreadSafe::Cache`. Use `Concurrent::Map` instead.
- ([Pull Request](https://github.com/rails/rails/pull/16938))
+ ([Pull Request](https://github.com/rails/rails/pull/21679))
### Deprecations
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index d2e2d27737..9ef2c1a441 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -995,10 +995,6 @@ you would like in a response object. The `ActionController::Live` module allows
you to create a persistent connection with a browser. Using this module, you will
be able to send arbitrary data to the browser at specific points in time.
-NOTE: The default Rails server (WEBrick) is a buffering web server and does not
-support streaming. In order to use this feature, you'll need to use a non buffering
-server like [Puma](http://puma.io), [Rainbows](http://rainbows.bogomips.org)
-or [Passenger](https://www.phusionpassenger.com).
#### Incorporating Live Streaming
@@ -1118,7 +1114,7 @@ Rails default exception handling displays a "500 Server Error" message for all e
### The Default 500 and 404 Templates
-By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
+By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
### `rescue_from`
@@ -1174,7 +1170,11 @@ end
WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development).
-NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed.
+NOTE: When running in the production environment, all
+`ActiveRecord::RecordNotFound` errors render the 404 error page. Unless you need
+a custom behavior you don't need to handle this.
+
+NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed.
Force HTTPS protocol
--------------------
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index e36c0f899f..76c13f0ea9 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -174,7 +174,7 @@ module YourApp
end
end
-# app/jobs/guests_cleanup.rb
+# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ActiveJob::Base
queue_as :low_priority
#....
@@ -197,7 +197,7 @@ module YourApp
end
end
-# app/jobs/guests_cleanup.rb
+# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ActiveJob::Base
queue_as :low_priority
#....
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index d95c6c0e78..fb5d2065d3 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -258,7 +258,7 @@ As you start registering new callbacks for your models, they will be queued for
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.
-WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` 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.
+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.
Relational Callbacks
--------------------
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index a4a23395fb..83f4b951ee 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -12,7 +12,7 @@ After reading this guide, you will know:
* The generators you can use to create them.
* The methods Active Record provides to manipulate your database.
-* The Rake tasks that manipulate migrations and your schema.
+* The bin/rails tasks that manipulate migrations and your schema.
* How migrations relate to `schema.rb`.
--------------------------------------------------------------------------------
@@ -717,9 +717,9 @@ you will have to use `structure.sql` as dump method. See
Running Migrations
------------------
-Rails provides a set of Rake tasks to run certain sets of migrations.
+Rails provides a set of bin/rails tasks to run certain sets of migrations.
-The very first migration related Rake task you will use will probably be
+The very first migration related bin/rails task you will use will probably be
`rails db:migrate`. In its most basic form it just runs the `change` or `up`
method for all the migrations that have not yet been run. If there are
no such migrations, it exits. It will run these migrations in order based
@@ -772,7 +772,7 @@ if you need to go more than one version back, for example:
$ bin/rails db:migrate:redo STEP=3
```
-Neither of these Rake tasks do anything you could not do with `db:migrate`. They
+Neither of these bin/rails tasks do anything you could not do with `db:migrate`. They
are simply more convenient, since you do not need to explicitly specify the
version to migrate to.
@@ -784,7 +784,7 @@ it with the seed data.
### Resetting the Database
The `rails db:reset` task will drop the database and set it up again. This is
-functionally equivalent to `rake db:drop db:setup`.
+functionally equivalent to `rails db:drop db:setup`.
NOTE: This is not the same as running all the migrations. It will only use the
contents of the current `db/schema.rb` or `db/structure.sql` file. If a migration can't be rolled back,
@@ -809,7 +809,7 @@ Active Record believes that it has already been run.
### Running Migrations in Different Environments
-By default running `rake db:migrate` will run in the `development` environment.
+By default running `bin/rails db:migrate` will run in the `development` environment.
To run migrations against another environment you can specify it using the
`RAILS_ENV` environment variable while running the command. For example to run
migrations against the `test` environment you could run:
@@ -886,7 +886,7 @@ Occasionally you will make a mistake when writing a migration. If you have
already run the migration then you cannot just edit the migration and run the
migration again: Rails thinks it has already run the migration and so will do
nothing when you run `rails db:migrate`. You must rollback the migration (for
-example with `rake db:rollback`), edit your migration and then run
+example with `bin/rails db:rollback`), edit your migration and then run
`rails db:migrate` to run the corrected version.
In general, editing existing migrations is not a good idea. You will be
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index b592209d4b..68c6a77882 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -84,6 +84,7 @@ Book.where("array_length(ratings, 1) >= 3")
### Hstore
* [type definition](http://www.postgresql.org/docs/current/static/hstore.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/hstore.html#AEN167712)
NOTE: You need to enable the `hstore` extension to use hstore.
@@ -108,6 +109,9 @@ profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
+
+Profile.where("settings->'color' = ?", "yellow")
+#=> #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>
```
### JSON
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 4606ac4683..784be91845 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -59,7 +59,7 @@ To retrieve objects from the database, Active Record provides several finder met
The methods are:
-* `bind`
+* `find`
* `create_with`
* `distinct`
* `eager_load`
@@ -170,7 +170,7 @@ TIP: The retrieved record may vary depending on the database engine.
#### `first`
-The `first` method finds the first record ordered by the primary key. For example:
+The `first` method finds the first record ordered by primary key (default). For example:
```ruby
client = Client.first
@@ -204,11 +204,24 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3
```
+On a collection that is ordered using `order`, `first` will return the first record ordered by the specified attribute for `order`.
+
+```ruby
+client = Client.order(:first_name).first
+# => #<Client id: 2, first_name: "Fifo">
+```
+
+The SQL equivalent of the above is:
+
+```sql
+SELECT * FROM clients ORDER BY clients.first_name ASC LIMIT 1
+```
+
The `first!` method behaves exactly like `first`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
#### `last`
-The `last` method finds the last record ordered by the primary key. For example:
+The `last` method finds the last record ordered by primary key (default). For example:
```ruby
client = Client.last
@@ -242,6 +255,19 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3
```
+On a collection that is ordered using `order`, `last` will return the last record ordered by the specified attribute for `order`.
+
+```ruby
+client = Client.order(:first_name).last
+# => #<Client id: 220, first_name: "Sara">
+```
+
+The SQL equivalent of the above is:
+
+```sql
+SELECT * FROM clients ORDER BY clients.first_name DESC LIMIT 1
+```
+
The `last!` method behaves exactly like `last`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
#### `find_by`
@@ -322,7 +348,7 @@ end
The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`.
-Three additional options, `:batch_size`, `:begin_at` and `:end_at`, are available as well.
+Three additional options, `:batch_size`, `:start` and `:finish`, are available as well.
**`:batch_size`**
@@ -334,34 +360,34 @@ User.find_each(batch_size: 5000) do |user|
end
```
-**`:begin_at`**
+**`:start`**
-By default, records are fetched in ascending order of the primary key, which must be an integer. The `:begin_at` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
+By default, records are fetched in ascending order of the primary key, which must be an integer. The `:start` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000:
```ruby
-User.find_each(begin_at: 2000, batch_size: 5000) do |user|
+User.find_each(start: 2000, batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
```
-**`:end_at`**
+**`:finish`**
-Similar to the `:begin_at` option, `:end_at` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need.
-This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:begin_at` and `:end_at`
+Similar to the `:start` option, `:finish` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need.
+This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:start` and `:finish`
For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 5000:
```ruby
-User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user|
+User.find_each(start: 2000, finish: 10000, batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
```
Another example would be if you wanted multiple workers handling the same
processing queue. You could have each worker handle 10000 records by setting the
-appropriate `:begin_at` and `:end_at` options on each worker.
+appropriate `:start` and `:finish` options on each worker.
#### `find_in_batches`
@@ -376,7 +402,7 @@ end
##### Options for `find_in_batches`
-The `find_in_batches` method accepts the same `:batch_size`, `:begin_at` and `:end_at` options as `find_each`.
+The `find_in_batches` method accepts the same `:batch_size`, `:start` and `:finish` options as `find_each`.
Conditions
----------
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 0cb34aa8bc..0aca6db9b6 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -2024,12 +2024,14 @@ Produce a string representation of a number rounded to a precision:
Produce a string representation of a number as a human-readable number of bytes:
```ruby
-123.to_s(:human_size) # => 123 Bytes
-1234.to_s(:human_size) # => 1.21 KB
-12345.to_s(:human_size) # => 12.1 KB
-1234567.to_s(:human_size) # => 1.18 MB
-1234567890.to_s(:human_size) # => 1.15 GB
-1234567890123.to_s(:human_size) # => 1.12 TB
+123.to_s(:human_size) # => 123 Bytes
+1234.to_s(:human_size) # => 1.21 KB
+12345.to_s(:human_size) # => 12.1 KB
+1234567.to_s(:human_size) # => 1.18 MB
+1234567890.to_s(:human_size) # => 1.15 GB
+1234567890123.to_s(:human_size) # => 1.12 TB
+1234567890123456.to_s(:human_size) # => 1.1 PB
+1234567890123456789.to_s(:human_size) # => 1.07 EB
```
Produce a string representation of a number in human-readable words:
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 0083fc0e6c..60f78d011c 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1280,8 +1280,9 @@ config.assets.debug = true
And in `production.rb`:
```ruby
-# Choose the compressors to use (if any) config.assets.js_compressor =
-# :uglifier config.assets.css_compressor = :yui
+# Choose the compressors to use (if any)
+config.assets.js_compressor = :uglifier
+# config.assets.css_compressor = :yui
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
@@ -1290,7 +1291,8 @@ config.assets.compile = false
config.assets.digest = true
# Precompile additional assets (application.js, application.css, and all
-# non-JS/CSS are already added) config.assets.precompile += %w( search.js )
+# non-JS/CSS are already added)
+# config.assets.precompile += %w( search.js )
```
Rails 4 no longer sets default config values for Sprockets in `test.rb`, so
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index d83dda7228..accce5a904 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -16,54 +16,54 @@ After reading this guide, you will know:
Why Associations?
-----------------
-In Rails, an _association_ is a connection between two Active Record models. Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for customers and a model for orders. Each customer can have many orders. Without associations, the model declarations would look like this:
+In Rails, an _association_ is a connection between two Active Record models. Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for authors and a model for books. Each author can have many books. Without associations, the model declarations would look like this:
```ruby
-class Customer < ApplicationRecord
+class Author < ApplicationRecord
end
-class Order < ApplicationRecord
+class Book < ApplicationRecord
end
```
-Now, suppose we wanted to add a new order for an existing customer. We'd need to do something like this:
+Now, suppose we wanted to add a new book for an existing author. We'd need to do something like this:
```ruby
-@order = Order.create(order_date: Time.now, customer_id: @customer.id)
+@book = Book.create(published_at: Time.now, author_id: @author.id)
```
-Or consider deleting a customer, and ensuring that all of its orders get deleted as well:
+Or consider deleting an author, and ensuring that all of its books get deleted as well:
```ruby
-@orders = Order.where(customer_id: @customer.id)
-@orders.each do |order|
- order.destroy
+@books = Book.where(author_id: @author.id)
+@books.each do |book|
+ book.destroy
end
-@customer.destroy
+@author.destroy
```
-With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders:
+With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up authors and books:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, dependent: :destroy
+class Author < ApplicationRecord
+ has_many :books, dependent: :destroy
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
-With this change, creating a new order for a particular customer is easier:
+With this change, creating a new book for a particular author is easier:
```ruby
-@order = @customer.orders.create(order_date: Time.now)
+@book = @author.books.create(published_at: Time.now)
```
-Deleting a customer and all of its orders is *much* easier:
+Deleting an author and all of its books is *much* easier:
```ruby
-@customer.destroy
+@author.destroy
```
To learn more about the different types of associations, read the next section of this guide. That's followed by some tips and tricks for working with associations, and then by a complete reference to the methods and options for associations in Rails.
@@ -86,31 +86,31 @@ In the remainder of this guide, you'll learn how to declare and use the various
### The `belongs_to` Association
-A `belongs_to` association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way:
+A `belongs_to` association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes authors and books, and each book can be assigned to exactly one author, you'd declare the book model this way:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
![belongs_to Association Diagram](images/belongs_to.png)
-NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `customer` association in the `Order` model, you would be told that there was an "uninitialized constant Order::Customers". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.
+NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `author` association in the `Book` model, you would be told that there was an "uninitialized constant Book::Authors". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.
The corresponding migration might look like this:
```ruby
-class CreateOrders < ActiveRecord::Migration[5.0]
+class CreateBooks < ActiveRecord::Migration[5.0]
def change
- create_table :customers do |t|
+ create_table :authors do |t|
t.string :name
t.timestamps null: false
end
- create_table :orders do |t|
- t.belongs_to :customer, index: true
- t.datetime :order_date
+ create_table :books do |t|
+ t.belongs_to :author, index: true
+ t.datetime :published_at
t.timestamps null: false
end
end
@@ -161,11 +161,11 @@ end
### The `has_many` Association
-A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
+A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing authors and books, the author model could be declared like this:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
@@ -176,16 +176,16 @@ NOTE: The name of the other model is pluralized when declaring a `has_many` asso
The corresponding migration might look like this:
```ruby
-class CreateCustomers < ActiveRecord::Migration[5.0]
+class CreateAuthors < ActiveRecord::Migration[5.0]
def change
- create_table :customers do |t|
+ create_table :authors do |t|
t.string :name
t.timestamps null: false
end
- create_table :orders do |t|
- t.belongs_to :customer, index: true
- t.datetime :order_date
+ create_table :books do |t|
+ t.belongs_to :author, index: true
+ t.datetime :published_at
t.timestamps null: false
end
end
@@ -540,17 +540,17 @@ Here are a few things you should know to make efficient use of Active Record ass
All of the association methods are built around caching, which keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example:
```ruby
-customer.orders # retrieves orders from the database
-customer.orders.size # uses the cached copy of orders
-customer.orders.empty? # uses the cached copy of orders
+author.books # retrieves books from the database
+author.books.size # uses the cached copy of books
+author.books.empty? # uses the cached copy of books
```
But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass `true` to the association call:
```ruby
-customer.orders # retrieves orders from the database
-customer.orders.size # uses the cached copy of orders
-customer.orders(true).empty? # discards the cached copy of orders
+author.books # retrieves books from the database
+author.books.size # uses the cached copy of books
+author.books(true).empty? # discards the cached copy of books
# and goes back to the database
```
@@ -567,23 +567,23 @@ Associations are extremely useful, but they are not magic. You are responsible f
When you declare a `belongs_to` association, you need to create foreign keys as appropriate. For example, consider this model:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
-This declaration needs to be backed up by the proper foreign key declaration on the orders table:
+This declaration needs to be backed up by the proper foreign key declaration on the books table:
```ruby
-class CreateOrders < ActiveRecord::Migration[5.0]
+class CreateBooks < ActiveRecord::Migration[5.0]
def change
- create_table :orders do |t|
- t.datetime :order_date
- t.string :order_number
- t.integer :customer_id
+ create_table :books do |t|
+ t.datetime :published_at
+ t.string :book_number
+ t.integer :author_id
end
- add_index :orders, :customer_id
+ add_index :books, :author_id
end
end
```
@@ -592,7 +592,7 @@ If you create an association some time after you build the underlying model, you
#### 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 order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.
+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.
WARNING: The precedence between model names is calculated using the `<=>` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings).
@@ -700,45 +700,45 @@ end
It's normal for associations to work in two directions, requiring declaration on two different models:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
By default, Active Record doesn't know about the connection between these associations. This can lead to two copies of an object getting out of sync:
```ruby
-c = Customer.first
-o = c.orders.first
-c.first_name == o.customer.first_name # => true
-c.first_name = 'Manny'
-c.first_name == o.customer.first_name # => false
+a = Author.first
+b = c.books.first
+a.first_name == b.author.first_name # => true
+a.first_name = 'Manny'
+a.first_name == b.author.first_name # => false
```
-This happens because `c` and `o.customer` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
+This happens because `a` and `b.author` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, inverse_of: :customer
+class Author < ApplicationRecord
+ has_many :books, inverse_of: :author
end
-class Order < ApplicationRecord
- belongs_to :customer, inverse_of: :orders
+class book < ApplicationRecord
+ belongs_to :author, inverse_of: :books
end
```
-With these changes, Active Record will only load one copy of the customer object, preventing inconsistencies and making your application more efficient:
+With these changes, Active Record will only load one copy of the author object, preventing inconsistencies and making your application more efficient:
```ruby
-c = Customer.first
-o = c.orders.first
-c.first_name == o.customer.first_name # => true
-c.first_name = 'Manny'
-c.first_name == o.customer.first_name # => true
+a = author.first
+b = c.books.first
+a.first_name == b.author.first_name # => true
+a.first_name = 'Manny'
+a.first_name == b.author.first_name # => true
```
There are a few limitations to `inverse_of` support:
@@ -781,19 +781,19 @@ When you declare a `belongs_to` association, the declaring class automatically g
In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
-Each instance of the `Order` model will have these methods:
+Each instance of the `Book` model will have these methods:
```ruby
-customer
-customer=
-build_customer
-create_customer
-create_customer!
+author
+author=
+build_author
+create_author
+create_author!
```
NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix.
@@ -803,13 +803,13 @@ NOTE: When initializing a new `has_one` or `belongs_to` association you must use
The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`.
```ruby
-@customer = @order.customer
+@author = @book.author
```
If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object.
```ruby
-@customer = @order.reload.customer
+@author = @book.reload.author
```
##### `association=(associate)`
@@ -817,7 +817,7 @@ If the associated object has already been retrieved from the database for this o
The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associated object and setting this object's foreign key to the same value.
```ruby
-@order.customer = @customer
+@book.author = @author
```
##### `build_association(attributes = {})`
@@ -825,8 +825,8 @@ The `association=` method assigns an associated object to this object. Behind th
The `build_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through this object's foreign key will be set, but the associated object will _not_ yet be saved.
```ruby
-@customer = @order.build_customer(customer_number: 123,
- customer_name: "John Doe")
+@author = @book.build_author(author_number: 123,
+ author_name: "John Doe")
```
##### `create_association(attributes = {})`
@@ -834,8 +834,8 @@ The `build_association` method returns a new object of the associated type. This
The `create_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through this object's foreign key will be set, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved.
```ruby
-@customer = @order.create_customer(customer_number: 123,
- customer_name: "John Doe")
+@author = @book.create_author(author_number: 123,
+ author_name: "John Doe")
```
##### `create_association!(attributes = {})`
@@ -848,8 +848,8 @@ Does the same as `create_association` above, but raises `ActiveRecord::RecordInv
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `belongs_to` association reference. Such customizations can easily be accomplished by passing options and scope blocks when you create the association. For example, this association uses two such options:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, dependent: :destroy,
+class Book < ApplicationRecord
+ belongs_to :author, dependent: :destroy,
counter_cache: true
end
```
@@ -874,11 +874,11 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
##### `:class_name`
-If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if an order belongs to a customer, but the actual name of the model containing customers is `Patron`, you'd set things up this way:
+If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a book belongs to an author, but the actual name of the model containing authors is `Patron`, you'd set things up this way:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, class_name: "Patron"
+class Book < ApplicationRecord
+ belongs_to :author, class_name: "Patron"
end
```
@@ -887,22 +887,22 @@ end
The `:counter_cache` option can be used to make finding the number of belonging objects more efficient. Consider these models:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-With these declarations, asking for the value of `@customer.orders.size` requires making a call to the database to perform a `COUNT(*)` query. To avoid this call, you can add a counter cache to the _belonging_ model:
+With these declarations, asking for the value of `@author.books.size` requires making a call to the database to perform a `COUNT(*)` query. To avoid this call, you can add a counter cache to the _belonging_ model:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, counter_cache: true
+class Book < ApplicationRecord
+ belongs_to :author, counter_cache: true
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
@@ -911,18 +911,18 @@ With this declaration, Rails will keep the cache value up to date, and then retu
Although the `:counter_cache` option is specified on the model that includes
the `belongs_to` declaration, the actual column must be added to the
_associated_ (`has_many`) model. In the case above, you would need to add a
-column named `orders_count` to the `Customer` model.
+column named `books_count` to the `Author` model.
You can override the default column name by specifying a custom column name in
the `counter_cache` declaration instead of `true`. For example, to use
-`count_of_orders` instead of `orders_count`:
+`count_of_books` instead of `books_count`:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, counter_cache: :count_of_orders
+class Book < ApplicationRecord
+ belongs_to :author, counter_cache: :count_of_books
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
@@ -949,8 +949,8 @@ WARNING: You should not specify this option on a `belongs_to` association that i
By convention, Rails assumes that the column used to hold the foreign key on this model is the name of the association with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, class_name: "Patron",
+class Book < ApplicationRecord
+ belongs_to :author, class_name: "Patron",
foreign_key: "patron_id"
end
```
@@ -982,12 +982,12 @@ When we execute `@user.todos.create` then the `@todo` record will have its
The `:inverse_of` option specifies the name of the `has_many` or `has_one` association that is the inverse of this association. Does not work in combination with the `:polymorphic` options.
```ruby
-class Customer < ApplicationRecord
- has_many :orders, inverse_of: :customer
+class Author < ApplicationRecord
+ has_many :books, inverse_of: :author
end
-class Order < ApplicationRecord
- belongs_to :customer, inverse_of: :orders
+class Book < ApplicationRecord
+ belongs_to :author, inverse_of: :books
end
```
@@ -1000,20 +1000,20 @@ Passing `true` to the `:polymorphic` option indicates that this is a polymorphic
If you set the `:touch` option to `true`, then the `updated_at` or `updated_on` timestamp on the associated object will be set to the current time whenever this object is saved or destroyed:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, touch: true
+class Book < ApplicationRecord
+ belongs_to :author, touch: true
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-In this case, saving or destroying an order will update the timestamp on the associated customer. You can also specify a particular timestamp attribute to update:
+In this case, saving or destroying an book will update the timestamp on the associated author. You can also specify a particular timestamp attribute to update:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, touch: :orders_updated_at
+class Book < ApplicationRecord
+ belongs_to :author, touch: :books_updated_at
end
```
@@ -1031,8 +1031,8 @@ object won't be validated. By default, this option is set to `false`.
There may be times when you wish to customize the query used by `belongs_to`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, -> { where active: true },
+class Book < ApplicationRecord
+ belongs_to :author, -> { where active: true },
dependent: :destroy
end
```
@@ -1049,8 +1049,8 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, -> { where active: true }
+class book < ApplicationRecord
+ belongs_to :author, -> { where active: true }
end
```
@@ -1060,37 +1060,37 @@ You can use the `includes` method to specify second-order associations that shou
```ruby
class LineItem < ApplicationRecord
- belongs_to :order
+ belongs_to :book
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-If you frequently retrieve customers directly from line items (`@line_item.order.customer`), then you can make your code somewhat more efficient by including customers in the association from line items to orders:
+If you frequently retrieve authors directly from line items (`@line_item.book.author`), then you can make your code somewhat more efficient by including authors in the association from line items to books:
```ruby
class LineItem < ApplicationRecord
- belongs_to :order, -> { includes :customer }
+ belongs_to :book, -> { includes :author }
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-NOTE: There's no need to use `includes` for immediate associations - that is, if you have `Order belongs_to :customer`, then the customer is eager-loaded automatically when it's needed.
+NOTE: There's no need to use `includes` for immediate associations - that is, if you have `Book belongs_to :author`, then the author is eager-loaded automatically when it's needed.
##### `readonly`
@@ -1107,8 +1107,8 @@ TIP: If you use the `select` method on a `belongs_to` association, you should al
You can see if any associated objects exist by using the `association.nil?` method:
```ruby
-if @order.customer.nil?
- @msg = "No customer found for this order"
+if @book.author.nil?
+ @msg = "No author found for this book"
end
```
@@ -1415,30 +1415,30 @@ When you declare a `has_many` association, the declaring class automatically gai
In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-Each instance of the `Customer` model will have these methods:
+Each instance of the `Author` model will have these methods:
```ruby
-orders
-orders<<(object, ...)
-orders.delete(object, ...)
-orders.destroy(object, ...)
-orders=(objects)
-order_ids
-order_ids=(ids)
-orders.clear
-orders.empty?
-orders.size
-orders.find(...)
-orders.where(...)
-orders.exists?(...)
-orders.build(attributes = {}, ...)
-orders.create(attributes = {})
-orders.create!(attributes = {})
+books
+books<<(object, ...)
+books.delete(object, ...)
+books.destroy(object, ...)
+books=(objects)
+book_ids
+book_ids=(ids)
+books.clear
+books.empty?
+books.size
+books.find(...)
+books.where(...)
+books.exists?(...)
+books.build(attributes = {}, ...)
+books.create(attributes = {})
+books.create!(attributes = {})
```
##### `collection`
@@ -1446,7 +1446,7 @@ orders.create!(attributes = {})
The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.
```ruby
-@orders = @customer.orders
+@books = @author.books
```
##### `collection<<(object, ...)`
@@ -1454,7 +1454,7 @@ The `collection` method returns an array of all of the associated objects. If th
The `collection<<` method adds one or more objects to the collection by setting their foreign keys to the primary key of the calling model.
```ruby
-@customer.orders << @order1
+@author.books << @book1
```
##### `collection.delete(object, ...)`
@@ -1462,7 +1462,7 @@ The `collection<<` method adds one or more objects to the collection by setting
The `collection.delete` method removes one or more objects from the collection by setting their foreign keys to `NULL`.
```ruby
-@customer.orders.delete(@order1)
+@author.books.delete(@book1)
```
WARNING: Additionally, objects will be destroyed if they're associated with `dependent: :destroy`, and deleted if they're associated with `dependent: :delete_all`.
@@ -1472,7 +1472,7 @@ WARNING: Additionally, objects will be destroyed if they're associated with `dep
The `collection.destroy` method removes one or more objects from the collection by running `destroy` on each object.
```ruby
-@customer.orders.destroy(@order1)
+@author.books.destroy(@book1)
```
WARNING: Objects will _always_ be removed from the database, ignoring the `:dependent` option.
@@ -1486,7 +1486,7 @@ The `collection=` method makes the collection contain only the supplied objects,
The `collection_singular_ids` method returns an array of the ids of the objects in the collection.
```ruby
-@order_ids = @customer.order_ids
+@book_ids = @author.book_ids
```
##### `collection_singular_ids=(ids)`
@@ -1498,7 +1498,7 @@ The `collection_singular_ids=` method makes the collection contain only the obje
The `collection.clear` method removes all objects from the collection according to the strategy specified by the `dependent` option. If no option is given, it follows the default strategy. The default strategy for `has_many :through` associations is `delete_all`, and for `has_many` associations is to set the foreign keys to `NULL`.
```ruby
-@customer.orders.clear
+@author.books.clear
```
WARNING: Objects will be deleted if they're associated with `dependent: :destroy`,
@@ -1509,8 +1509,8 @@ just like `dependent: :delete_all`.
The `collection.empty?` method returns `true` if the collection does not contain any associated objects.
```erb
-<% if @customer.orders.empty? %>
- No Orders Found
+<% if @author.books.empty? %>
+ No Books Found
<% end %>
```
@@ -1519,7 +1519,7 @@ The `collection.empty?` method returns `true` if the collection does not contain
The `collection.size` method returns the number of objects in the collection.
```ruby
-@order_count = @customer.orders.size
+@book_count = @author.books.size
```
##### `collection.find(...)`
@@ -1527,7 +1527,7 @@ The `collection.size` method returns the number of objects in the collection.
The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`.
```ruby
-@open_orders = @customer.orders.find(1)
+@available_books = @author.books.find(1)
```
##### `collection.where(...)`
@@ -1535,8 +1535,8 @@ The `collection.find` method finds objects within the collection. It uses the sa
The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed.
```ruby
-@open_orders = @customer.orders.where(open: true) # No query yet
-@open_order = @open_orders.first # Now the database will be queried
+@available_books = @author.books.where(available: true) # No query yet
+@available_book = @available_books.first # Now the database will be queried
```
##### `collection.exists?(...)`
@@ -1550,12 +1550,12 @@ conditions exists in the collection. It uses the same syntax and options as
The `collection.build` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved.
```ruby
-@order = @customer.orders.build(order_date: Time.now,
- order_number: "A12345")
+@book = @author.books.build(published_at: Time.now,
+ book_number: "A12345")
-@orders = @customer.orders.build([
- { order_date: Time.now, order_number: "A12346" },
- { order_date: Time.now, order_number: "A12347" }
+@books = @author.books.build([
+ { published_at: Time.now, book_number: "A12346" },
+ { published_at: Time.now, book_number: "A12347" }
])
```
@@ -1564,12 +1564,12 @@ The `collection.build` method returns a single or array of new objects of the as
The `collection.create` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, the link through its foreign key will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved.
```ruby
-@order = @customer.orders.create(order_date: Time.now,
- order_number: "A12345")
+@book = @author.books.create(published_at: Time.now,
+ book_number: "A12345")
-@orders = @customer.orders.create([
- { order_date: Time.now, order_number: "A12346" },
- { order_date: Time.now, order_number: "A12347" }
+@books = @author.books.create([
+ { published_at: Time.now, book_number: "A12346" },
+ { published_at: Time.now, book_number: "A12347" }
])
```
@@ -1582,8 +1582,8 @@ Does the same as `collection.create` above, but raises `ActiveRecord::RecordInva
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, dependent: :delete_all, validate: false
+class Author < ApplicationRecord
+ has_many :books, dependent: :delete_all, validate: false
end
```
@@ -1612,11 +1612,11 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
##### `:class_name`
-If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a customer has many orders, but the actual name of the model containing orders is `Transaction`, you'd set things up this way:
+If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if an author has many books, but the actual name of the model containing books is `Transaction`, you'd set things up this way:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, class_name: "Transaction"
+class Author < ApplicationRecord
+ has_many :books, class_name: "Transaction"
end
```
@@ -1639,8 +1639,8 @@ Controls what happens to the associated objects when their owner is destroyed:
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, foreign_key: "cust_id"
+class Author < ApplicationRecord
+ has_many :books, foreign_key: "cust_id"
end
```
@@ -1651,12 +1651,12 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to
The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options.
```ruby
-class Customer < ApplicationRecord
- has_many :orders, inverse_of: :customer
+class Author < ApplicationRecord
+ has_many :books, inverse_of: :author
end
-class Order < ApplicationRecord
- belongs_to :customer, inverse_of: :orders
+class Book < ApplicationRecord
+ belongs_to :author, inverse_of: :books
end
```
@@ -1700,8 +1700,8 @@ If you set the `:validate` option to `false`, then associated objects will not b
There may be times when you wish to customize the query used by `has_many`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, -> { where processed: true }
+class Author < ApplicationRecord
+ has_many :books, -> { where processed: true }
end
```
@@ -1723,22 +1723,22 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Customer < ApplicationRecord
- has_many :confirmed_orders, -> { where "confirmed = 1" },
- class_name: "Order"
+class Author < ApplicationRecord
+ has_many :confirmed_books, -> { where "confirmed = 1" },
+ class_name: "Book"
end
```
You can also set conditions via a hash:
```ruby
-class Customer < ApplicationRecord
- has_many :confirmed_orders, -> { where confirmed: true },
- class_name: "Order"
+class Author < ApplicationRecord
+ has_many :confirmed_books, -> { where confirmed: true },
+ class_name: "Book"
end
```
-If you use a hash-style `where` option, then record creation via this association will be automatically scoped using the hash. In this case, using `@customer.confirmed_orders.create` or `@customer.confirmed_orders.build` will create orders where the confirmed column has the value `true`.
+If you use a hash-style `where` option, then record creation via this association will be automatically scoped using the hash. In this case, using `@author.confirmed_books.create` or `@author.confirmed_books.build` will create books where the confirmed column has the value `true`.
##### `extending`
@@ -1749,9 +1749,9 @@ The `extending` method specifies a named module to extend the association proxy.
The `group` method supplies an attribute name to group the result set by, using a `GROUP BY` clause in the finder SQL.
```ruby
-class Customer < ApplicationRecord
- has_many :line_items, -> { group 'orders.id' },
- through: :orders
+class Author < ApplicationRecord
+ has_many :line_items, -> { group 'books.id' },
+ through: :books
end
```
@@ -1760,34 +1760,34 @@ end
You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
class LineItem < ApplicationRecord
- belongs_to :order
+ belongs_to :book
end
```
-If you frequently retrieve line items directly from customers (`@customer.orders.line_items`), then you can make your code somewhat more efficient by including line items in the association from customers to orders:
+If you frequently retrieve line items directly from authors (`@author.books.line_items`), then you can make your code somewhat more efficient by including line items in the association from authors to books:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, -> { includes :line_items }
+class Author < ApplicationRecord
+ has_many :books, -> { includes :line_items }
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
class LineItem < ApplicationRecord
- belongs_to :order
+ belongs_to :book
end
```
@@ -1796,10 +1796,10 @@ end
The `limit` method lets you restrict the total number of objects that will be fetched through an association.
```ruby
-class Customer < ApplicationRecord
- has_many :recent_orders,
- -> { order('order_date desc').limit(100) },
- class_name: "Order",
+class Author < ApplicationRecord
+ has_many :recent_books,
+ -> { order('published_at desc').limit(100) },
+ class_name: "Book",
end
```
@@ -1812,8 +1812,8 @@ The `offset` method lets you specify the starting offset for fetching objects vi
The `order` method dictates the order in which associated objects will be received (in the syntax used by an SQL `ORDER BY` clause).
```ruby
-class Customer < ApplicationRecord
- has_many :orders, -> { order "date_confirmed DESC" }
+class Author < ApplicationRecord
+ has_many :books, -> { order "date_confirmed DESC" }
end
```
@@ -1872,11 +1872,21 @@ If you want to make sure that, upon insertion, all of the records in the
persisted association are distinct (so that you can be sure that when you
inspect the association that you will never find duplicate records), you should
add a unique index on the table itself. For example, if you have a table named
-`person_articles` and you want to make sure all the articles are unique, you could
-add the following in a migration:
+`readings` and you want to make sure the articles can only be added to a person once,
+you could add the following in a migration:
```ruby
-add_index :person_articles, :article, unique: true
+add_index :readings, [:person_id, :article_id], unique: true
+```
+
+Once you have this unique index, attempting to add the article to a person twice
+will raise an `ActiveRecord::RecordNotUnique` error:
+
+```ruby
+person = Person.create(name: 'Honda')
+article = Article.create(name: 'a1')
+person.articles << article
+person.articles << article # => ActiveRecord::RecordNotUnique
```
Note that checking for uniqueness using something like `include?` is subject
@@ -2271,10 +2281,10 @@ Association callbacks are similar to normal callbacks, but they are triggered by
You define association callbacks by adding options to the association declaration. For example:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, before_add: :check_credit_limit
+class Author < ApplicationRecord
+ has_many :books, before_add: :check_credit_limit
- def check_credit_limit(order)
+ def check_credit_limit(book)
...
end
end
@@ -2285,15 +2295,15 @@ Rails passes the object being added or removed to the callback.
You can stack callbacks on a single event by passing them as an array:
```ruby
-class Customer < ApplicationRecord
- has_many :orders,
+class Author < ApplicationRecord
+ has_many :books,
before_add: [:check_credit_limit, :calculate_shipping_charges]
- def check_credit_limit(order)
+ def check_credit_limit(book)
...
end
- def calculate_shipping_charges(order)
+ def calculate_shipping_charges(book)
...
end
end
@@ -2306,10 +2316,10 @@ If a `before_add` callback throws an exception, the object does not get added to
You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example:
```ruby
-class Customer < ApplicationRecord
- has_many :orders do
- def find_by_order_prefix(order_number)
- find_by(region_id: order_number[0..2])
+class Author < ApplicationRecord
+ has_many :books do
+ def find_by_book_prefix(book_number)
+ find_by(category_id: book_number[0..2])
end
end
end
@@ -2324,8 +2334,8 @@ module FindRecentExtension
end
end
-class Customer < ApplicationRecord
- has_many :orders, -> { extending FindRecentExtension }
+class Author < ApplicationRecord
+ has_many :books, -> { extending FindRecentExtension }
end
class Supplier < ApplicationRecord
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index eb9bf3fa18..f33e729de0 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -21,7 +21,7 @@ There are a few commands that are absolutely critical to your everyday usage of
* `rails console`
* `rails server`
-* `rake`
+* `bin/rails`
* `rails generate`
* `rails dbconsole`
* `rails new app_name`
@@ -55,20 +55,21 @@ Rails will set you up with what seems like a huge amount of stuff for such a tin
### `rails server`
-The `rails server` command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to access your application through a web browser.
+The `rails server` command launches a web server named Puma which comes bundled with Rails. You'll use this any time you want to access your application through a web browser.
With no further work, `rails server` will run our new shiny Rails app:
```bash
$ cd commandsapp
$ bin/rails server
-=> Booting WEBrick
-=> Rails 5.0.0 application starting in development on http://localhost:3000
+=> Booting Puma
+=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
-[2013-08-07 02:00:01] INFO WEBrick 1.3.1
-[2013-08-07 02:00:01] INFO ruby 2.2.2 (2015-06-27) [x86_64-darwin11.2.0]
-[2013-08-07 02:00:01] INFO WEBrick::HTTPServer#start: pid=69680 port=3000
+Puma 2.15.3 starting...
+* Min threads: 0, max threads: 16
+* Environment: development
+* Listening on tcp://localhost:3000
```
With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open [http://localhost:3000](http://localhost:3000), you will see a basic Rails app running.
@@ -181,7 +182,7 @@ Fire up your server using `rails server`.
```bash
$ bin/rails server
-=> Booting WEBrick...
+=> Booting Puma...
```
The URL will be [http://localhost:3000/greetings/hello](http://localhost:3000/greetings/hello).
@@ -250,10 +251,10 @@ $ bin/rails generate scaffold HighScore game:string score:integer
The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything.
-The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
+The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `bin/rails db:migrate` command. We'll talk more about bin/rails in-depth in a little while.
```bash
-$ bin/rake db:migrate
+$ bin/rails db:migrate
== CreateHighScores: migrating ===============================================
-- create_table(:high_scores)
-> 0.0017s
@@ -375,37 +376,56 @@ $ bin/rails destroy model Oops
remove test/fixtures/oops.yml
```
-Rake
-----
+bin/rails
+---------
-Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'make', and uses a 'Rakefile' and `.rake` files to build up a list of tasks. In Rails, Rake is used for common administration tasks, especially sophisticated ones that build off of each other.
+Since Rails 5.0+ has rake commands built into the rails executable, `bin/rails` is the new default for running commands.
-You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing `rake --tasks`. Each task has a description, and should help you find the thing you need.
-
-To get the full backtrace for running rake task you can pass the option `--trace` to command line, for example `rake db:create --trace`.
+You can get a list of bin/rails tasks available to you, which will often depend on your current directory, by typing `bin/rails --help`. Each task has a description, and should help you find the thing you need.
```bash
-$ bin/rake --tasks
-rake about # List versions of all Rails frameworks and the environment
-rake assets:clean # Remove old compiled assets
-rake assets:clobber # Remove compiled assets
-rake assets:precompile # Compile all the assets named in config.assets.precompile
-rake db:create # Create the database from config/database.yml for the current Rails.env
+$ bin/rails --help
+Usage: rails COMMAND [ARGS]
+
+The most common rails commands are:
+generate Generate new code (short-cut alias: "g")
+console Start the Rails console (short-cut alias: "c")
+server Start the Rails server (short-cut alias: "s")
+...
+
+All commands can be run with -h (or --help) for more information.
+
+In addition to those commands, there are:
+about List versions of all Rails ...
+assets:clean[keep] Remove old compiled assets
+assets:clobber Remove compiled assets
+assets:environment Load asset compile environment
+assets:precompile Compile all the assets ...
...
-rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
-rake middleware # Prints out your Rack middleware stack
+db:fixtures:load Loads fixtures into the ...
+db:migrate Migrate the database ...
+db:migrate:status Display status of migrations
+db:rollback Rolls the schema back to ...
+db:schema:cache:clear Clears a db/schema_cache.dump file
+db:schema:cache:dump Creates a db/schema_cache.dump file
+db:schema:dump Creates a db/schema.rb file ...
+db:schema:load Loads a schema.rb file ...
+db:seed Loads the seed data ...
+db:structure:dump Dumps the database structure ...
+db:structure:load Recreates the databases ...
+db:version Retrieves the current schema ...
...
-rake tmp:clear # Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)
-rake tmp:create # Creates tmp directories for cache, sockets, and pids
+restart Restart app by touching ...
+tmp:create Creates tmp directories ...
```
-INFO: You can also use `rake -T` to get the list of tasks.
+INFO: You can also use `bin/rails -T` to get the list of tasks.
### `about`
-`rake about` gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation.
+`bin/rails about` gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation.
```bash
-$ bin/rake about
+$ bin/rails about
About your application's environment
Rails version 5.0.0
Ruby version 2.2.2 (x86_64-linux)
@@ -421,22 +441,22 @@ Database schema version 20110805173523
### `assets`
-You can precompile the assets in `app/assets` using `rake assets:precompile`, and remove older compiled assets using `rake assets:clean`. The `assets:clean` task allows for rolling deploys that may still be linking to an old asset while the new assets are being built.
+You can precompile the assets in `app/assets` using `bin/rails assets:precompile`, and remove older compiled assets using `bin/rails assets:clean`. The `assets:clean` task allows for rolling deploys that may still be linking to an old asset while the new assets are being built.
-If you want to clear `public/assets` completely, you can use `rake assets:clobber`.
+If you want to clear `public/assets` completely, you can use `bin/rails assets:clobber`.
### `db`
-The most common tasks of the `db:` Rake namespace are `migrate` and `create`, and it will pay off to try out all of the migration rake tasks (`up`, `down`, `redo`, `reset`). `rake db:version` is useful when troubleshooting, telling you the current version of the database.
+The most common tasks of the `db:` bin/rails namespace are `migrate` and `create`, and it will pay off to try out all of the migration bin/rails tasks (`up`, `down`, `redo`, `reset`). `bin/rails db:version` is useful when troubleshooting, telling you the current version of the database.
More information about migrations can be found in the [Migrations](active_record_migrations.html) guide.
### `notes`
-`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations.
+`bin/rails notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations.
```bash
-$ bin/rake notes
+$ bin/rails notes
(in /home/foobar/commandsapp)
app/controllers/admin/users_controller.rb:
* [ 20] [TODO] any other way to do this?
@@ -453,10 +473,10 @@ You can add support for new file extensions using `config.annotations.register_e
config.annotations.register_extensions("scss", "sass", "less") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ }
```
-If you are looking for a specific annotation, say FIXME, you can use `rake notes:fixme`. Note that you have to lower case the annotation's name.
+If you are looking for a specific annotation, say FIXME, you can use `bin/rails notes:fixme`. Note that you have to lower case the annotation's name.
```bash
-$ bin/rake notes:fixme
+$ bin/rails notes:fixme
(in /home/foobar/commandsapp)
app/controllers/admin/users_controller.rb:
* [132] high priority for next deploy
@@ -465,10 +485,10 @@ app/models/school.rb:
* [ 17]
```
-You can also use custom annotations in your code and list them using `rake notes:custom` by specifying the annotation using an environment variable `ANNOTATION`.
+You can also use custom annotations in your code and list them using `bin/rails notes:custom` by specifying the annotation using an environment variable `ANNOTATION`.
```bash
-$ bin/rake notes:custom ANNOTATION=BUG
+$ bin/rails notes:custom ANNOTATION=BUG
(in /home/foobar/commandsapp)
app/models/article.rb:
* [ 23] Have to fix this one before pushing!
@@ -476,11 +496,11 @@ app/models/article.rb:
NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines.
-By default, `rake notes` will look in the `app`, `config`, `db`, `lib` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`.
+By default, `rails notes` will look in the `app`, `config`, `db`, `lib` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`.
```bash
$ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor'
-$ bin/rake notes
+$ bin/rails notes
(in /home/foobar/commandsapp)
app/models/user.rb:
* [ 35] [FIXME] User should have a subscription at this point
@@ -490,7 +510,7 @@ spec/models/user_spec.rb:
### `routes`
-`rake routes` will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with.
+`rails routes` will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with.
### `test`
@@ -504,16 +524,16 @@ The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding pla
The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory:
-* `rake tmp:cache:clear` clears `tmp/cache`.
-* `rake tmp:sockets:clear` clears `tmp/sockets`.
-* `rake tmp:clear` clears all cache and sockets files.
-* `rake tmp:create` creates tmp directories for cache, sockets and pids.
+* `rails tmp:cache:clear` clears `tmp/cache`.
+* `rails tmp:sockets:clear` clears `tmp/sockets`.
+* `rails tmp:clear` clears all cache and sockets files.
+* `rails tmp:create` creates tmp directories for cache, sockets and pids.
### Miscellaneous
-* `rake stats` is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio.
-* `rake secret` will give you a pseudo-random key to use for your session secret.
-* `rake time:zones:all` lists all the timezones Rails knows about.
+* `rails stats` is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio.
+* `rails secret` will give you a pseudo-random key to use for your session secret.
+* `rails time:zones:all` lists all the timezones Rails knows about.
### Custom Rake Tasks
@@ -551,9 +571,9 @@ end
Invocation of the tasks will look like:
```bash
-$ bin/rake task_name
-$ bin/rake "task_name[value 1]" # entire argument string should be quoted
-$ bin/rake db:nothing
+$ bin/rails task_name
+$ bin/rails "task_name[value 1]" # entire argument string should be quoted
+$ bin/rails db:nothing
```
NOTE: If your need to interact with your application models, perform database queries and so on, your task should depend on the `environment` task, which will load your application code.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 47acff7c3f..8a21d4062a 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -104,7 +104,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
you don't want shown in the logs, such as passwords or credit card
numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`.
-* `config.force_ssl` forces all requests to be under HTTPS protocol by using `ActionDispatch::SSL` middleware.
+* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
@@ -198,7 +198,7 @@ The full set of methods that can be used in this block are as follows:
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
-* `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`.
+* `ActionDispatch::SSL` forces every request to be served using HTTPS. Enabled if `config.force_ssl` is set to `true`. Options passed to this can be configured by setting `config.ssl_options`.
* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`.
* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`.
* `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
@@ -335,8 +335,6 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`.
-* `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8".
-
* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`.
* `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging.
@@ -347,6 +345,8 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.forgery_protection_origin_check` configures whether the HTTP `Origin` header should be checked against the site's origin as an additional CSRF defense.
+* `config.action_controller.per_form_csrf_tokens` configures whether CSRF tokens are only valid for the method/action they were generated for.
+
* `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`.
* `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`.
@@ -369,6 +369,8 @@ The schema dumper adds one additional configuration option:
}
```
+* `config.action_dispatch.default_charset` specifies the default character set for all renders. Defaults to `nil`.
+
* `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`.
* `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults
@@ -483,7 +485,7 @@ There are a number of settings available on `config.action_mailer`:
* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true.
-* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration) for more info.
+* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](action_mailer_basics.html#action-mailer-configuration) for more info.
* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing.
@@ -1117,21 +1119,48 @@ NOTE. If you are running in a multi-threaded environment, there could be a chanc
Custom configuration
--------------------
-You can configure your own code through the Rails configuration object with custom configuration under the `config.x` property. It works like this:
+You can configure your own code through the Rails configuration object with custom configuration. It works like this:
```ruby
- config.x.payment_processing.schedule = :daily
- config.x.payment_processing.retries = 3
- config.x.super_debugger = true
+ config.payment_processing.schedule = :daily
+ config.payment_processing.retries = 3
+ config.super_debugger = true
```
These configuration points are then available through the configuration object:
```ruby
- Rails.configuration.x.payment_processing.schedule # => :daily
- Rails.configuration.x.payment_processing.retries # => 3
- Rails.configuration.x.super_debugger # => true
- Rails.configuration.x.super_debugger.not_set # => nil
+ Rails.configuration.payment_processing.schedule # => :daily
+ Rails.configuration.payment_processing.retries # => 3
+ Rails.configuration.super_debugger # => true
+ Rails.configuration.super_debugger.not_set # => nil
+ ```
+
+You can also use `Rails::Application.config_for` to load whole configuration files:
+
+ ```ruby
+ # config/payment.yml:
+ production:
+ environment: production
+ merchant_id: production_merchant_id
+ public_key: production_public_key
+ private_key: production_private_key
+ development:
+ environment: sandbox
+ merchant_id: development_merchant_id
+ public_key: development_public_key
+ private_key: development_private_key
+
+ # config/application.rb
+ module MyApp
+ class Application < Rails::Application
+ config.payment = config_for(:payment)
+ end
+ end
+ ```
+
+ ```ruby
+ Rails.configuration.payment['merchant_id'] # => production_merchant_id or development_merchant_id
```
Search Engines Indexing
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index ed88ecf6ac..cbc304c87f 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -357,7 +357,6 @@ $ bundle exec rake test:sqlite3
You can now run the tests as you did for `sqlite3`. The tasks are respectively:
```bash
-test:mysql
test:mysql2
test:postgresql
```
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 0046ff7b4e..35ad6eb705 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -311,14 +311,14 @@ processing the entire request.
For example:
```bash
-=> Booting WEBrick
+=> Booting Puma
=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
-=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
-[2014-04-11 13:11:47] INFO WEBrick 1.3.1
-[2014-04-11 13:11:47] INFO ruby 2.2.2 (2015-04-13) [i686-linux]
-[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000
+Puma 2.15.3 starting...
+* Min threads: 0, max threads: 16
+* Environment: development
+* Listening on tcp://localhost:3000
Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 4322f03d05..7beb8f72a9 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -165,7 +165,7 @@ $ bundle exec ruby -Itest path/to/test.rb -n test_name
### Active Record Setup
-The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (`mysql` and `mysql2`), and once for PostgreSQL. We are going to see now how to set up the environment for them.
+Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them.
WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 8382bde4d3..697938434c 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -423,7 +423,7 @@ Finally, the assets for this resource are generated in two files:
`app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little
later.
-You can see what the engine has so far by running `rake db:migrate` at the root
+You can see what the engine has so far by running `bin/rails db:migrate` at the root
of our engine to run the migration generated by the scaffold generator, and then
running `rails server` in `test/dummy`. When you open
`http://localhost:3000/blorgh/articles` you will see the default scaffold that has
@@ -485,7 +485,7 @@ called `Blorgh::Comment`. Now run the migration to create our blorgh_comments
table:
```bash
-$ rake db:migrate
+$ bin/rails db:migrate
```
To show the comments on an article, edit `app/views/blorgh/articles/show.html.erb` and
@@ -694,14 +694,14 @@ engine's models can query them correctly. To copy these migrations into the
application run the following command from the `test/dummy` directory of your Rails engine:
```bash
-$ rake blorgh:install:migrations
+$ bin/rails blorgh:install:migrations
```
If you have multiple engines that need migrations copied over, use
`railties:install:migrations` instead:
```bash
-$ rake railties:install:migrations
+$ bin/rails railties:install:migrations
```
This command, when run for the first time, will copy over all the migrations
@@ -719,7 +719,7 @@ timestamp (`[timestamp_2]`) will be the current time plus a second. The reason
for this is so that the migrations for the engine are run after any existing
migrations in the application.
-To run these migrations within the context of the application, simply run `rake
+To run these migrations within the context of the application, simply run `bin/rails
db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the
articles will be empty. This is because the table created inside the application is
different from the one created within the engine. Go ahead, play around with the
@@ -730,14 +730,14 @@ If you would like to run migrations only from one engine, you can do it by
specifying `SCOPE`:
```bash
-rake db:migrate SCOPE=blorgh
+bin/rails db:migrate SCOPE=blorgh
```
This may be useful if you want to revert engine's migrations before removing it.
To revert all migrations from blorgh engine you can run code such as:
```bash
-rake db:migrate SCOPE=blorgh VERSION=0
+bin/rails db:migrate SCOPE=blorgh VERSION=0
```
### Using a Class Provided by the Application
@@ -764,7 +764,7 @@ application:
rails g model user name:string
```
-The `rake db:migrate` command needs to be run here to ensure that our
+The `bin/rails db:migrate` command needs to be run here to ensure that our
application has the `users` table for future use.
Also, to keep it simple, the articles form will have a new text field called
@@ -836,7 +836,7 @@ This migration will need to be run on the application. To do that, it must first
be copied using this command:
```bash
-$ rake blorgh:install:migrations
+$ bin/rails blorgh:install:migrations
```
Notice that only _one_ migration was copied over here. This is because the first
@@ -851,7 +851,7 @@ Copied migration [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb from blo
Run the migration using:
```bash
-$ rake db:migrate
+$ bin/rails db:migrate
```
Now with all the pieces in place, an action will take place that will associate
@@ -1354,7 +1354,7 @@ need to require `admin.css` or `admin.js`. Only the gem's admin layout needs
these assets. It doesn't make sense for the host app to include
`"blorgh/admin.css"` in its stylesheets. In this situation, you should
explicitly define these assets for precompilation. This tells sprockets to add
-your engine assets when `rake assets:precompile` is triggered.
+your engine assets when `bin/rails assets:precompile` is triggered.
You can define assets for precompilation in `engine.rb`:
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 97c5f8b15d..9677ab1583 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -37,6 +37,7 @@ curve diving straight into Rails. There are several curated lists of online reso
for learning Ruby:
* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/)
+* [List of Free Programming Books](https://github.com/vhf/free-programming-books/blob/master/free-programming-books.md#ruby)
Be aware that some resources, while still excellent, cover versions of Ruby as old as
1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day
@@ -207,7 +208,7 @@ commented line for new apps and you can uncomment if you need it.
default to the `Gemfile` in apps generated under JRuby. You can investigate
all the supported runtimes at [ExecJS](https://github.com/rails/execjs#readme).
-This will fire up WEBrick, a web server distributed with Ruby by default. To see
+This will fire up Puma, a web server distributed with Rails by default. To see
your application in action, open a browser window and navigate to
<http://localhost:3000>. You should see the Rails default information page:
@@ -299,7 +300,7 @@ Rails.application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
- # See how all your routes lay out with "rake routes".
+ # See how all your routes lay out with "bin/rails routes".
#
# You can have the root of your site routed with "root"
# root 'welcome#index'
@@ -358,13 +359,13 @@ Rails.application.routes.draw do
end
```
-If you run `bin/rake routes`, you'll see that it has defined routes for all the
+If you run `bin/rails routes`, you'll see that it has defined routes for all the
standard RESTful actions. The meaning of the prefix column (and other columns)
will be seen later, but for now notice that Rails has inferred the
singular form `article` and makes meaningful use of the distinction.
```bash
-$ bin/rake routes
+$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
@@ -558,10 +559,10 @@ this:
In this example, the `articles_path` helper is passed to the `:url` option.
To see what Rails will do with this, we look back at the output of
-`bin/rake routes`:
+`bin/rails routes`:
```bash
-$ bin/rake routes
+$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
@@ -701,10 +702,10 @@ two timestamp fields to allow Rails to track article creation and update times.
TIP: For more information about migrations, refer to [Rails Database Migrations]
(migrations.html).
-At this point, you can use a rake command to run the migration:
+At this point, you can use a bin/rails command to run the migration:
```bash
-$ bin/rake db:migrate
+$ bin/rails db:migrate
```
Rails will execute this migration command and tell you it created the Articles
@@ -721,7 +722,7 @@ NOTE. Because you're working in the development environment by default, this
command will apply to the database defined in the `development` section of your
`config/database.yml` file. If you would like to execute migrations in another
environment, for instance in production, you must explicitly pass it when
-invoking the command: `bin/rake db:migrate RAILS_ENV=production`.
+invoking the command: `bin/rails db:migrate RAILS_ENV=production`.
### Saving data in the controller
@@ -808,7 +809,7 @@ If you submit the form again now, Rails will complain about not finding the
`show` action. That's not very useful though, so let's add the `show` action
before proceeding.
-As we have seen in the output of `bin/rake routes`, the route for `show` action is
+As we have seen in the output of `bin/rails routes`, the route for `show` action is
as follows:
```
@@ -870,7 +871,7 @@ Visit <http://localhost:3000/articles/new> and give it a try!
### Listing all articles
We still need a way to list all our articles, so let's do that.
-The route for this as per output of `bin/rake routes` is:
+The route for this as per output of `bin/rails routes` is:
```
articles GET /articles(.:format) articles#index
@@ -1365,7 +1366,7 @@ Then do the same for the `app/views/articles/edit.html.erb` view:
We're now ready to cover the "D" part of CRUD, deleting articles from the
database. Following the REST convention, the route for
-deleting articles as per output of `bin/rake routes` is:
+deleting articles as per output of `bin/rails routes` is:
```ruby
DELETE /articles/:id(.:format) articles#destroy
@@ -1539,6 +1540,11 @@ This is very similar to the `Article` model that you saw earlier. The difference
is the line `belongs_to :article`, which sets up an Active Record _association_.
You'll learn a little about associations in the next section of this guide.
+The (`:references`) keyword used in the bash command is a special data type for models.
+It creates a new column on your database table with the provided model name appended with an `_id`
+that can hold integer values. You can get a better understanding after analyzing the
+`db/schema.rb` file below.
+
In addition to the model, Rails has also made a migration to create the
corresponding database table:
@@ -1561,7 +1567,7 @@ for it, and a foreign key constraint that points to the `id` column of the `arti
table. Go ahead and run the migration:
```bash
-$ bin/rake db:migrate
+$ bin/rails db:migrate
```
Rails is smart enough to only execute the migrations that have not already been
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 7bf7eebb62..6232ef4c57 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -530,16 +530,17 @@ This file is responsible for requiring all the individual frameworks of Rails:
require "rails"
%w(
- active_record
- action_controller
- action_view
- action_mailer
- active_job
- rails/test_unit
- sprockets
-).each do |framework|
+ active_record/railtie
+ action_controller/railtie
+ action_view/railtie
+ action_mailer/railtie
+ active_job/railtie
+ action_cable/engine
+ rails/test_unit/railtie
+ sprockets/railtie
+).each do |railtie|
begin
- require "#{framework}/railtie"
+ require "#{railtie}"
rescue LoadError
end
end
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 1005057ca9..1f81ea4694 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -24,20 +24,7 @@
<% end %>
<div id="topNav">
<div class="wrapper">
- <strong class="more-info-label">More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
- <span class="red-button more-info-button">
- More Ruby on Rails
- </span>
- <ul class="more-info-links s-hidden">
- <li class="more-info"><a href="http://rubyonrails.org/">Overview</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/download">Download</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/deploy">Deploy</a></li>
- <li class="more-info"><a href="https://github.com/rails/rails">Code</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/screencasts">Screencasts</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/documentation">Documentation</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/community">Community</a></li>
- <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
- </ul>
+ <strong class="more-info-label">←<a href="http://rubyonrails.org/">Back to rubyonrails.org:</a> </strong>
</div>
</div>
<div id="header">
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 9b7f916b9e..d55e1007ee 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -238,7 +238,7 @@ TIP: This is useful when you're rendering a small snippet of HTML code.
However, you might want to consider moving it to a template file if the markup
is complex.
-NOTE: This option will escape HTML entities if the string is not HTML safe.
+NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method.
#### Rendering JSON
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 50308f505a..f99b6ebd31 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -44,7 +44,7 @@ from.
In special situations, where someone from the Core Team agrees to support more series,
they are included in the list of supported series.
-**Currently included series:** `4.2.Z`, `4.1.Z` (Supported by Rafael França).
+**Currently included series:** `5.0.Z`.
Security Issues
---------------
@@ -59,7 +59,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that
security releases are easy to upgrade to if you're running the latest version
of Rails.
-**Currently included series:** `4.2.Z`, `4.1.Z`.
+**Currently included series:** `5.0.Z`, `4.2.Z`.
Severe Security Issues
----------------------
@@ -68,7 +68,7 @@ For severe security issues we will provide new versions as above, and also the
last major release series will receive patches and new versions. The
classification of the security issue is judged by the core team.
-**Currently included series:** `4.2.Z`, `4.1.Z`, `3.2.Z`.
+**Currently included series:** `5.0.Z`, `4.2.Z`.
Unsupported Release Series
--------------------------
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 68e54f2414..8f055f8fe3 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -247,7 +247,7 @@ and migrating the database. First, run:
```bash
$ cd test/dummy
-$ bin/rake db:migrate
+$ bin/rails db:migrate
```
While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index edd54826cf..5a46baff2d 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -25,8 +25,8 @@ $ rails new blog -m http://example.com/template.rb
You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL.
```bash
-$ bin/rake rails:template LOCATION=~/template.rb
-$ bin/rake rails:template LOCATION=http://example.com/template.rb
+$ bin/rails rails:template LOCATION=~/template.rb
+$ bin/rails rails:template LOCATION=http://example.com/template.rb
```
Template API
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 273fbc08e2..934693252e 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -96,7 +96,7 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`,
Rails has a handy rake task for inspecting the middleware stack in use:
```bash
-$ bin/rake middleware
+$ bin/rails middleware
```
For a freshly generated Rails application, this might produce something like:
@@ -178,7 +178,7 @@ And now if you inspect the middleware stack, you'll find that `Rack::Lock` is
not a part of it.
```bash
-$ bin/rake middleware
+$ bin/rails middleware
(in /Users/lifo/Rails/blog)
use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 9401132500..5a745b10cd 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -706,6 +706,8 @@ end
NOTE: Request constraints work by calling a method on the [Request object](action_controller_overview.html#the-request-object) with the same name as the hash key and then compare the return value with the hash value. Therefore, constraint values should match the corresponding Request object method return type. For example: `constraints: { subdomain: 'api' }` will match an `api` subdomain as expected, however using a symbol `constraints: { subdomain: :api }` will not, because `request.subdomain` returns `'api'` as a String.
+NOTE: There is an exception for the `format` constraint: while it's a method on the Request object, it's also an implicit optional parameter on every path. Segment constraints take precedence and the `format` constraint is only applied as such when enforced through a hash. For example, `get 'foo', constraints: { format: 'json' }` will match `GET /foo` because the format is optional by default. However, you can [use a lambda](#advanced-constraints) like in `get 'foo', constraints: lambda { |req| req.format == :json }` and the route will only match explicit JSON requests.
+
### Advanced Constraints
If you have a more advanced constraint, you can provide an object that responds to `matches?` that Rails should use. Let's say you wanted to route all users on a blacklist to the `BlacklistController`. You could do:
@@ -1116,7 +1118,7 @@ Rails offers facilities for inspecting and testing your routes.
### Listing Existing Routes
-To get a complete list of the available routes in your application, visit `http://localhost:3000/rails/info/routes` in your browser while your server is running in the **development** environment. You can also execute the `rake routes` command in your terminal to produce the same output.
+To get a complete list of the available routes in your application, visit `http://localhost:3000/rails/info/routes` in your browser while your server is running in the **development** environment. You can also execute the `rails routes` command in your terminal to produce the same output.
Both methods will list all of your routes, in the same order that they appear in `config/routes.rb`. For each route, you'll see:
@@ -1125,7 +1127,7 @@ Both methods will list all of your routes, in the same order that they appear in
* The URL pattern to match
* The routing parameters for the route
-For example, here's a small section of the `rake routes` output for a RESTful route:
+For example, here's a small section of the `rails routes` output for a RESTful route:
```
users GET /users(.:format) users#index
@@ -1137,10 +1139,10 @@ edit_user GET /users/:id/edit(.:format) users#edit
You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable:
```bash
-$ CONTROLLER=users bin/rake routes
+$ CONTROLLER=users bin/rails routes
```
-TIP: You'll find that the output from `rake routes` is much more readable if you widen your terminal window until the output lines don't wrap.
+TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap.
### Testing Routes
diff --git a/guides/source/testing.md b/guides/source/testing.md
index a4b62955c5..b5e49a41f4 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -54,9 +54,9 @@ NOTE: Your tests are run under `RAILS_ENV=test`.
### Rails meets Minitest
-If you remember when you used the `rails generate model` command from the
+If you remember, we used the `rails generate model` command in the
[Getting Started with Rails](getting_started.html) guide. We created our first
-model among other things it created test stubs in the `test` directory:
+model, and among other things it created test stubs in the `test` directory:
```bash
$ bin/rails generate model article title:string body:text
@@ -85,13 +85,13 @@ A line by line examination of this file will help get you oriented to Rails test
require 'test_helper'
```
-By requiring this file, `test_helper.rb` the default configuration to run our tests is loaded. We will include this with all the tests we write, so any methods added to this file are available to all your tests.
+By requiring this file, `test_helper.rb` the default configuration to run our tests is loaded. We will include this with all the tests we write, so any methods added to this file are available to all our tests.
```ruby
class ArticleTest < ActiveSupport::TestCase
```
-The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. Later in this guide, you'll see some of the methods it gives you.
+The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. Later in this guide, we'll see some of the methods it gives us.
Any method defined within a class inherited from `Minitest::Test`
(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, methods defined as `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run.
@@ -129,7 +129,7 @@ An assertion is a line of code that evaluates an object (or expression) for expe
* does this line of code throw an exception?
* is the user's password greater than 5 characters?
-Every test must contain at least one assertion, with no restriction as to how many assertions are allowed. Only when all the assertions are successful will the test pass.
+Every test may contain one or more assertions, with no restriction as to how many assertions are allowed. Only when all the assertions are successful will the test pass.
#### Your first failing test
@@ -265,7 +265,7 @@ By now you've caught a glimpse of some of the assertions that are available. Ass
Here's an extract of the assertions you can use with
[`Minitest`](https://github.com/seattlerb/minitest), the default testing library
used by Rails. The `[msg]` parameter is an optional string message you can
-specify to make your test failure messages clearer. It's not required.
+specify to make your test failure messages clearer.
| Assertion | Purpose |
| ---------------------------------------------------------------- | ------- |
@@ -298,7 +298,7 @@ specify to make your test failure messages clearer. It's not required.
| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
| `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`|
| `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`|
-| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?|
+| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true, e.g. assert_send [@user, :full_name, 'Sam Smith']. This one is weird eh?|
| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
The above are a subset of assertions that minitest supports. For an exhaustive &
@@ -396,14 +396,14 @@ A dedicated test database allows you to set up and interact with test data in is
In order to run your tests, your test database will need to have the current
structure. The test helper checks whether your test database has any pending
-migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql`
+migrations. It will try to load your `db/schema.rb` or `db/structure.sql`
into the test database. If migrations are still pending, an error will be
raised. Usually this indicates that your schema is not fully migrated. Running
-the migrations against the development database (`bin/rake db:migrate`) will
+the migrations against the development database (`bin/rails db:migrate`) will
bring the schema up to date.
-NOTE: If existing migrations required modifications, the test database needs to
-be rebuilt. This can be done by executing `bin/rake db:test:prepare`.
+NOTE: If there were modifications to existing migrations, the test database needs to
+be rebuilt. This can be done by executing `bin/rails db:test:prepare`.
### The Low-Down on Fixtures
@@ -448,15 +448,15 @@ about:
name: About
# In fixtures/articles.yml
-one:
+first:
title: Welcome to Rails!
body: Hello world!
category: about
```
-Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`.
+Notice the `category` key of the `first` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`.
-NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+NOTE: For associations to reference one another by name, you can use the fixture name instead of specifying the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
#### ERB'in It Up
@@ -524,9 +524,9 @@ Model tests don't have their own superclass like `ActionMailer::TestCase` instea
Integration Testing
-------------------
-Integration tests are used to test how various parts of your application interact. They are generally used to test important workflows within your application.
+Integration tests are used to test how various parts of your application interact. They are generally used to test important workflows within our application.
-For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you.
+For creating Rails integration tests, we use the 'test/integration' directory for our application. Rails provides a generator to create an integration test skeleton for us.
```bash
$ bin/rails generate integration_test user_flows
@@ -546,17 +546,17 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
end
```
-Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests.
+Here the test is inheriting from `ActionDispatch::IntegrationTest`. This makes some additional helpers available for us to use in our integration tests.
### Helpers Available for Integration Tests
-In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from.
+In addition to the standard testing helpers, inheriting from `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's get briefly introduced to the three categories of helpers we get to choose from.
For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html).
-When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use.
+When performing requests, we will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for our use.
-If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help.
+If we need to modify the session, or state of our integration test, take a look at [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help.
### Implementing an integration test
@@ -569,7 +569,7 @@ $ bin/rails generate integration_test blog_flow
```
It should have created a test file placeholder for us. With the output of the
-previous command you should see:
+previous command we should see:
```bash
invoke test_unit
@@ -589,9 +589,9 @@ class BlogFlowTest < ActionDispatch::IntegrationTest
end
```
-If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request.
+We will take a look at `assert_select` to query the resulting HTML of a request in the "Testing Views" section below. It is used for testing the response of our request by asserting the presence of key HTML elements and their content.
-When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
+When we visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
#### Creating articles integration
@@ -654,7 +654,7 @@ The easiest way to see functional tests in action is to generate a controller
scaffold:
```bash
-$ bin/rails generate scaffold_controller article title:string body:test
+$ bin/rails generate scaffold_controller article title:string body:text
...
create app/controllers/articles_controller.rb
...
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 501c7d2ce5..2c363c55da 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Bring back `TEST=` env for `rake test` task.
+
+ *Yves Senn*
+
+* Specify log file names or all logs to clear `rake log:clear`
+
+ Specify which logs to clear when using the `rake log:clear` task, e.g. `rake log:clear LOGS=test,staging`
+
+ Clear all logs from log/*.log e.g. `rake log:clear LOGS=all`
+
+ By default `rake log:clear` clears standard environment log files i.e. 'development,test,production'
+
+ *Pramod Shinde*
+
* Fix using `add_source` with a block after using `gem` in a custom generator.
*Will Fisher*
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 7c2197229d..1f496cf280 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index ed6a1f82d3..4f1cc0703d 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -68,7 +68,7 @@ module Rails
middleware.use ::ActionDispatch::Cookies unless config.api_only
if !config.api_only && config.session_store
- if config.force_ssl && !config.session_options.key?(:secure)
+ if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure)
config.session_options[:secure] = true
end
middleware.use config.session_store, config.session_options
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 8e9097e1ef..0997414482 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -33,7 +33,7 @@ class CodeStatistics #:nodoc:
Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}]
end
- def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|rake)$/)
+ def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|coffee|rake)$/)
stats = CodeStatisticsCalculator.new
Dir.foreach(directory) do |file_name|
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 86bce9b2fe..babb197ba1 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -58,5 +58,11 @@ elsif File.exist?(code_or_file)
$0 = code_or_file
Kernel.load code_or_file
else
- eval(code_or_file, binding, __FILE__, __LINE__)
+ begin
+ eval(code_or_file, binding, __FILE__, __LINE__)
+ rescue SyntaxError,NameError => err
+ $stderr.puts "Please specify a valid ruby command or the path of a script to run."
+ $stderr.puts "Run '#{$0} -h' for help."
+ exit 1
+ end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index d3ea441f8e..27cbaf360a 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -6,6 +6,8 @@ require 'rails'
module Rails
class Server < ::Rack::Server
class Options
+ DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze
+
def parse!(args)
args, options = args.dup, {}
@@ -91,7 +93,7 @@ module Rails
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
caching: false,
- pid: File.expand_path("tmp/pids/server.pid")
+ pid: Options::DEFAULT_PID_PATH
})
end
@@ -133,11 +135,13 @@ module Rails
def log_to_stdout
wrapped_app # touch the app so the logger is set up
- console = ActiveSupport::Logger.new($stdout)
+ console = ActiveSupport::Logger.new(STDOUT)
console.formatter = Rails.logger.formatter
console.level = Rails.logger.level
- Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ end
end
end
end
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 92230a74e4..f4d5b660dc 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -8,7 +8,7 @@ module Rails
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index cd83175da8..9ca731347a 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -20,7 +20,7 @@ module Rails
# Set the message to be shown in logs. Uses the git repo if one is given,
# otherwise use name (version).
- parts, message = [ quote(name) ], name
+ parts, message = [ quote(name) ], name.dup
if version ||= options.delete(:version)
parts << quote(version)
message << " (#{version})"
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 2f18619e30..c629459d95 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -75,7 +75,7 @@ module Rails
class_option :edge, type: :boolean, default: false,
desc: "Setup the #{name} with Gemfile pointing to Rails repository"
- class_option :rc, type: :string, default: false,
+ class_option :rc, type: :string, default: nil,
desc: "Path to file containing extra configuration options for rails command"
class_option :no_rc, type: :boolean, default: false,
@@ -117,6 +117,7 @@ module Rails
javascript_gemfile_entry,
jbuilder_gemfile_entry,
psych_gemfile_entry,
+ cable_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -281,6 +282,8 @@ module Rails
return [] if options[:skip_sprockets]
gems = []
+ gems << GemfileEntry.version('sass-rails', '~> 5.0',
+ 'Use SCSS for stylesheets')
gems << GemfileEntry.version('uglifier',
'>= 1.3.0',
@@ -337,6 +340,15 @@ module Rails
GemfileEntry.new('psych', '~> 2.0', comment, platforms: :rbx)
end
+ def cable_gemfile_entry
+ return [] if options[:skip_action_cable]
+ comment = 'Action Cable dependencies for the Redis adapter'
+ gems = []
+ gems << GemfileEntry.new("em-hiredis", '~> 0.3.0', comment)
+ gems << GemfileEntry.new("redis", '~> 3.0', comment)
+ gems
+ end
+
def bundle_command(command)
say_status :run, "bundle #{command}"
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 8145a26e22..7e437e7344 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -23,8 +23,9 @@ module Rails
type = type.to_sym if type
if type && reference?(type)
- references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { unique: true } : true
- attr_options[:index] = references_index
+ if UNIQ_INDEX_OPTIONS.include?(has_index)
+ attr_options[:index] = { unique: true }
+ end
end
new(name, type, has_index, attr_options)
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 658d883883..efbf51ddfb 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -161,6 +161,10 @@ module Rails
@route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name
end
+ def url_helper_prefix
+ @url_helper_prefix ||= (class_path + [file_name]).join('_')
+ end
+
# Tries to retrieve the application name or simply return application.
def application_name
if defined?(Rails) && Rails.application
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index f4deec7135..a4758857f2 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -78,11 +78,11 @@ module Rails
template "application.rb"
template "environment.rb"
template "secrets.yml"
+ template "cable.yml" unless options[:skip_action_cable]
directory "environments"
directory "initializers"
directory "locales"
- directory "redis" unless options[:skip_action_cable]
end
end
@@ -315,9 +315,10 @@ module Rails
def delete_action_cable_files_skipping_action_cable
if options[:skip_action_cable]
- remove_file 'config/redis/cable.yml'
+ remove_file 'config/cable.yml'
remove_file 'app/assets/javascripts/cable.coffee'
remove_dir 'app/channels'
+ gsub_file 'app/views/layouts/application.html.erb', /action_cable_meta_tag/, '' unless options[:api]
end
end
@@ -326,6 +327,7 @@ module Rails
remove_file 'config/initializers/session_store.rb'
remove_file 'config/initializers/cookies_serializer.rb'
remove_file 'config/initializers/request_forgery_protection.rb'
+ remove_file 'config/initializers/per_form_csrf_tokens.rb'
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
index 49a7cfa69b..07934d026f 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
@@ -1,4 +1,4 @@
-# Action Cable provides the framework to deal with WebSockets in Rails.
+# Action Cable provides the framework to deal with WebSockets in Rails.
# You can generate new channels where WebSocket features live using the rails generate channel command.
#
# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb).
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
index 438c84154d..d56fa30f4d 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
@@ -1,4 +1,4 @@
-# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
+# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
index 965046f3c7..b4f41389ad 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
@@ -1,4 +1,4 @@
-# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
+# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
index 70556fcc99..343c0833d7 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
@@ -3,9 +3,8 @@
require ::File.expand_path('../config/environment', __FILE__)
<%- unless options[:skip_action_cable] -%>
-# Action Cable uses EventMachine which requires that all classes are loaded in advance
+# Action Cable requires that all classes are loaded in advance
Rails.application.eager_load!
-require 'action_cable/process/logging'
<%- end -%>
run Rails.application
diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
new file mode 100644
index 0000000000..004adb7b3c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
@@ -0,0 +1,12 @@
+# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket.
+production:
+ adapter: redis
+ url: redis://localhost:6379/1
+
+development:
+ adapter: redis
+ url: redis://localhost:6379/2
+
+test:
+ adapter: redis
+ url: redis://localhost:6379/3
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 8d59a6fcf3..82509f5ef5 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -54,7 +54,7 @@ Rails.application.configure do
config.log_level = :debug
# Prepend all log lines with the following tags.
- # config.log_tags = [ :subdomain, :request_id ]
+ config.log_tags = [ :request_id ]
# Use a different logger for distributed setups.
# require 'syslog/logger'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
index 40c831ec58..f613b40f80 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
@@ -1,5 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Require `belongs_to` associations by default. This is a new Rails 5.0 default,
-# so introduced as a config to ensure apps made with earlier versions of Rails aren't affected when upgrading.
+# Require `belongs_to` associations by default. This is a new Rails 5.0
+# default, so it is introduced as a configuration option to ensure that apps
+# made on earlier versions of Rails are not affected when upgrading.
Rails.application.config.active_record.belongs_to_required_by_default = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
index 43510c244b..649e82280e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
@@ -1,5 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Do not halt callback chains when a callback returns false. This is a new Rails 5.0 default,
-# so introduced as a config to ensure apps made with earlier versions of Rails aren't affected when upgrading.
+# Do not halt callback chains when a callback returns false. This is a new
+# Rails 5.0 default, so it is introduced as a configuration option to ensure
+# that apps made with earlier versions of Rails are not affected when upgrading.
ActiveSupport.halt_callback_chains_on_return_false = false
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb
new file mode 100644
index 0000000000..1f569dedfd
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Enable per-form CSRF tokens.
+Rails.application.config.action_controller.per_form_csrf_tokens = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml
deleted file mode 100644
index 0176be24f9..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Action Cable uses Redis to administer connections, channels, and sending/receiving messages over the WebSocket.
-production:
- url: redis://localhost:6379/1
-
-development:
- url: redis://localhost:6379/2
-
-test:
- url: redis://localhost:6379/3
diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
index b2669a0f79..cdea2fd060 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
@@ -5,7 +5,7 @@
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-# You can use `rake secret` to generate a secure secret key.
+# You can use `rails secret` to generate a secure secret key.
# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 11daa5c3cb..025bcf4774 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -8,14 +8,14 @@ Description:
As a special case, specifying 'password:digest' will generate a
password_digest field of string type, and configure your generated model and
- tests for use with ActiveModel has_secure_password (assuming the default ORM
+ tests for use with Active Model has_secure_password (assuming the default ORM
and test framework are being used).
You don't have to think up every attribute up front, but it helps to
sketch out a few so you can start working with the model immediately.
This generator invokes your configured ORM and test framework, which
- defaults to ActiveRecord and TestUnit.
+ defaults to Active Record and TestUnit.
Finally, if --parent option is given, it's used as superclass of the
created model. This allows you create Single Table Inheritance models.
@@ -91,7 +91,7 @@ Available field types:
Examples:
`rails generate model account`
- For ActiveRecord and TestUnit it creates:
+ For Active Record and TestUnit it creates:
Model: app/models/account.rb
Test: test/models/account_test.rb
@@ -104,7 +104,7 @@ Examples:
`rails generate model admin/account`
- For ActiveRecord and TestUnit it creates:
+ For Active Record and TestUnit it creates:
Module: app/models/admin.rb
Model: app/models/admin/account.rb
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 7d7477de75..0c7a73a54e 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -337,7 +337,7 @@ task default: :test
end
def wrap_in_modules(unwrapped_code)
- unwrapped_code = "#{unwrapped_code}".strip.gsub(/\W$\n/, '')
+ unwrapped_code = "#{unwrapped_code}".strip.gsub(/\s$\n/, '')
modules.reverse.inject(unwrapped_code) do |content, mod|
str = "module #{mod}\n"
str += content.lines.map { |line| " #{line}" }.join
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt
new file mode 100644
index 0000000000..09aac13f42
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt
@@ -0,0 +1,7 @@
+<%= wrap_in_modules <<-rb.strip_heredoc
+ class ApplicationMailer < ActionMailer::Base
+ default from: 'from@example.com'
+ layout 'mailer'
+ end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/models/application_record.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt
index 8aa3de78f1..8aa3de78f1 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/app/models/application_record.rb.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index a0b00fc5c5..a5eebcb19f 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -1,7 +1,7 @@
# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
-require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
+require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
<% unless options[:skip_active_record] -%>
ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)]
<% if options[:mountable] -%>
diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE
index d2e495758d..c9283eda87 100644
--- a/railties/lib/rails/generators/rails/scaffold/USAGE
+++ b/railties/lib/rails/generators/rails/scaffold/USAGE
@@ -16,7 +16,7 @@ Description:
As a special case, specifying 'password:digest' will generate a
password_digest field of string type, and configure your generated model,
- controller, views, and test suite for use with ActiveModel
+ controller, views, and test suite for use with Active Model
has_secure_password (assuming they are using Rails defaults).
Timestamps are added by default, so you don't have to specify them by hand
diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
index 4f2ceb8589..ff41fef9e9 100644
--- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
@@ -13,7 +13,7 @@ class <%= class_name %>ControllerTest < ActionDispatch::IntegrationTest
<% else -%>
<% actions.each do |action| -%>
test "should get <%= action %>" do
- get <%= file_name %>_<%= action %>_url
+ get <%= url_helper_prefix %>_<%= action %>_url
assert_response :success
end
diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake
index 877f175ef3..073f235ec5 100644
--- a/railties/lib/rails/tasks/log.rake
+++ b/railties/lib/rails/tasks/log.rake
@@ -1,5 +1,12 @@
namespace :log do
- desc "Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)"
+
+ ##
+ # Truncates all/specified log files
+ # ENV['LOGS']
+ # - defaults to standard environment log files i.e. 'development,test,production'
+ # - ENV['LOGS']=all truncates all files i.e. log/*.log
+ # - ENV['LOGS']='test,development' truncates only specified files
+ desc "Truncates all/specified *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)"
task :clear do
log_files.each do |file|
clear_log_file(file)
@@ -7,15 +14,21 @@ namespace :log do
end
def log_files
- if ENV['LOGS']
- ENV['LOGS'].split(',')
- .map { |file| "log/#{file.strip}.log" }
- .select { |file| File.exist?(file) }
- else
+ if ENV['LOGS'] == 'all'
FileList["log/*.log"]
+ elsif ENV['LOGS']
+ log_files_to_truncate(ENV['LOGS'])
+ else
+ log_files_to_truncate("development,test,production")
end
end
+ def log_files_to_truncate(envs)
+ envs.split(',')
+ .map { |file| "log/#{file.strip}.log" }
+ .select { |file| File.exist?(file) }
+ end
+
def clear_log_file(file)
f = File.open(file, "w")
f.close
diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb
new file mode 100644
index 0000000000..dab4d3631d
--- /dev/null
+++ b/railties/lib/rails/test_unit/line_filtering.rb
@@ -0,0 +1,70 @@
+require 'method_source'
+
+module Rails
+ module LineFiltering # :nodoc:
+ def run(reporter, options = {})
+ if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
+ options[:filter] = \
+ CompositeFilter.new(self, options[:filter], options[:patterns])
+ end
+
+ super
+ end
+ end
+
+ class CompositeFilter # :nodoc:
+ def initialize(runnable, filter, patterns)
+ @runnable = runnable
+ @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
+ end
+
+ # Minitest uses === to find matching filters.
+ def ===(method)
+ @filters.any? { |filter| filter === method }
+ end
+
+ private
+ def derive_regexp(filter)
+ # Regexp filtering copied from Minitest.
+ filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
+ end
+
+ def derive_line_filters(patterns)
+ patterns.flat_map do |file_and_line|
+ file, *lines = file_and_line.split(':')
+
+ if lines.empty?
+ Filter.new(@runnable, file, nil) if file
+ else
+ lines.map { |line| Filter.new(@runnable, file, line) }
+ end
+ end
+ end
+ end
+
+ class Filter # :nodoc:
+ def initialize(runnable, file, line)
+ @runnable, @file = runnable, File.expand_path(file)
+ @line = line.to_i if line
+ end
+
+ def ===(method)
+ return unless @runnable.method_defined?(method)
+
+ if @line
+ test_file, test_range = definition_for(@runnable.instance_method(method))
+ test_file == @file && test_range.include?(@line)
+ else
+ @runnable.instance_method(method).source_location.first == @file
+ end
+ end
+
+ private
+ def definition_for(method)
+ file, start_line = method.source_location
+ end_line = method.source.count("\n") + start_line - 1
+
+ return file, start_line..end_line
+ end
+ end
+end
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index d4ab2ada66..29a3d991b8 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -80,7 +80,8 @@ module Minitest
Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner)
end
- self.reporter.reporters.clear # Replace progress reporter for colors.
+ # Replace progress reporter for colors.
+ self.reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) }
self.reporter << SuppressedSummaryReporter.new(options[:io], options)
self.reporter << ::Rails::TestUnitReporter.new(options[:io], options)
end
diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb
index 75180ff978..511cee33bd 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -1,3 +1,5 @@
+require 'rails/test_unit/line_filtering'
+
if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
ENV['RAILS_ENV'] ||= 'test'
end
@@ -11,6 +13,10 @@ module Rails
c.integration_tool :test_unit
end
+ initializer "test_unit.line_filtering" do
+ ActiveSupport::TestCase.extend Rails::LineFiltering
+ end
+
rake_tasks do
load "rails/test_unit/testing.rake"
end
diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb
index 83d2c55ffd..8b211ce130 100644
--- a/railties/lib/rails/test_unit/test_requirer.rb
+++ b/railties/lib/rails/test_unit/test_requirer.rb
@@ -15,7 +15,7 @@ module Rails
private
def expand_patterns(patterns)
patterns.map do |arg|
- arg = arg.gsub(/:(\d+)?$/, '')
+ arg = arg.gsub(/(:\d+)+?$/, '')
if Dir.exist?(arg)
"#{arg}/**/*_test.rb"
else
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 6676c6a079..41921e43f3 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -7,7 +7,12 @@ task default: :test
desc "Runs all tests in test folder"
task :test do
$: << "test"
- Minitest.rake_run(["test"])
+ pattern = if ENV.key?('TEST')
+ ENV['TEST']
+ else
+ "test"
+ end
+ Minitest.rake_run([pattern])
end
namespace :test do
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 0659110ac0..bcb6aff0d7 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -44,7 +44,7 @@ module ApplicationTests
test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do
# config.assets.debug and config.assets.compile are false for production environment
ENV["RAILS_ENV"] = "production"
- output = Dir.chdir(app_path){ `bin/rake assets:precompile --trace 2>&1` }
+ output = Dir.chdir(app_path){ `bin/rails assets:precompile --trace 2>&1` }
assert $?.success?, output
# Load app env
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 5f3b364f97..2670fad618 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -19,7 +19,7 @@ module ApplicationTests
def precompile!(env = nil)
with_env env.to_h do
quietly do
- precompile_task = "bin/rake assets:precompile --trace 2>&1"
+ precompile_task = "bin/rails assets:precompile --trace 2>&1"
output = Dir.chdir(app_path) { %x[ #{precompile_task} ] }
assert $?.success?, output
output
@@ -36,7 +36,7 @@ module ApplicationTests
def clean_assets!
quietly do
- assert Dir.chdir(app_path) { system('bin/rake assets:clobber') }
+ assert Dir.chdir(app_path) { system('bin/rails assets:clobber') }
end
end
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index 1bdced02e9..8c3ab65c51 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -21,14 +21,14 @@ module ApplicationTests
RUBY
list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
- File.write("log/my.log", "zomg!")
+ File.write("log/test.log", "zomg!")
assert_equal '[]', list_tables.call
- assert_equal 5, File.size("log/my.log")
+ assert_equal 5, File.size("log/test.log")
assert_not File.exist?("tmp/restart.txt")
`bin/setup 2>&1`
- assert_equal 0, File.size("log/my.log")
- assert_equal '["articles", "schema_migrations"]', list_tables.call
+ assert_equal 0, File.size("log/test.log")
+ assert_equal '["articles", "schema_migrations", "active_record_internal_metadatas"]', list_tables.call
assert File.exist?("tmp/restart.txt")
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 50d343865c..7bcfc86d03 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -79,6 +79,24 @@ module ApplicationTests
end
end
+ test "By default logs tags are not set in development" do
+ restore_default_config
+
+ with_rails_env "development" do
+ app 'development'
+ assert Rails.application.config.log_tags.blank?
+ end
+ end
+
+ test "By default logs are tagged with :request_id in production" do
+ restore_default_config
+
+ with_rails_env "production" do
+ app 'production'
+ assert_equal [:request_id], Rails.application.config.log_tags
+ end
+ end
+
test "lib dir is on LOAD_PATH during config" do
app_file 'lib/my_logger.rb', <<-RUBY
require "logger"
@@ -657,7 +675,7 @@ module ApplicationTests
private
- def form_authenticity_token; token; end # stub the authenticy token
+ def form_authenticity_token(*args); token; end # stub the authenticy token
end
RUBY
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 13f3250f5b..4c06b6324c 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -216,7 +216,7 @@ module ApplicationTests
test "use schema cache dump" do
Dir.chdir(app_path) do
`rails generate model post title:string;
- bin/rake db:migrate db:schema:cache:dump`
+ bin/rails db:migrate db:schema:cache:dump`
end
require "#{app_path}/config/environment"
ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test.
@@ -226,7 +226,7 @@ module ApplicationTests
test "expire schema cache dump" do
Dir.chdir(app_path) do
`rails generate model post title:string;
- bin/rake db:migrate db:schema:cache:dump db:rollback`
+ bin/rails db:migrate db:schema:cache:dump db:rollback`
end
silence_warnings {
require "#{app_path}/config/environment"
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 2cc599ca6f..40abaf860d 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -116,11 +116,11 @@ class LoadingTest < ActiveSupport::TestCase
require "#{rails_root}/config/environment"
setup_ar!
- assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata], ActiveRecord::Base.descendants
get "/load"
- assert_equal [ActiveRecord::SchemaMigration, Post], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, Post], ActiveRecord::Base.descendants
get "/unload"
- assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata], ActiveRecord::Base.descendants
end
test "initialize cant be called twice" do
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 25eadfc387..f847e80471 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -20,12 +20,19 @@ module ApplicationTests
@app ||= Rails.application
end
- test "config.force_ssl sets cookie to secure only" do
+ test "config.force_ssl sets cookie to secure only by default" do
add_to_config "config.force_ssl = true"
require "#{app_path}/config/environment"
assert app.config.session_options[:secure], "Expected session to be marked as secure"
end
+ test "config.force_ssl doesn't set cookie to secure only when changed from default" do
+ add_to_config "config.force_ssl = true"
+ add_to_config "config.ssl_options = { secure_cookies: false }"
+ require "#{app_path}/config/environment"
+ assert !app.config.session_options[:secure]
+ end
+
test "session is not loaded if it's not used" do
make_basic_app
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 0b0fb50fe1..c000a70382 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -28,11 +28,11 @@ module ApplicationTests
def db_create_and_drop(expected_database)
Dir.chdir(app_path) do
- output = `bin/rake db:create`
+ output = `bin/rails db:create`
assert_empty output
assert File.exist?(expected_database)
assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
- output = `bin/rake db:drop`
+ output = `bin/rails db:drop`
assert_empty output
assert !File.exist?(expected_database)
end
@@ -52,15 +52,15 @@ module ApplicationTests
def with_database_existing
Dir.chdir(app_path) do
set_database_url
- `bin/rake db:create`
+ `bin/rails db:create`
yield
- `bin/rake db:drop`
+ `bin/rails db:drop`
end
end
test 'db:create failure because database exists' do
with_database_existing do
- output = `bin/rake db:create 2>&1`
+ output = `bin/rails db:create 2>&1`
assert_match(/already exists/, output)
assert_equal 0, $?.exitstatus
end
@@ -77,7 +77,7 @@ module ApplicationTests
test 'db:create failure because bad permissions' do
with_bad_permissions do
- output = `bin/rake db:create 2>&1`
+ output = `bin/rails db:create 2>&1`
assert_match(/Couldn't create database/, output)
assert_equal 1, $?.exitstatus
end
@@ -85,7 +85,7 @@ module ApplicationTests
test 'db:drop failure because database does not exist' do
Dir.chdir(app_path) do
- output = `bin/rake db:drop 2>&1`
+ output = `bin/rails db:drop:_unsafe --trace 2>&1`
assert_match(/does not exist/, output)
assert_equal 0, $?.exitstatus
end
@@ -94,7 +94,7 @@ module ApplicationTests
test 'db:drop failure because bad permissions' do
with_database_existing do
with_bad_permissions do
- output = `bin/rake db:drop 2>&1`
+ output = `bin/rails db:drop 2>&1`
assert_match(/Couldn't drop/, output)
assert_equal 1, $?.exitstatus
end
@@ -104,8 +104,8 @@ module ApplicationTests
def db_migrate_and_status(expected_database)
Dir.chdir(app_path) do
`bin/rails generate model book title:string;
- bin/rake db:migrate`
- output = `bin/rake db:migrate:status`
+ bin/rails db:migrate`
+ output = `bin/rails db:migrate:status`
assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output)
assert_match(/up\s+\d{14}\s+Create books/, output)
end
@@ -125,7 +125,7 @@ module ApplicationTests
def db_schema_dump
Dir.chdir(app_path) do
`bin/rails generate model book title:string;
- bin/rake db:migrate db:schema:dump`
+ bin/rails db:migrate db:schema:dump`
schema_dump = File.read("db/schema.rb")
assert_match(/create_table \"books\"/, schema_dump)
end
@@ -143,7 +143,7 @@ module ApplicationTests
def db_fixtures_load(expected_database)
Dir.chdir(app_path) do
`bin/rails generate model book title:string;
- bin/rake db:migrate db:fixtures:load`
+ bin/rails db:migrate db:fixtures:load`
assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
assert_equal 2, Book.count
@@ -165,7 +165,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
Dir.chdir(app_path) do
`bin/rails generate model admin::book title:string;
- bin/rake db:migrate db:fixtures:load`
+ bin/rails db:migrate db:fixtures:load`
require "#{app_path}/app/models/admin/book"
assert_equal 2, Admin::Book.count
end
@@ -174,10 +174,10 @@ module ApplicationTests
def db_structure_dump_and_load(expected_database)
Dir.chdir(app_path) do
`bin/rails generate model book title:string;
- bin/rake db:migrate db:structure:dump`
+ bin/rails db:migrate db:structure:dump`
structure_dump = File.read("db/structure.sql")
assert_match(/CREATE TABLE \"books\"/, structure_dump)
- `bin/rake environment db:drop db:structure:load`
+ `bin/rails environment db:drop db:structure:load`
assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
#if structure is not loaded correctly, exception would be raised
@@ -201,7 +201,7 @@ module ApplicationTests
# create table without migrations
`bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
- stderr_output = capture(:stderr) { `bin/rake db:structure:dump` }
+ stderr_output = capture(:stderr) { `bin/rails db:structure:dump` }
assert_empty stderr_output
structure_dump = File.read("db/structure.sql")
assert_match(/CREATE TABLE \"posts\"/, structure_dump)
@@ -221,15 +221,15 @@ module ApplicationTests
list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
assert_equal '["posts"]', list_tables[]
- `bin/rake db:schema:load`
- assert_equal '["posts", "comments", "schema_migrations"]', list_tables[]
+ `bin/rails db:schema:load`
+ assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas"]', list_tables[]
app_file 'db/structure.sql', <<-SQL
CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
SQL
- `bin/rake db:structure:load`
- assert_equal '["posts", "comments", "schema_migrations", "users"]', list_tables[]
+ `bin/rails db:structure:load`
+ assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas", "users"]', list_tables[]
end
end
@@ -251,7 +251,7 @@ module ApplicationTests
end
RUBY
- `bin/rake db:schema:load`
+ `bin/rails db:schema:load`
tables = `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip
assert_match(/"geese"/, tables)
@@ -264,7 +264,7 @@ module ApplicationTests
def db_test_load_structure
Dir.chdir(app_path) do
`bin/rails generate model book title:string;
- bin/rake db:migrate db:structure:dump db:test:load_structure`
+ bin/rails db:migrate db:structure:dump db:test:load_structure`
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Base.establish_connection :test
require "#{app_path}/app/models/book"
@@ -300,7 +300,7 @@ module ApplicationTests
RUBY
Dir.chdir(app_path) do
- database_path = `bin/rake db:setup`
+ database_path = `bin/rails db:setup`
assert_equal "development.sqlite3", File.basename(database_path.strip)
end
ensure
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 580ed269cb..7e2519ae5a 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -22,14 +22,14 @@ module ApplicationTests
end
MIGRATION
- output = `bin/rake db:migrate SCOPE=bukkits`
+ output = `bin/rails db:migrate SCOPE=bukkits`
assert_no_match(/create_table\(:users\)/, output)
assert_no_match(/CreateUsers/, output)
assert_no_match(/add_column\(:users, :email, :string\)/, output)
assert_match(/AMigration: migrated/, output)
- output = `bin/rake db:migrate SCOPE=bukkits VERSION=0`
+ output = `bin/rails db:migrate SCOPE=bukkits VERSION=0`
assert_no_match(/drop_table\(:users\)/, output)
assert_no_match(/CreateUsers/, output)
assert_no_match(/remove_column\(:users, :email\)/, output)
@@ -43,13 +43,13 @@ module ApplicationTests
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string`
- output = `bin/rake db:migrate`
+ output = `bin/rails db:migrate`
assert_match(/create_table\(:users\)/, output)
assert_match(/CreateUsers: migrated/, output)
assert_match(/add_column\(:users, :email, :string\)/, output)
assert_match(/AddEmailToUsers: migrated/, output)
- output = `bin/rake db:rollback STEP=2`
+ output = `bin/rails db:rollback STEP=2`
assert_match(/drop_table\(:users\)/, output)
assert_match(/CreateUsers: reverted/, output)
assert_match(/remove_column\(:users, :email, :string\)/, output)
@@ -58,7 +58,7 @@ module ApplicationTests
end
test 'migration status when schema migrations table is not present' do
- output = Dir.chdir(app_path){ `bin/rake db:migrate:status 2>&1` }
+ output = Dir.chdir(app_path){ `bin/rails db:migrate:status 2>&1` }
assert_equal "Schema migrations table does not exist yet.\n", output
end
@@ -66,15 +66,15 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
- bin/rake db:migrate`
+ bin/rails db:migrate`
- output = `bin/rake db:migrate:status`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+Add email to users/, output)
- `bin/rake db:rollback STEP=1`
- output = `bin/rake db:migrate:status`
+ `bin/rails db:rollback STEP=1`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/down\s+\d{14}\s+Add email to users/, output)
@@ -87,15 +87,15 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
- bin/rake db:migrate`
+ bin/rails db:migrate`
- output = `bin/rake db:migrate:status`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/up\s+\d{3,}\s+Add email to users/, output)
- `bin/rake db:rollback STEP=1`
- output = `bin/rake db:migrate:status`
+ `bin/rails db:rollback STEP=1`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/down\s+\d{3,}\s+Add email to users/, output)
@@ -106,21 +106,21 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
- bin/rake db:migrate`
+ bin/rails db:migrate`
- output = `bin/rake db:migrate:status`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+Add email to users/, output)
- `bin/rake db:rollback STEP=2`
- output = `bin/rake db:migrate:status`
+ `bin/rails db:rollback STEP=2`
+ output = `bin/rails db:migrate:status`
assert_match(/down\s+\d{14}\s+Create users/, output)
assert_match(/down\s+\d{14}\s+Add email to users/, output)
- `bin/rake db:migrate:redo`
- output = `bin/rake db:migrate:status`
+ `bin/rails db:migrate:redo`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+Add email to users/, output)
@@ -133,21 +133,21 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
- bin/rake db:migrate`
+ bin/rails db:migrate`
- output = `bin/rake db:migrate:status`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/up\s+\d{3,}\s+Add email to users/, output)
- `bin/rake db:rollback STEP=2`
- output = `bin/rake db:migrate:status`
+ `bin/rails db:rollback STEP=2`
+ output = `bin/rails db:migrate:status`
assert_match(/down\s+\d{3,}\s+Create users/, output)
assert_match(/down\s+\d{3,}\s+Add email to users/, output)
- `bin/rake db:migrate:redo`
- output = `bin/rake db:migrate:status`
+ `bin/rails db:migrate:redo`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/up\s+\d{3,}\s+Add email to users/, output)
@@ -167,9 +167,9 @@ module ApplicationTests
end
MIGRATION
- `bin/rake db:migrate`
+ `bin/rails db:migrate`
- output = `bin/rake db:migrate:status`
+ output = `bin/rails db:migrate:status`
assert_match(/up\s+001\s+One migration/, output)
assert_match(/up\s+002\s+Two migration/, output)
@@ -184,7 +184,7 @@ module ApplicationTests
output = `bin/rails generate model author name:string`
version = output =~ %r{[^/]+db/migrate/(\d+)_create_authors\.rb} && $1
- `bin/rake db:migrate db:rollback db:forward db:migrate:up db:migrate:down VERSION=#{version}`
+ `bin/rails db:migrate db:rollback db:forward db:migrate:up db:migrate:down VERSION=#{version}`
assert !File.exist?("db/schema.rb"), "should not dump schema when configured not to"
end
@@ -192,7 +192,7 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model reviews book_id:integer`
- `bin/rake db:migrate`
+ `bin/rails db:migrate`
structure_dump = File.read("db/schema.rb")
assert_match(/create_table "reviews"/, structure_dump)
@@ -202,7 +202,7 @@ module ApplicationTests
test 'default schema generation after migration' do
Dir.chdir(app_path) do
`bin/rails generate model book title:string;
- bin/rake db:migrate`
+ bin/rails db:migrate`
structure_dump = File.read("db/schema.rb")
assert_match(/create_table "books"/, structure_dump)
@@ -213,10 +213,10 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
- bin/rake db:migrate
+ bin/rails db:migrate
rm db/migrate/*email*.rb`
- output = `bin/rake db:migrate:status`
+ output = `bin/rails db:migrate:status`
File.write('test.txt', output)
assert_match(/up\s+\d{14}\s+Create users/, output)
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index c87515f00f..50def9beb0 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -74,7 +74,7 @@ module ApplicationTests
app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
- run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bin/rake notes" do |output, lines|
+ run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bin/rails notes" do |output, lines|
assert_match(/note in app directory/, output)
assert_match(/note in config directory/, output)
assert_match(/note in db directory/, output)
@@ -102,7 +102,7 @@ module ApplicationTests
end
EOS
- run_rake_notes "bin/rake notes_custom" do |output, lines|
+ run_rake_notes "bin/rails notes_custom" do |output, lines|
assert_match(/\[FIXME\] note in lib directory/, output)
assert_match(/\[TODO\] note in test directory/, output)
assert_no_match(/OPTIMIZE/, output)
@@ -128,7 +128,7 @@ module ApplicationTests
private
- def run_rake_notes(command = 'bin/rake notes')
+ def run_rake_notes(command = 'bin/rails notes')
boot_rails
load_tasks
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index c8fb9fbc67..b979ad64d1 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -24,6 +24,26 @@ module ApplicationTests
assert $task_loaded
end
+ def test_the_test_rake_task_is_protected_when_previous_migration_was_production
+ Dir.chdir(app_path) do
+ output = `bin/rails generate model product name:string;
+ env RAILS_ENV=production bin/rails db:create db:migrate;
+ env RAILS_ENV=production bin/rails db:test:prepare test 2>&1`
+
+ assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
+ end
+ end
+
+ def test_not_protected_when_previous_migration_was_not_production
+ Dir.chdir(app_path) do
+ output = `bin/rails generate model product name:string;
+ env RAILS_ENV=test bin/rails db:create db:migrate;
+ env RAILS_ENV=test bin/rails db:test:prepare test 2>&1`
+
+ refute_match(/ActiveRecord::ProtectedEnvironmentError/, output)
+ end
+ end
+
def test_environment_is_required_in_rake_tasks
app_file "config/environment.rb", <<-RUBY
SuperMiddleware = Struct.new(:app)
@@ -35,7 +55,7 @@ module ApplicationTests
Rails.application.initialize!
RUBY
- assert_match("SuperMiddleware", Dir.chdir(app_path){ `bin/rake middleware` })
+ assert_match("SuperMiddleware", Dir.chdir(app_path){ `bin/rails middleware` })
end
def test_initializers_are_executed_in_rake_tasks
@@ -50,7 +70,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rake do_nothing` }
+ output = Dir.chdir(app_path){ `bin/rails do_nothing` }
assert_match "Doing something...", output
end
@@ -71,7 +91,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rake do_nothing` }
+ output = Dir.chdir(app_path) { `bin/rails do_nothing` }
assert_match 'Hello world', output
end
@@ -92,14 +112,14 @@ module ApplicationTests
RUBY
Dir.chdir(app_path) do
- assert system('bin/rake do_nothing RAILS_ENV=production'),
+ assert system('bin/rails do_nothing RAILS_ENV=production'),
'should not be pre-required for rake even eager_load=true'
end
end
def test_code_statistics_sanity
assert_match "Code LOC: 14 Test LOC: 0 Code to Test Ratio: 1:0.0",
- Dir.chdir(app_path){ `bin/rake stats` }
+ Dir.chdir(app_path){ `bin/rails stats` }
end
def test_rake_routes_calls_the_route_inspector
@@ -109,7 +129,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rake routes` }
+ output = Dir.chdir(app_path){ `bin/rails routes` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -122,7 +142,7 @@ module ApplicationTests
RUBY
ENV['CONTROLLER'] = 'cart'
- output = Dir.chdir(app_path){ `bin/rake routes` }
+ output = Dir.chdir(app_path){ `bin/rails routes` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -132,7 +152,7 @@ module ApplicationTests
end
RUBY
- assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `bin/rake routes` }
+ assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `bin/rails routes` }
You don't have any routes defined!
Please add some routes in config/routes.rb.
@@ -150,7 +170,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rake log_something RAILS_ENV=production && cat log/production.log` }
+ output = Dir.chdir(app_path){ `bin/rails log_something RAILS_ENV=production && cat log/production.log` }
assert_match "Sample log message", output
end
@@ -158,13 +178,13 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate model product name:string;
- bin/rake db:migrate`
+ bin/rails db:migrate`
end
require "#{rails_root}/config/environment"
# loading a specific fixture
- errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load FIXTURES=products` }
+ errormsg = Dir.chdir(app_path) { `bin/rails db:fixtures:load FIXTURES=products` }
assert $?.success?, errormsg
assert_equal 2, ::AppTemplate::Application::Product.count
@@ -173,20 +193,20 @@ module ApplicationTests
def test_loading_only_yml_fixtures
Dir.chdir(app_path) do
- `bin/rake db:migrate`
+ `bin/rails db:migrate`
end
app_file "test/fixtures/products.csv", ""
require "#{rails_root}/config/environment"
- errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load` }
+ errormsg = Dir.chdir(app_path) { `bin/rails db:fixtures:load` }
assert $?.success?, errormsg
end
def test_scaffold_tests_pass_by_default
output = Dir.chdir(app_path) do
`bin/rails generate scaffold user username:string password:string;
- RAILS_ENV=test bin/rake db:migrate test`
+ RAILS_ENV=test bin/rails db:migrate test`
end
assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
@@ -205,7 +225,7 @@ module ApplicationTests
output = Dir.chdir(app_path) do
`bin/rails generate scaffold user username:string password:string;
- RAILS_ENV=test bin/rake db:migrate test`
+ RAILS_ENV=test bin/rails db:migrate test`
end
assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output)
@@ -218,7 +238,7 @@ module ApplicationTests
output = Dir.chdir(app_path) do
`bin/rails generate scaffold LineItems product:references cart:belongs_to;
- RAILS_ENV=test bin/rake db:migrate test`
+ RAILS_ENV=test bin/rails db:migrate test`
end
assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
@@ -229,8 +249,8 @@ module ApplicationTests
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
`bin/rails generate scaffold user username:string;
- bin/rake db:migrate;
- bin/rake db:test:clone 2>&1 --trace`
+ bin/rails db:migrate;
+ bin/rails db:test:clone 2>&1 --trace`
end
assert_match(/Execute db:test:clone_structure/, output)
end
@@ -239,8 +259,8 @@ module ApplicationTests
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
`bin/rails generate scaffold user username:string;
- bin/rake db:migrate;
- bin/rake db:test:prepare 2>&1 --trace`
+ bin/rails db:migrate;
+ bin/rails db:test:prepare 2>&1 --trace`
end
assert_match(/Execute db:test:load_structure/, output)
end
@@ -248,7 +268,7 @@ module ApplicationTests
def test_rake_dump_structure_should_respect_db_structure_env_variable
Dir.chdir(app_path) do
# ensure we have a schema_migrations table to dump
- `bin/rake db:migrate db:structure:dump SCHEMA=db/my_structure.sql`
+ `bin/rails db:migrate db:structure:dump SCHEMA=db/my_structure.sql`
end
assert File.exist?(File.join(app_path, 'db', 'my_structure.sql'))
end
@@ -258,7 +278,7 @@ module ApplicationTests
output = Dir.chdir(app_path) do
`bin/rails g model post title:string;
- bin/rake db:migrate:redo 2>&1 --trace;`
+ bin/rails db:migrate:redo 2>&1 --trace;`
end
# expect only Invoke db:structure:dump (first_time)
@@ -269,21 +289,21 @@ module ApplicationTests
Dir.chdir(app_path) do
`bin/rails generate model post title:string;
bin/rails generate model product name:string;
- bin/rake db:migrate db:schema:cache:dump`
+ bin/rails db:migrate db:schema:cache:dump`
end
assert File.exist?(File.join(app_path, 'db', 'schema_cache.dump'))
end
def test_rake_clear_schema_cache
Dir.chdir(app_path) do
- `bin/rake db:schema:cache:dump db:schema:cache:clear`
+ `bin/rails db:schema:cache:dump db:schema:cache:clear`
end
assert !File.exist?(File.join(app_path, 'db', 'schema_cache.dump'))
end
def test_copy_templates
Dir.chdir(app_path) do
- `bin/rake rails:templates:copy`
+ `bin/rails rails:templates:copy`
%w(controller mailer scaffold).each do |dir|
assert File.exist?(File.join(app_path, 'lib', 'templates', 'erb', dir))
end
@@ -298,7 +318,7 @@ module ApplicationTests
app_file "template.rb", ""
output = Dir.chdir(app_path) do
- `bin/rake rails:template LOCATION=template.rb`
+ `bin/rails rails:template LOCATION=template.rb`
end
assert_match(/Hello, World!/, output)
@@ -306,7 +326,7 @@ module ApplicationTests
def test_tmp_clear_should_work_if_folder_missing
FileUtils.remove_dir("#{app_path}/tmp")
- errormsg = Dir.chdir(app_path) { `bin/rake tmp:clear` }
+ errormsg = Dir.chdir(app_path) { `bin/rails tmp:clear` }
assert_predicate $?, :success?
assert_empty errormsg
end
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index 0c180339b4..9f15ce5e85 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -74,6 +74,16 @@ module ApplicationTests
assert_match "development", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
end
+ def test_runner_detects_syntax_errors
+ Dir.chdir(app_path) { `bin/rails runner "puts 'hello world" 2>&1` }
+ refute $?.success?
+ end
+
+ def test_runner_detects_bad_script_name
+ Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` }
+ refute $?.success?
+ end
+
def test_environment_with_rails_env
with_rails_env "production" do
assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 92a9b99fd8..a7eb0feb11 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -234,6 +234,11 @@ module ApplicationTests
assert_match "0 failures, 0 errors, 0 skips", run_test_command('')
end
+ def test_generated_controller_works_with_rails_test
+ create_controller
+ assert_match "0 failures, 0 errors, 0 skips", run_test_command('')
+ end
+
def test_run_multiple_folders
create_test_file :models, 'account'
create_test_file :controllers, 'accounts_controller'
@@ -289,21 +294,91 @@ module ApplicationTests
end
end
- def test_multiple_line_filters
- create_test_file :models, 'account'
- create_test_file :models, 'post'
+ def test_more_than_one_line_filter
+ app_file 'test/models/post_test.rb', <<-RUBY
+ require 'test_helper'
- run_test_command('test/models/account_test.rb:4 test/models/post_test.rb:4').tap do |output|
- assert_match 'AccountTest', output
- assert_match 'PostTest', output
+ class PostTest < ActiveSupport::TestCase
+ test "first filter" do
+ puts 'PostTest:FirstFilter'
+ assert true
+ end
+
+ test "second filter" do
+ puts 'PostTest:SecondFilter'
+ assert true
+ end
+
+ test "test line filter does not run this" do
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command('test/models/post_test.rb:4:9').tap do |output|
+ assert_match 'PostTest:FirstFilter', output
+ assert_match 'PostTest:SecondFilter', output
+ assert_match '2 runs, 2 assertions', output
+ end
+ end
+
+ def test_more_than_one_line_filter_with_multiple_files
+ app_file 'test/models/account_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class AccountTest < ActiveSupport::TestCase
+ test "first filter" do
+ puts 'AccountTest:FirstFilter'
+ assert true
+ end
+
+ test "second filter" do
+ puts 'AccountTest:SecondFilter'
+ assert true
+ end
+
+ test "line filter does not run this" do
+ assert true
+ end
+ end
+ RUBY
+
+ app_file 'test/models/post_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostTest < ActiveSupport::TestCase
+ test "first filter" do
+ puts 'PostTest:FirstFilter'
+ assert true
+ end
+
+ test "second filter" do
+ puts 'PostTest:SecondFilter'
+ assert true
+ end
+
+ test "line filter does not run this" do
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command('test/models/account_test.rb:4:9 test/models/post_test:4:9').tap do |output|
+ assert_match 'AccountTest:FirstFilter', output
+ assert_match 'AccountTest:SecondFilter', output
+ assert_match 'PostTest:FirstFilter', output
+ assert_match 'PostTest:SecondFilter', output
+ assert_match '4 runs, 4 assertions', output
end
end
- def test_line_filter_without_line_runs_all_tests
+ def test_multiple_line_filters
create_test_file :models, 'account'
+ create_test_file :models, 'post'
- run_test_command('test/models/account_test.rb:').tap do |output|
+ run_test_command('test/models/account_test.rb:4 test/models/post_test.rb:4').tap do |output|
assert_match 'AccountTest', output
+ assert_match 'PostTest', output
end
end
@@ -367,6 +442,18 @@ module ApplicationTests
assert_match(%r{cannot load such file.+test/not_exists\.rb}, error)
end
+ def test_pass_TEST_env_on_rake_test
+ create_test_file :models, 'account'
+ create_test_file :models, 'post', pass: false
+ # This specifically verifies TEST for backwards compatibility with rake test
+ # as bin/rails test already supports running tests from a single file more cleanly.
+ output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` }
+
+ assert_match "PostTest", output, "passing TEST= should run selected test"
+ assert_no_match "AccountTest", output, "passing TEST= should only run selected test"
+ assert_match '1 runs, 1 assertions', output
+ end
+
private
def run_test_command(arguments = 'test/unit/test_test.rb')
Dir.chdir(app_path) { `bin/rails t #{arguments}` }
@@ -449,8 +536,12 @@ module ApplicationTests
run_migration
end
+ def create_controller
+ script 'generate controller admin/dashboard index'
+ end
+
def run_migration
- Dir.chdir(app_path) { `bin/rake db:migrate` }
+ Dir.chdir(app_path) { `bin/rails db:migrate` }
end
end
end
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 0e997f4ba7..85b003fce9 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -232,7 +232,7 @@ module ApplicationTests
assert_successful_test_run "models/user_test.rb"
- Dir.chdir(app_path) { `bin/rake db:test:prepare` }
+ Dir.chdir(app_path) { `bin/rails db:test:prepare` }
assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION
Expected: ["id", "name"]
diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb
index 1b1ff80bc1..4d80901217 100644
--- a/railties/test/code_statistics_test.rb
+++ b/railties/test/code_statistics_test.rb
@@ -4,7 +4,7 @@ require 'rails/code_statistics'
class CodeStatisticsTest < ActiveSupport::TestCase
def setup
@tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp'))
- @dir_js = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp', 'lib.js'))
+ @dir_js = File.join(@tmp_path, 'lib.js')
FileUtils.mkdir_p(@dir_js)
end
@@ -17,4 +17,17 @@ class CodeStatisticsTest < ActiveSupport::TestCase
@code_statistics = CodeStatistics.new(['tmp dir', @tmp_path])
end
end
+
+ test 'ignores hidden files' do
+ File.write File.join(@tmp_path, '.example.rb'), <<-CODE
+ def foo
+ puts 'foo'
+ end
+ CODE
+
+ assert_nothing_raised do
+ CodeStatistics.new(['hidden file', @tmp_path])
+ end
+ end
+
end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 3be4a74f74..0c49bd9c53 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -108,4 +108,13 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
end
end
+
+ def test_default_options
+ server = Rails::Server.new
+ old_default_options = server.default_options
+
+ Dir.chdir("..") do
+ assert_equal old_default_options, server.default_options
+ end
+ end
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index a1a17d90d8..3300850604 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -113,6 +113,14 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'Gemfile', /^gem 'rspec', ">=2\.0'0"$/
end
+ def test_gem_works_even_if_frozen_string_is_passed_as_argument
+ run_generator
+
+ action :gem, "frozen_gem".freeze, "1.0.0".freeze
+
+ assert_file 'Gemfile', /^gem 'frozen_gem', '1.0.0'$/
+ end
+
def test_gem_group_should_wrap_gems_in_a_group
run_generator
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index 2c24a6e46a..d9eb7770f3 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -89,6 +89,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
config/initializers/cookies_serializer.rb
config/initializers/session_store.rb
config/initializers/request_forgery_protection.rb
+ config/initializers/per_form_csrf_tokens.rb
lib/assets
vendor/assets
test/helpers
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 314d8c50b0..136bdd1694 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -26,6 +26,7 @@ DEFAULT_APP_FILES = %w(
config/environments
config/initializers
config/locales
+ config/cable.yml
db
lib
lib/tasks
@@ -361,6 +362,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_has_assets_gems
+ run_generator
+
+ assert_gem 'sass-rails'
+ assert_gem 'uglifier'
+ end
+
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_no_file "config/initializers/assets.rb"
@@ -385,9 +393,34 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_if_skip_action_cable_is_given
run_generator [destination_root, "--skip-action-cable"]
assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
- assert_no_file "config/redis/cable.yml"
+ assert_no_file "config/cable.yml"
+ assert_no_file "app/assets/javascripts/cable.coffee"
+ assert_no_file "app/channels"
+ assert_file "app/views/layouts/application.html.erb" do |content|
+ assert_no_match(/action_cable_meta_tag/, content)
+ end
+ assert_file "Gemfile" do |content|
+ assert_no_match(/em-hiredis/, content)
+ assert_no_match(/redis/, content)
+ end
+ end
+
+ def test_generator_if_skip_action_cable_is_given_for_an_api_app
+ run_generator [destination_root, "--skip-action-cable", "--api"]
+ assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
+ assert_no_file "config/cable.yml"
assert_no_file "app/assets/javascripts/cable.coffee"
assert_no_file "app/channels"
+ assert_file "Gemfile" do |content|
+ assert_no_match(/em-hiredis/, content)
+ assert_no_match(/redis/, content)
+ end
+ end
+
+ def test_action_cable_redis_gems
+ run_generator
+ assert_gem 'em-hiredis'
+ assert_gem 'redis'
end
def test_inclusion_of_javascript_runtime
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 80f284674d..46154b7db2 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -79,8 +79,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/#{migration}.rb" do |content|
assert_method :change, content do |change|
- assert_match(/remove_reference :books, :author, index: true/, change)
- assert_match(/remove_reference :books, :distributor, polymorphic: true, index: true/, change)
+ assert_match(/remove_reference :books, :author/, change)
+ assert_match(/remove_reference :books, :distributor, polymorphic: true/, change)
end
end
end
@@ -166,8 +166,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/#{migration}.rb" do |content|
assert_method :change, content do |change|
- assert_match(/add_reference :books, :author, index: true/, change)
- assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, change)
+ assert_match(/add_reference :books, :author/, change)
+ assert_match(/add_reference :books, :distributor, polymorphic: true/, change)
end
end
end
@@ -178,8 +178,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/#{migration}.rb" do |content|
assert_method :change, content do |change|
- assert_match(/add_reference :books, :author, index: true, null: false/, change)
- assert_match(/add_reference :books, :distributor, polymorphic: true, index: true, null: false/, change)
+ assert_match(/add_reference :books, :author, null: false/, change)
+ assert_match(/add_reference :books, :distributor, polymorphic: true, null: false/, change)
end
end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index fb502ec0c5..814f4c050e 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -345,26 +345,6 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_match(/The name 'Object' is either already used in your application or reserved/, content)
end
- def test_index_is_added_for_belongs_to_association
- run_generator ["account", "supplier:belongs_to"]
-
- assert_migration "db/migrate/create_accounts.rb" do |m|
- assert_method :change, m do |up|
- assert_match(/index: true/, up)
- end
- end
- end
-
- def test_index_is_added_for_references_association
- run_generator ["account", "supplier:references"]
-
- assert_migration "db/migrate/create_accounts.rb" do |m|
- assert_method :change, m do |up|
- assert_match(/index: true/, up)
- end
- end
- end
-
def test_index_is_skipped_for_belongs_to_association
run_generator ["account", "supplier:belongs_to", "--no-indexes"]
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 85a752455c..874bda17b7 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -307,7 +307,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/
assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/
assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/
+ assert_file "app/models/bukkits/application_record.rb", /module Bukkits\n class ApplicationRecord < ActiveRecord::Base/
assert_file "app/jobs/bukkits/application_job.rb", /module Bukkits\n class ApplicationJob < ActiveJob::Base/
+ assert_file "app/mailers/bukkits/application_mailer.rb", /module Bukkits\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n/
assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/
assert_file "app/views/layouts/bukkits/application.html.erb" do |contents|
assert_match "<title>Bukkits</title>", contents
@@ -334,7 +336,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/
assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
- assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "hyphenated-name/app/models/hyphenated/name/application_record.rb", /module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
+ assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/
assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents|
assert_match "<title>Hyphenated name</title>", contents
@@ -354,7 +358,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/
assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
- assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "my_hyphenated-name/app/models/my_hyphenated/name/application_record.rb", /module MyHyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
+ assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/
assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents|
assert_match "<title>My hyphenated name</title>", contents
@@ -374,7 +380,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/
assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/
assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/
- assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "deep-hyphenated-name/app/models/deep/hyphenated/name/application_record.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
+ assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents|
assert_match "<title>Deep hyphenated name</title>", contents
@@ -617,6 +625,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_directory "app/views"
end
+ def test_model_with_existent_application_record_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g model article`
+ end
+
+ assert_file "app/models/bukkits/article.rb", /class Article < ApplicationRecord/
+ end
+
protected
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
index f0fb63c208..f492cd49ef 100644
--- a/railties/test/generators/plugin_test_runner_test.rb
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -59,14 +59,6 @@ class PluginTestRunnerTest < ActiveSupport::TestCase
end
end
- def test_line_filter_without_line_runs_all_tests
- create_test_file 'account'
-
- run_test_command('test/account_test.rb:').tap do |output|
- assert_match 'AccountTest', output
- end
- end
-
def test_output_inline_by_default
create_test_file 'post', pass: false
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index eb81ea3d0e..6f7a83cae0 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -14,8 +14,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/product_lines.yml"
- assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/
- assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/
+ assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/
+ assert_migration "db/migrate/create_product_lines.rb", /references :user/
# Route
assert_file "config/routes.rb" do |route|
@@ -94,8 +94,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/product_lines.yml"
- assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/
- assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/
+ assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/
+ assert_migration "db/migrate/create_product_lines.rb", /references :user/
# Route
assert_file "config/routes.rb" do |route|
diff --git a/tasks/release.rb b/tasks/release.rb
index ea7f66a171..c7704aa865 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -27,7 +27,7 @@ directory "pkg"
file = Dir[glob].first
ruby = File.read(file)
- major, minor, tiny, pre = version.split('.')
+ major, minor, tiny, pre = version.split('.', 4)
pre = pre ? pre.inspect : "nil"
ruby.gsub!(/^(\s*)MAJOR(\s*)= .*?$/, "\\1MAJOR = #{major}")
diff --git a/version.rb b/version.rb
index 92230a74e4..f4d5b660dc 100644
--- a/version.rb
+++ b/version.rb
@@ -8,7 +8,7 @@ module Rails
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1"
+ PRE = "beta1.1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end