aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml5
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock17
-rw-r--r--README.md2
-rw-r--r--actionmailer/test/base_test.rb7
-rw-r--r--actionmailer/test/delivery_methods_test.rb6
-rw-r--r--actionmailer/test/mailers/base_mailer.rb6
-rw-r--r--actionpack/CHANGELOG.md18
-rw-r--r--actionpack/lib/abstract_controller/collector.rb6
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb3
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb16
-rw-r--r--actionpack/lib/action_controller/metal/live.rb31
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb6
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb4
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb14
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb6
-rw-r--r--actionpack/lib/action_controller/test_case.rb25
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb20
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb130
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb35
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb53
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/dot.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb23
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb77
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb41
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb2
-rw-r--r--actionpack/test/abstract/collector_test.rb6
-rw-r--r--actionpack/test/abstract_unit.rb1
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb2
-rw-r--r--actionpack/test/controller/content_type_test.rb44
-rw-r--r--actionpack/test/controller/live_stream_test.rb92
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb4
-rw-r--r--actionpack/test/controller/redirect_test.rb6
-rw-r--r--actionpack/test/controller/render_other_test.rb2
-rw-r--r--actionpack/test/controller/render_xml_test.rb2
-rw-r--r--actionpack/test/controller/send_file_test.rb104
-rw-r--r--actionpack/test/controller/test_case_test.rb11
-rw-r--r--actionpack/test/controller/webservice_test.rb22
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb97
-rw-r--r--actionpack/test/dispatch/request_test.rb48
-rw-r--r--actionpack/test/dispatch/response_test.rb23
-rw-r--r--actionpack/test/journey/router_test.rb12
-rw-r--r--actionview/CHANGELOG.md5
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb4
-rw-r--r--actionview/lib/action_view/digestor.rb6
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb15
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb22
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb25
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb26
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb10
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb9
-rw-r--r--actionview/lib/action_view/lookup_context.rb8
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb6
-rw-r--r--actionview/lib/action_view/template/resolver.rb4
-rw-r--r--actionview/test/abstract_unit.rb1
-rw-r--r--actionview/test/actionpack/controller/capture_test.rb2
-rw-r--r--actionview/test/actionpack/controller/render_test.rb2
-rw-r--r--actionview/test/template/dependency_tracker_test.rb1
-rw-r--r--actionview/test/template/form_collections_helper_test.rb42
-rw-r--r--actionview/test/template/form_helper_test.rb36
-rw-r--r--actionview/test/template/form_tag_helper_test.rb6
-rw-r--r--actionview/test/template/lookup_context_test.rb6
-rw-r--r--actionview/test/template/number_helper_test.rb1
-rw-r--r--actionview/test/template/render_test.rb10
-rw-r--r--actionview/test/template/translation_helper_test.rb6
-rw-r--r--activejob/CHANGELOG.md4
-rw-r--r--activejob/lib/active_job/async_job.rb3
-rw-r--r--activejob/lib/active_job/base.rb2
-rw-r--r--activejob/lib/active_job/core.rb8
-rw-r--r--activejob/lib/active_job/enqueuing.rb4
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb4
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb4
-rw-r--r--activejob/lib/active_job/queue_priority.rb44
-rw-r--r--activejob/test/cases/queue_priority_test.rb47
-rw-r--r--activejob/test/cases/test_case_test.rb2
-rw-r--r--activejob/test/helper.rb1
-rw-r--r--activejob/test/integration/queuing_test.rb12
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb2
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb3
-rw-r--r--activejob/test/support/integration/helper.rb2
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb8
-rw-r--r--activejob/test/support/que/inline.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb4
-rw-r--r--activemodel/lib/active_model/callbacks.rb1
-rw-r--r--activemodel/lib/active_model/dirty.rb4
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/type.rb57
-rw-r--r--activemodel/lib/active_model/type/big_integer.rb (renamed from activerecord/lib/active_record/type/big_integer.rb)4
-rw-r--r--activemodel/lib/active_model/type/binary.rb (renamed from activerecord/lib/active_record/type/binary.rb)2
-rw-r--r--activemodel/lib/active_model/type/boolean.rb (renamed from activerecord/lib/active_record/type/boolean.rb)6
-rw-r--r--activemodel/lib/active_model/type/date.rb50
-rw-r--r--activemodel/lib/active_model/type/date_time.rb44
-rw-r--r--activemodel/lib/active_model/type/decimal.rb (renamed from activerecord/lib/active_record/type/decimal.rb)4
-rw-r--r--activemodel/lib/active_model/type/decimal_without_scale.rb (renamed from activerecord/lib/active_record/type/decimal_without_scale.rb)4
-rw-r--r--activemodel/lib/active_model/type/float.rb (renamed from activerecord/lib/active_record/type/float.rb)2
-rw-r--r--activemodel/lib/active_model/type/helpers.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb (renamed from activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb)15
-rw-r--r--activemodel/lib/active_model/type/helpers/mutable.rb (renamed from activerecord/lib/active_record/type/helpers/mutable.rb)2
-rw-r--r--activemodel/lib/active_model/type/helpers/numeric.rb (renamed from activerecord/lib/active_record/type/helpers/numeric.rb)2
-rw-r--r--activemodel/lib/active_model/type/helpers/time_value.rb (renamed from activerecord/lib/active_record/type/helpers/time_value.rb)39
-rw-r--r--activemodel/lib/active_model/type/integer.rb (renamed from activerecord/lib/active_record/type/integer.rb)2
-rw-r--r--activemodel/lib/active_model/type/registry.rb64
-rw-r--r--activemodel/lib/active_model/type/string.rb (renamed from activerecord/lib/active_record/type/string.rb)2
-rw-r--r--activemodel/lib/active_model/type/text.rb (renamed from activerecord/lib/active_record/type/text.rb)4
-rw-r--r--activemodel/lib/active_model/type/time.rb42
-rw-r--r--activemodel/lib/active_model/type/unsigned_integer.rb (renamed from activerecord/lib/active_record/type/unsigned_integer.rb)2
-rw-r--r--activemodel/lib/active_model/type/value.rb (renamed from activerecord/lib/active_record/type/value.rb)5
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb55
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb1
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb4
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb2
-rw-r--r--activemodel/test/cases/type/decimal_test.rb (renamed from activerecord/test/cases/type/decimal_test.rb)5
-rw-r--r--activemodel/test/cases/type/integer_test.rb108
-rw-r--r--activemodel/test/cases/type/registry_test.rb39
-rw-r--r--activemodel/test/cases/type/string_test.rb20
-rw-r--r--activemodel/test/cases/type/unsigned_integer_test.rb (renamed from activerecord/test/cases/type/unsigned_integer_test.rb)5
-rw-r--r--activemodel/test/cases/types_test.rb122
-rw-r--r--activerecord/CHANGELOG.md89
-rw-r--r--activerecord/README.rdoc2
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb6
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb3
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb14
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb2
-rw-r--r--activerecord/lib/active_record/attribute.rb7
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb122
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb2
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb84
-rw-r--r--activerecord/lib/active_record/attribute_set.rb13
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb8
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb80
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb234
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb78
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb5
-rw-r--r--activerecord/lib/active_record/core.rb4
-rw-r--r--activerecord/lib/active_record/enum.rb42
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb13
-rw-r--r--activerecord/lib/active_record/null_relation.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/reflection.rb30
-rw-r--r--activerecord/lib/active_record/relation.rb1
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb7
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb6
-rw-r--r--activerecord/lib/active_record/sanitization.rb77
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb21
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb27
-rw-r--r--activerecord/lib/active_record/transactions.rb1
-rw-r--r--activerecord/lib/active_record/type.rb34
-rw-r--r--activerecord/lib/active_record/type/adapter_specific_registry.rb36
-rw-r--r--activerecord/lib/active_record/type/date.rb46
-rw-r--r--activerecord/lib/active_record/type/date_time.rb41
-rw-r--r--activerecord/lib/active_record/type/helpers.rb4
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb4
-rw-r--r--activerecord/lib/active_record/type/internal/timezone.rb15
-rw-r--r--activerecord/lib/active_record/type/serialized.rb10
-rw-r--r--activerecord/lib/active_record/type/time.rb40
-rw-r--r--activerecord/lib/active_record/type/type_map.rb8
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb2
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb6
-rw-r--r--activerecord/test/cases/adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/explain_test.rb21
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql/statement_pool_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/unsigned_type_test.rb39
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb39
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb21
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb13
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb2
-rw-r--r--activerecord/test/cases/attribute_set_test.rb34
-rw-r--r--activerecord/test/cases/attribute_test.rb20
-rw-r--r--activerecord/test/cases/base_test.rb22
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb11
-rw-r--r--activerecord/test/cases/dirty_test.rb21
-rw-r--r--activerecord/test/cases/finder_test.rb12
-rw-r--r--activerecord/test/cases/helper.rb8
-rw-r--r--activerecord/test/cases/integration_test.rb1
-rw-r--r--activerecord/test/cases/migration/helper.rb2
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb8
-rw-r--r--activerecord/test/cases/primary_keys_test.rb30
-rw-r--r--activerecord/test/cases/relation_test.rb29
-rw-r--r--activerecord/test/cases/sanitize_test.rb8
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb14
-rw-r--r--activerecord/test/cases/test_case.rb2
-rw-r--r--activerecord/test/cases/test_fixtures_test.rb2
-rw-r--r--activerecord/test/cases/timestamp_test.rb20
-rw-r--r--activerecord/test/cases/touch_later_test.rb2
-rw-r--r--activerecord/test/cases/type/date_time_test.rb14
-rw-r--r--activerecord/test/cases/type/integer_test.rb100
-rw-r--r--activerecord/test/cases/type/string_test.rb14
-rw-r--r--activerecord/test/cases/types_test.rb105
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb1
-rw-r--r--activerecord/test/cases/validations_test.rb11
-rw-r--r--activerecord/test/cases/view_test.rb37
-rw-r--r--activerecord/test/models/company.rb3
-rw-r--r--activerecord/test/models/contact.rb2
-rw-r--r--activerecord/test/models/developer.rb8
-rw-r--r--activerecord/test/models/project.rb2
-rw-r--r--activerecord/test/schema/schema.rb79
-rw-r--r--activesupport/CHANGELOG.md39
-rw-r--r--activesupport/activesupport.gemspec3
-rw-r--r--activesupport/lib/active_support/callbacks.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/strip.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/uri.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb4
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb2
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb7
-rw-r--r--activesupport/lib/active_support/key_generator.rb4
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb1
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb1
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb6
-rw-r--r--activesupport/lib/active_support/number_helper.rb2
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb4
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb7
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb12
-rw-r--r--activesupport/test/callbacks_test.rb20
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb12
-rw-r--r--activesupport/test/core_ext/hash/transform_values_test.rb12
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb1
-rw-r--r--activesupport/test/core_ext/object/try_test.rb2
-rw-r--r--activesupport/test/inflector_test_cases.rb1
-rw-r--r--activesupport/test/multibyte_conformance_test.rb1
-rw-r--r--activesupport/test/multibyte_proxy_test.rb1
-rw-r--r--activesupport/test/multibyte_test_helpers.rb1
-rw-r--r--activesupport/test/test_case_test.rb8
-rw-r--r--guides/rails_guides/markdown.rb2
-rw-r--r--guides/source/_welcome.html.erb10
-rw-r--r--guides/source/action_mailer_basics.md4
-rw-r--r--guides/source/action_view_overview.md33
-rw-r--r--guides/source/active_record_validations.md2
-rw-r--r--guides/source/active_support_core_extensions.md6
-rw-r--r--guides/source/api_app.md7
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/association_basics.md32
-rw-r--r--guides/source/command_line.md2
-rw-r--r--guides/source/configuring.md3
-rw-r--r--guides/source/engines.md24
-rw-r--r--guides/source/i18n.md4
-rw-r--r--guides/source/kindle/layout.html.erb4
-rw-r--r--guides/source/kindle/toc.ncx.erb8
-rw-r--r--guides/source/rails_on_rack.md5
-rw-r--r--guides/source/routing.md4
-rw-r--r--guides/source/testing.md10
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb1
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb2
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb4
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb3
-rw-r--r--railties/test/application/configuration_test.rb2
-rw-r--r--railties/test/application/middleware_test.rb2
-rw-r--r--railties/test/application/rake_test.rb1
-rw-r--r--railties/test/application/routing_test.rb12
-rw-r--r--railties/test/generators/app_generator_test.rb193
-rw-r--r--railties/test/generators/namespaced_generators_test.rb1
-rw-r--r--railties/test/generators/plugin_generator_test.rb1
-rw-r--r--railties/test/generators/shared_generator_tests.rb48
-rw-r--r--railties/test/isolation/abstract_unit.rb1
321 files changed, 3492 insertions, 1852 deletions
diff --git a/.travis.yml b/.travis.yml
index c648bd2ca7..5d4a9e9c67 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,11 @@ script: 'ci/travis.rb'
before_install:
- gem install bundler
- "rm ${BUNDLE_GEMFILE}.lock"
+ - curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp
+ - cd /tmp/beanstalkd-1.10/
+ - make
+ - ./beanstalkd &
+ - cd $TRAVIS_BUILD_DIR
before_script:
- bundle update
cache: bundler
diff --git a/Gemfile b/Gemfile
index a9a326c0ff..8bb4c7d7a9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -17,7 +17,7 @@ gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
gem 'coffee-rails', '~> 4.1.0'
-gem 'turbolinks'
+gem 'turbolinks', github: 'rails/turbolinks', branch: 'master'
gem 'arel', github: 'rails/arel', branch: 'master'
gem 'mail', github: 'mikel/mail', branch: 'master'
diff --git a/Gemfile.lock b/Gemfile.lock
index da4614db2b..563a9f56be 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -89,6 +89,14 @@ GIT
sprockets (4.0.0)
rack (> 1, < 3)
+GIT
+ remote: git://github.com/rails/turbolinks.git
+ revision: 4bb563cd777875d3ad73cf007c26334f2aa8dc37
+ branch: master
+ specs:
+ turbolinks (3.0.0)
+ coffee-rails
+
PATH
remote: .
specs:
@@ -122,12 +130,11 @@ PATH
activesupport (= 5.0.0.alpha)
arel (= 7.0.0.alpha)
activesupport (5.0.0.alpha)
- concurrent-ruby (~> 0.9.1)
+ concurrent-ruby (~> 1.0.0.pre2, < 2.0.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
method_source
minitest (~> 5.1)
- thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
rails (5.0.0.alpha)
actionmailer (= 5.0.0.alpha)
@@ -172,7 +179,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.9.1.1)
- concurrent-ruby (0.9.1)
+ concurrent-ruby (1.0.0.pre2)
connection_pool (2.2.0)
dalli (2.7.4)
dante (0.2.0)
@@ -274,8 +281,6 @@ GEM
thread_safe (0.3.5)
timers (4.0.4)
hitimes
- turbolinks (2.5.3)
- coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
tzinfo-data (1.2015.6)
@@ -342,7 +347,7 @@ DEPENDENCIES
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
- turbolinks
+ turbolinks!
tzinfo-data
uglifier (>= 1.3.0)
w3c_validators
diff --git a/README.md b/README.md
index a17b6245f5..f823a49f7d 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ Ruby code (ERB files). Views are typically rendered to generate a controller res
or to generate the body of an email. In Rails, View generation is handled by Action View.
You can read more about Action View in its [README](actionview/README.rdoc).
-Active Record, Action Pack, and Action View can each be used independently outside Rails.
+Active Record, Active Model, Action Pack, and Action View can each be used independently outside Rails.
In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library
to generate and send emails; Active Job ([README](activejob/README.md)), a
framework for declaring jobs and making them run on a variety of queueing
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 6b2e6a531c..50f2c71737 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -449,6 +449,13 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("Format with any!", email.parts[1].body.encoded)
end
+ test 'explicit without specifying format with format.any' do
+ error = assert_raises(ArgumentError) do
+ BaseMailer.explicit_without_specifying_format_with_any.parts
+ end
+ assert_equal "You have to supply at least one format", error.message
+ end
+
test "explicit multipart with format(Hash)" do
email = BaseMailer.explicit_multipart_with_options(true)
email.ready_to_send!
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index ceaa4ca83e..d17e774092 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -165,16 +165,18 @@ class MailDeliveryTest < ActiveSupport::TestCase
test "non registered delivery methods raises errors" do
DeliveryMailer.delivery_method = :unknown
- assert_raise RuntimeError do
+ error = assert_raise RuntimeError do
DeliveryMailer.welcome.deliver_now
end
+ assert_equal "Invalid delivery method :unknown", error.message
end
test "undefined delivery methods raises errors" do
DeliveryMailer.delivery_method = nil
- assert_raise RuntimeError do
+ error = assert_raise RuntimeError do
DeliveryMailer.welcome.deliver_now
end
+ assert_equal "Delivery method cannot be nil", error.message
end
test "does not perform deliveries if requested" do
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index bd991e209e..8c2225ce60 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -80,6 +80,12 @@ class BaseMailer < ActionMailer::Base
end
end
+ def explicit_without_specifying_format_with_any(hash = {})
+ mail(hash) do |format|
+ format.any
+ end
+ end
+
def explicit_multipart_with_options(include_html = false)
mail do |format|
format.text(content_transfer_encoding: "base64"){ render "welcome" }
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 73a833a23b..bb15edee63 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,21 @@
+* ActionDispatch::Response#new no longer applies default headers. If you want
+ default headers applied to the response object, then call
+ `ActionDispatch::Response.create`. This change only impacts people who are
+ directly constructing an `ActionDispatch::Response` object.
+
+* Accessing mime types via constants like `Mime::HTML` is deprecated. Please
+ change code like this:
+
+ Mime::HTML
+
+ To this:
+
+ Mime::Type[:HTML]
+
+ This change is so that Rails will not manage a list of constants, and fixes
+ an issue where if a type isn't registered you could possibly get the wrong
+ object.
+
* `url_for` does not modify its arguments when generating polymorphic URLs.
*Bernerd Schaefer*
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index ddd56b354a..3b5128cda5 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -7,7 +7,7 @@ module AbstractController
const = sym.upcase
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{sym}(*args, &block) # def html(*args, &block)
- custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block)
+ custom(Mime::Type[:#{const}], *args, &block) # custom(Mime::Type[:HTML], *args, &block)
end # end
RUBY
end
@@ -25,7 +25,7 @@ module AbstractController
def method_missing(symbol, &block)
const_name = symbol.upcase
- unless Mime.const_defined?(const_name)
+ unless Mime::Type.registered?(const_name)
raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
"http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
"If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
@@ -33,7 +33,7 @@ module AbstractController
"format.html { |html| html.tablet { ... } }"
end
- mime_constant = Mime.const_get(const_name)
+ mime_constant = Mime::Type[const_name]
if Mime::SET.include?(mime_constant)
AbstractController::Collector.generate_method_for_mime(mime_constant)
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 6c0a072b73..78b43f2fbe 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -55,7 +55,7 @@ module AbstractController
# Returns Content-Type of rendered content
# :api: public
def rendered_format
- Mime::TEXT
+ Mime::Type[:TEXT]
end
DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 0384740fef..beeaae9d0c 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -135,7 +135,7 @@ module ActionController
end
def self.make_response!(request)
- ActionDispatch::Response.new.tap do |res|
+ ActionDispatch::Response.create.tap do |res|
res.request = request
end
end
@@ -187,6 +187,7 @@ module ActionController
set_request!(request)
set_response!(response)
process(name)
+ request.commit_flash
to_a
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 15d4562abb..fe470552b0 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -34,7 +34,7 @@ module ActionController
#
# def authenticate
# case request.format
- # when Mime::XML, Mime::ATOM
+ # when Mime::Type[:XML], Mime::Type[:ATOM]
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
# @current_user = user
# else
@@ -361,7 +361,7 @@ module ActionController
#
# def authenticate
# case request.format
- # when Mime::XML, Mime::ATOM
+ # when Mime::Type[:XML], Mime::Type[:ATOM]
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
# @current_user = user
# else
@@ -436,15 +436,17 @@ module ActionController
end
end
- # Parses the token and options out of the token authorization header. If
- # the header looks like this:
+ # Parses the token and options out of the token authorization header.
+ # The value for the Authorization header is expected to have the prefix
+ # <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
# Authorization: Token token="abc", nonce="def"
- # Then the returned token is "abc", and the options is {nonce: "def"}
+ # Then the returned token is <tt>"abc"</tt>, and the options are
+ # <tt>{nonce: "def"}</tt>
#
# request - ActionDispatch::Request instance with the current headers.
#
- # Returns an Array of [String, Hash] if a token is present.
- # Returns nil if no token is found.
+ # Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
+ # Returns +nil+ if no token is found.
def token_and_options(request)
authorization_request = request.authorization.to_s
if authorization_request[TOKEN_REGEX]
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 667c7f87ca..7db8d13e24 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -213,29 +213,6 @@ module ActionController
end
class Response < ActionDispatch::Response #:nodoc: all
- class Header < DelegateClass(Hash) # :nodoc:
- def initialize(response, header)
- @response = response
- super(header)
- end
-
- def []=(k,v)
- if @response.committed?
- raise ActionDispatch::IllegalStateError, 'header already sent'
- end
-
- super
- end
-
- def merge(other)
- self.class.new @response, __getobj__.merge(other)
- end
-
- def to_hash
- __getobj__.dup
- end
- end
-
private
def before_committed
@@ -256,14 +233,6 @@ module ActionController
body.each { |part| buf.write part }
buf
end
-
- def merge_default_headers(original, default)
- Header.new self, super
- end
-
- def handle_conditional_get!
- super unless committed?
- end
end
def process(name)
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 88a4818c16..fc42fe5c07 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -229,14 +229,14 @@ module ActionController #:nodoc:
@responses = {}
@variant = variant
- mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil }
+ mimes.each { |mime| @responses[Mime::Type[mime.upcase.to_sym]] = nil }
end
def any(*args, &block)
if args.any?
args.each { |type| send(type, &block) }
else
- custom(Mime::ALL, &block)
+ custom(Mime::Type[:ALL], &block)
end
end
alias :all :any
@@ -251,7 +251,7 @@ module ActionController #:nodoc:
end
def response
- response = @responses.fetch(format, @responses[Mime::ALL])
+ response = @responses.fetch(format, @responses[Mime::Type[:ALL]])
if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
response.variant
elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index e680432127..c38fc40b81 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -276,7 +276,9 @@ module ActionController
# Checks if we should perform parameters wrapping.
def _wrapper_enabled?
- ref = request.content_mime_type.try(:ref)
+ return false unless request.has_content_type?
+
+ ref = request.content_mime_type.ref
_wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key]
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index cb74c4f0d4..d867c97b46 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -68,11 +68,11 @@ module ActionController
# ActionController::Renderers.add :csv do |obj, options|
# filename = options[:filename] || 'data'
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
- # send_data str, type: Mime::CSV,
+ # send_data str, type: Mime::Type[:CSV],
# disposition: "attachment; filename=#{filename}.csv"
# end
#
- # Note that we used Mime::CSV for the csv mime type as it comes with Rails.
+ # Note that we used Mime::Type[:CSV] for the csv mime type as it comes with Rails.
# For a custom renderer, you'll need to register a mime type with
# <tt>Mime::Type.register</tt>.
#
@@ -116,24 +116,24 @@ module ActionController
json = json.to_json(options) unless json.kind_of?(String)
if options[:callback].present?
- if content_type.nil? || content_type == Mime::JSON
- self.content_type = Mime::JS
+ if content_type.nil? || content_type == Mime::Type[:JSON]
+ self.content_type = Mime::Type[:JS]
end
"/**/#{options[:callback]}(#{json})"
else
- self.content_type ||= Mime::JSON
+ self.content_type ||= Mime::Type[:JSON]
json
end
end
add :js do |js, options|
- self.content_type ||= Mime::JS
+ self.content_type ||= Mime::Type[:JS]
js.respond_to?(:to_js) ? js.to_js(options) : js
end
add :xml do |xml, options|
- self.content_type ||= Mime::XML
+ self.content_type ||= Mime::Type[:XML]
xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 00b551af94..1ecccf9864 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -64,7 +64,7 @@ module ActionController
end
def _set_html_content_type
- self.content_type = Mime::HTML.to_s
+ self.content_type = Mime::Type[:HTML].to_s
end
def _set_rendered_content_type(format)
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index 47d940f692..b2b3b4283f 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -2,12 +2,6 @@ module ActionController
module Testing
extend ActiveSupport::Concern
- # TODO : Rewrite tests using controller.headers= to use Rack env
- def headers=(new_headers)
- @_response ||= ActionDispatch::Response.new
- @_response.headers.replace(new_headers)
- end
-
# Behavior specific to functional tests
module Functional # :nodoc:
def set_response!(request)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index fbbaa1a887..cf78688126 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -33,6 +33,9 @@ module ActionController
self.session = session
self.session_options = TestSession::DEFAULT_OPTIONS
+ @custom_param_parsers = {
+ Mime::Type[:XML] => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
+ }
end
def query_string=(string)
@@ -74,26 +77,18 @@ module ActionController
set_header k, 'application/x-www-form-urlencoded'
end
- # FIXME: setting `request_parametes` is normally handled by the
- # params parser middleware, and we should remove this roundtripping
- # when we switch to caling `call` on the controller
-
case content_mime_type.to_sym
when nil
raise "Unknown Content-Type: #{content_type}"
when :json
data = ActiveSupport::JSON.encode(non_path_parameters)
- params = ActiveSupport::JSON.decode(data).with_indifferent_access
- self.request_parameters = params
when :xml
data = non_path_parameters.to_xml
- params = Hash.from_xml(data)['hash']
- self.request_parameters = params
when :url_encoded_form
data = non_path_parameters.to_query
else
+ @custom_param_parsers[content_mime_type] = ->(_) { non_path_parameters }
data = non_path_parameters.to_query
- self.request_parameters = non_path_parameters
end
end
@@ -136,6 +131,12 @@ module ActionController
"multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
end
end.new
+
+ private
+
+ def params_parsers
+ super.merge @custom_param_parsers
+ end
end
class LiveTestResponse < Live::Response
@@ -401,7 +402,7 @@ module ActionController
MSG
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ @request.env['HTTP_ACCEPT'] ||= [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], 'text/xml', Mime::Type[:ALL]].join(', ')
__send__(*args).tap do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
@@ -504,7 +505,7 @@ module ActionController
if xhr
@request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
@request.fetch_header('HTTP_ACCEPT') do |k|
- @request.set_header k, [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ @request.set_header k, [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], 'text/xml', Mime::Type[:ALL]].join(', ')
end
end
@@ -584,7 +585,7 @@ module ActionController
end
def build_response(klass)
- klass.new
+ klass.create
end
included do
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index cab60a508a..a966c5e452 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -10,7 +10,7 @@ module ActionDispatch
self.ignore_accept_header = false
end
- # The MIME type of the HTTP request, such as Mime::XML.
+ # The MIME type of the HTTP request, such as Mime::Type[:XML].
#
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
@@ -29,6 +29,10 @@ module ActionDispatch
content_mime_type && content_mime_type.to_s
end
+ def has_content_type?
+ has_header? 'CONTENT_TYPE'
+ end
+
# Returns the accepted MIME type for the request.
def accepts
fetch_header("action_dispatch.request.accepts") do |k|
@@ -45,9 +49,9 @@ module ActionDispatch
# Returns the MIME type for the \format used in the request.
#
- # GET /posts/5.xml | request.format => Mime::XML
- # GET /posts/5.xhtml | request.format => Mime::HTML
- # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
+ # GET /posts/5.xml | request.format => Mime::Type[:XML]
+ # GET /posts/5.xhtml | request.format => Mime::Type[:HTML]
+ # GET /posts/5 | request.format => Mime::Type[:HTML] or Mime::Type[:JS], or request.accepts.first
#
def format(view_path = [])
formats.first || Mime::NullType.instance
@@ -66,9 +70,9 @@ module ActionDispatch
elsif use_accept_header && valid_accept_header
accepts
elsif xhr?
- [Mime::JS]
+ [Mime::Type[:JS]]
else
- [Mime::HTML]
+ [Mime::Type[:HTML]]
end
set_header k, v
end
@@ -134,14 +138,14 @@ module ActionDispatch
#
def negotiate_mime(order)
formats.each do |priority|
- if priority == Mime::ALL
+ if priority == Mime::Type[:ALL]
return order.first
elsif order.include?(priority)
return priority
end
end
- order.include?(Mime::ALL) ? format : nil
+ order.include?(Mime::Type[:ALL]) ? format : nil
end
protected
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index a639f8a8f8..36e90e5855 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,23 +1,32 @@
-require 'set'
require 'singleton'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/starts_ends_with'
+require 'active_support/deprecation'
module Mime
- class Mimes < Array
- def symbols
- @symbols ||= map(&:to_sym)
+ class Mimes
+ include Enumerable
+
+ def initialize
+ @mimes = []
+ @symbols = nil
end
- %w(<< concat shift unshift push pop []= clear compact! collect!
- delete delete_at delete_if flatten! map! insert reject! reverse!
- replace slice! sort! uniq!).each do |method|
- module_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{method}(*)
- @symbols = nil
- super
- end
- CODE
+ def each
+ @mimes.each { |x| yield x }
+ end
+
+ def <<(type)
+ @mimes << type
+ @symbols = nil
+ end
+
+ def delete_if
+ @mimes.delete_if { |x| yield x }.tap { @symbols = nil }
+ end
+
+ def symbols
+ @symbols ||= map(&:to_sym)
end
end
@@ -35,6 +44,40 @@ module Mime
return type if type.is_a?(Type)
EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
end
+
+ def const_missing(sym)
+ if Mime::Type.registered?(sym)
+ ActiveSupport::Deprecation.warn <<-eow
+Accessing mime types via constants is deprecated. Please change:
+
+ `Mime::#{sym}`
+
+to:
+
+ `Mime::Type[:#{sym}]`
+ eow
+ Mime::Type[sym]
+ else
+ super
+ end
+ end
+
+ def const_defined?(sym, inherit = true)
+ if Mime::Type.registered?(sym)
+ ActiveSupport::Deprecation.warn <<-eow
+Accessing mime types via constants is deprecated. Please change:
+
+ `Mime.const_defined?(#{sym})`
+
+to:
+
+ `Mime::Type.registered?(:#{sym})`
+ eow
+ true
+ else
+ super
+ end
+ end
end
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
@@ -51,9 +94,6 @@ module Mime
# end
# end
class Type
- @@html_types = Set.new [:html, :all]
- cattr_reader :html_types
-
attr_reader :symbol
@register_callbacks = []
@@ -66,7 +106,7 @@ module Mime
def initialize(index, name, q = nil)
@index = index
@name = name
- q ||= 0.0 if @name == Mime::ALL.to_s # default wildcard match to end of list
+ q ||= 0.0 if @name == Mime::Type[:ALL].to_s # default wildcard match to end of list
@q = ((q || 1.0).to_f * 100).to_i
end
@@ -91,7 +131,7 @@ module Mime
exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
delete_at(text_xml_idx) # delete text_xml from the list
elsif text_xml_idx
- text_xml.name = Mime::XML.to_s
+ text_xml.name = Mime::Type[:XML].to_s
end
# Look for more specific XML-based types and sort them ahead of app/xml
@@ -120,7 +160,7 @@ module Mime
end
def app_xml_idx
- @app_xml_idx ||= index(Mime::XML.to_s)
+ @app_xml_idx ||= index(Mime::Type[:XML].to_s)
end
def text_xml
@@ -137,6 +177,8 @@ module Mime
end
end
+ TYPES = {}
+
class << self
TRAILING_STAR_REGEXP = /(text|application)\/\*/
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
@@ -145,6 +187,18 @@ module Mime
@register_callbacks << block
end
+ def registered?(symbol)
+ TYPES.key? symbol
+ end
+
+ def [](symbol)
+ TYPES[symbol]
+ end
+
+ def add_type(symbol, type)
+ TYPES[symbol] = type
+ end
+
def lookup(string)
LOOKUP[string]
end
@@ -160,17 +214,18 @@ module Mime
end
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
+ new_mime = Type.new(string, symbol, mime_type_synonyms)
+ add_type symbol.upcase, new_mime
- new_mime = Mime.const_get(symbol.upcase)
SET << new_mime
- ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
- ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
+ ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = new_mime } unless skip_lookup
+ ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = new_mime }
@register_callbacks.each do |callback|
callback.call(new_mime)
end
+ new_mime
end
def parse(accept_header)
@@ -200,11 +255,11 @@ module Mime
parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
end
- # For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS,
- # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>.
+ # For an input of <tt>'text'</tt>, returns <tt>[Mime::Type[:JSON], Mime::Type[:XML], Mime::Type[:ICS],
+ # Mime::Type[:HTML], Mime::Type[:CSS], Mime::Type[:CSV], Mime::Type[:JS], Mime::Type[:YAML], Mime::Type[:TEXT]</tt>.
#
- # For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
- # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
+ # For an input of <tt>'application'</tt>, returns <tt>[Mime::Type[:HTML], Mime::Type[:JS],
+ # Mime::Type[:XML], Mime::Type[:YAML], Mime::Type[:ATOM], Mime::Type[:JSON], Mime::Type[:RSS], Mime::Type[:URL_ENCODED_FORM]</tt>.
def parse_data_with_trailing_star(input)
Mime::SET.select { |m| m =~ input }
end
@@ -216,8 +271,7 @@ module Mime
# Mime::Type.unregister(:mobile)
def unregister(symbol)
symbol = symbol.upcase
- mime = Mime.const_get(symbol)
- Mime.instance_eval { remove_const(symbol) }
+ mime = TYPES.delete symbol
SET.delete_if { |v| v.eql?(mime) }
LOOKUP.delete_if { |_,v| v.eql?(mime) }
@@ -243,7 +297,7 @@ module Mime
end
def ref
- to_sym || to_s
+ symbol || to_s
end
def ===(list)
@@ -255,24 +309,23 @@ module Mime
end
def ==(mime_type)
- return false if mime_type.blank?
+ return false unless mime_type
(@synonyms + [ self ]).any? do |synonym|
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
end
end
def =~(mime_type)
- return false if mime_type.blank?
+ return false unless mime_type
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
- (@synonyms + [ self ]).any? do |synonym|
- synonym.to_s =~ regexp
- end
+ @synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp
end
def html?
- @@html_types.include?(to_sym) || @string =~ /html/
+ symbol == :html || @string =~ /html/
end
+ def all?; false; end
private
@@ -290,6 +343,11 @@ module Mime
def respond_to_missing?(method, include_private = false) #:nodoc:
method.to_s.ends_with? '?'
end
+
+ class All < Type
+ def all?; true; end
+ def html?; true; end
+ end
end
class NullType
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 01a10c693b..04828f7c87 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -32,5 +32,5 @@ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonr
Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
Mime::Type.register "application/zip", :zip, [], %w(zip)
-# Create Mime::ALL but do not add it to the SET.
-Mime::ALL = Mime::Type.new("*/*", :all, [])
+# Create Mime::Type[:ALL] but do not add it to the SET.
+Mime::Type.add_type :ALL, Mime::Type::All.new("*/*", :all, [])
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 3c9f8cd9e4..e3c4392760 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -3,6 +3,20 @@ module ActionDispatch
module Parameters
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
+ DEFAULT_PARSERS = {
+ Mime::Type[:JSON] => lambda { |raw_post|
+ data = ActiveSupport::JSON.decode(raw_post)
+ data.is_a?(Hash) ? data : {:_json => data}
+ }
+ }
+
+ def self.included(klass)
+ class << klass
+ attr_accessor :parameter_parsers
+ end
+
+ klass.parameter_parsers = DEFAULT_PARSERS
+ end
# Returns both GET and POST \parameters in a single hash.
def parameters
params = get_header("action_dispatch.request.parameters")
@@ -31,6 +45,27 @@ module ActionDispatch
def path_parameters
get_header(PARAMETERS_KEY) || {}
end
+
+ private
+
+ def parse_formatted_parameters(parsers)
+ return yield if content_length.zero?
+
+ strategy = parsers.fetch(content_mime_type) { return yield }
+
+ begin
+ strategy.call(raw_post)
+ rescue => e # JSON or Ruby code block errors
+ my_logger = logger || ActiveSupport::Logger.new($stderr)
+ my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}"
+
+ raise ParamsParser::ParseError.new(e.message, e)
+ end
+ end
+
+ def params_parsers
+ ActionDispatch::Request.parameter_parsers
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index b2566c4820..bf20a33d36 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -348,8 +348,14 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
fetch_header("action_dispatch.request.request_parameters") do
- self.request_parameters = Request::Utils.normalize_encode_params(super || {})
+ pr = parse_formatted_parameters(params_parsers) do |params|
+ super || {}
+ end
+ self.request_parameters = Request::Utils.normalize_encode_params(pr)
end
+ rescue ParamsParser::ParseError # one of the parse strategies blew up
+ self.request_parameters = Request::Utils.normalize_encode_params(super || {})
+ raise
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:request, e)
end
@@ -378,6 +384,9 @@ module ActionDispatch
get_header("action_dispatch.logger".freeze)
end
+ def commit_flash
+ end
+
private
def check_method(name)
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 45ffacd6f5..85d9c3be00 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -32,6 +32,29 @@ module ActionDispatch # :nodoc:
# end
# end
class Response
+ class Header < DelegateClass(Hash) # :nodoc:
+ def initialize(response, header)
+ @response = response
+ super(header)
+ end
+
+ def []=(k,v)
+ if @response.committed?
+ raise ActionDispatch::IllegalStateError, 'header already sent'
+ end
+
+ super
+ end
+
+ def merge(other)
+ self.class.new @response, __getobj__.merge(other)
+ end
+
+ def to_hash
+ __getobj__.dup
+ end
+ end
+
# The request that the response is responding to.
attr_accessor :request
@@ -103,14 +126,22 @@ module ActionDispatch # :nodoc:
end
end
+ def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
+ header = merge_default_headers(header, default_headers)
+ new status, header, body
+ end
+
+ def self.merge_default_headers(original, default)
+ default.respond_to?(:merge) ? default.merge(original) : original
+ end
+
# The underlying body, as a streamable object.
attr_reader :stream
- def initialize(status = 200, header = {}, body = [], default_headers: self.class.default_headers)
+ def initialize(status = 200, header = {}, body = [])
super()
- header = merge_default_headers(header, default_headers)
- @header = header
+ @header = Header.new(self, header)
self.body, self.status = body, status
@@ -340,15 +371,12 @@ module ActionDispatch # :nodoc:
return if committed?
assign_default_content_type_and_charset!
handle_conditional_get!
+ handle_no_content!
end
def before_sending
end
- def merge_default_headers(original, default)
- default.respond_to?(:merge) ? default.merge(original) : original
- end
-
def build_buffer(response, body)
Buffer.new response, body
end
@@ -361,7 +389,7 @@ module ActionDispatch # :nodoc:
return if content_type
ct = parse_content_type
- set_content_type(ct.mime_type || Mime::HTML.to_s,
+ set_content_type(ct.mime_type || Mime::Type[:HTML].to_s,
ct.charset || self.class.default_charset)
end
@@ -401,10 +429,15 @@ module ActionDispatch # :nodoc:
end
end
+ def handle_no_content!
+ if NO_CONTENT_CODES.include?(@status)
+ @header.delete CONTENT_TYPE
+ @header.delete 'Content-Length'
+ end
+ end
+
def rack_response(status, header)
if NO_CONTENT_CODES.include?(status)
- header.delete CONTENT_TYPE
- header.delete 'Content-Length'
[status, header, []]
else
[status, header, RackBody.new(self)]
diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
index 47bf76bdbf..7063b44bb5 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 537c9b2f5c..306d2e674a 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
module ActionDispatch
module Journey # :nodoc:
class Format
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index b653e4eacd..7ed77352ae 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -396,17 +396,30 @@ module ActionDispatch
end
def write(headers)
- @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
- @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
+ headers[HTTP_HEADER] = make_set_cookie_header headers[HTTP_HEADER]
end
mattr_accessor :always_write_cookie
self.always_write_cookie = false
private
- def write_cookie?(cookie)
- request.ssl? || !cookie[:secure] || always_write_cookie
- end
+
+ def make_set_cookie_header(header)
+ header = @set_cookies.inject(header) { |m, (k, v)|
+ if write_cookie?(v)
+ ::Rack::Utils.add_cookie_to_header(m, k, v)
+ else
+ m
+ end
+ }
+ @delete_cookies.inject(header) { |m, (k, v)|
+ ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
+ }
+ end
+
+ def write_cookie?(cookie)
+ request.ssl? || !cookie[:secure] || always_write_cookie
+ end
end
class AbstractCookieJar # :nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 039efc3af8..5fd984cd07 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -61,7 +61,7 @@ module ActionDispatch
end
def traces
- appplication_trace_with_ids = []
+ application_trace_with_ids = []
framework_trace_with_ids = []
full_trace_with_ids = []
@@ -69,7 +69,7 @@ module ActionDispatch
trace_with_id = { id: idx, trace: trace }
if application_trace.include?(trace)
- appplication_trace_with_ids << trace_with_id
+ application_trace_with_ids << trace_with_id
else
framework_trace_with_ids << trace_with_id
end
@@ -78,7 +78,7 @@ module ActionDispatch
end
{
- "Application Trace" => appplication_trace_with_ids,
+ "Application Trace" => application_trace_with_ids,
"Framework Trace" => framework_trace_with_ids,
"Full Trace" => full_trace_with_ids
}
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index c482b1c5e7..c51dcd542a 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,25 +1,6 @@
require 'active_support/core_ext/hash/keys'
module ActionDispatch
- class Request
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
- # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
- # to put a new one.
- def flash
- flash = flash_hash
- return flash if flash
- self.flash = Flash::FlashHash.from_session_value(session["flash"])
- end
-
- def flash=(flash)
- set_header Flash::KEY, flash
- end
-
- def flash_hash # :nodoc:
- get_header Flash::KEY
- end
- end
-
# The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
# action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
@@ -57,6 +38,40 @@ module ActionDispatch
class Flash
KEY = 'action_dispatch.request.flash_hash'.freeze
+ module RequestMethods
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
+ # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
+ # to put a new one.
+ def flash
+ flash = flash_hash
+ return flash if flash
+ self.flash = Flash::FlashHash.from_session_value(session["flash"])
+ end
+
+ def flash=(flash)
+ set_header Flash::KEY, flash
+ end
+
+ def flash_hash # :nodoc:
+ get_header Flash::KEY
+ end
+
+ def commit_flash # :nodoc:
+ session = self.session || {}
+ flash_hash = self.flash_hash
+
+ if flash_hash && (flash_hash.present? || session.key?('flash'))
+ session["flash"] = flash_hash.to_session_value
+ self.flash = flash_hash.dup
+ end
+
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
+ session.key?('flash') && session['flash'].nil?
+ session.delete('flash')
+ end
+ end
+ end
+
class FlashNow #:nodoc:
attr_accessor :flash
@@ -268,26 +283,10 @@ module ActionDispatch
end
end
- def initialize(app)
- @app = app
- end
-
- def call(env)
- req = ActionDispatch::Request.new env
- @app.call(env)
- ensure
- session = Request::Session.find(req) || {}
- flash_hash = req.flash_hash
-
- if flash_hash && (flash_hash.present? || session.key?('flash'))
- session["flash"] = flash_hash.to_session_value
- req.flash = flash_hash.dup
- end
+ def self.new(app) app; end
+ end
- if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
- session.key?('flash') && session['flash'].nil?
- session.delete('flash')
- end
- end
+ class Request
+ prepend Flash::RequestMethods
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 9cde9c9b98..18af0a583a 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -18,48 +18,13 @@ module ActionDispatch
end
end
- DEFAULT_PARSERS = {
- Mime::JSON => lambda { |raw_post|
- data = ActiveSupport::JSON.decode(raw_post)
- data = {:_json => data} unless data.is_a?(Hash)
- Request::Utils.normalize_encode_params(data)
- }
- }
-
# Create a new +ParamsParser+ middleware instance.
#
# The +parsers+ argument can take Hash of parsers where key is identifying
# content mime type, and value is a lambda that is going to process data.
- def initialize(app, parsers = {})
- @app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
+ def self.new(app, parsers = {})
+ ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers)
+ app
end
-
- def call(env)
- request = Request.new(env)
-
- parse_formatted_parameters(request, @parsers) do |params|
- request.request_parameters = params
- end
-
- @app.call(env)
- end
-
- private
- def parse_formatted_parameters(request, parsers)
- return if request.content_length.zero?
-
- strategy = parsers.fetch(request.content_mime_type) { return nil }
-
- yield strategy.call(request.raw_post)
-
- rescue => e # JSON or Ruby code block errors
- logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
-
- raise ParseError.new(e.message, e)
- end
-
- def logger(request)
- request.logger || ActiveSupport::Logger.new($stderr)
- end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 02b6cfe727..0e636b8257 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -62,11 +62,7 @@ module ActionDispatch
# would set the session cookie to expire automatically 14 days after creation.
# Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
# <tt>:httponly</tt>.
- class CookieStore < Rack::Session::Abstract::Persisted
- include Compatibility
- include StaleSessionCheck
- include SessionObject
-
+ class CookieStore < AbstractStore
def initialize(app, options={})
super(app, options.merge!(:cookie_only => true))
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 8757c9ea7f..f3c6be864f 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,5 +1,3 @@
-# encoding: UTF-8
-
module ActionDispatch
# The routing module provides URL rewriting in native Ruby. It's a way to
# redirect incoming requests to controllers and actions. This replaces
@@ -148,6 +146,7 @@ module ActionDispatch
# get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /\d{5}(-\d{4})?/
# }
+ # end
#
# Constraints can include the 'ignorecase' and 'extended syntax' regular
# expression modifiers:
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 21b3b89d22..81fa10a613 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -12,7 +12,7 @@ module ActionDispatch
include Rails::Dom::Testing::Assertions
def html_document
- @html_document ||= if @response.content_type === Mime::XML
+ @html_document ||= if @response.content_type === Mime::Type[:XML]
Nokogiri::XML::Document.parse(@response.body)
else
Nokogiri::HTML::Document.parse(@response.body)
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 4dfd4f3f71..753cd2073b 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -354,7 +354,7 @@ module ActionDispatch
if xhr
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ headers['HTTP_ACCEPT'] ||= [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], 'text/xml', Mime::Type[:ALL]].join(', ')
end
# this modifies the passed request_env directly
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 6a31d6243f..4b79a90242 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -7,7 +7,7 @@ module ActionDispatch
# See Response for more information on controller response objects.
class TestResponse < Response
def self.from_response(response)
- new response.status, response.headers, response.body, default_headers: nil
+ new response.status, response.headers, response.body
end
# Was the response successful?
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index fc59bf19c4..3b36e43c0b 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -53,9 +53,9 @@ module AbstractController
collector.html
collector.text(:foo)
collector.js(:bar) { :baz }
- assert_equal [Mime::HTML, [], nil], collector.responses[0]
- assert_equal [Mime::TEXT, [:foo], nil], collector.responses[1]
- assert_equal [Mime::JS, [:bar]], collector.responses[2][0,2]
+ assert_equal [Mime::Type[:HTML], [], nil], collector.responses[0]
+ assert_equal [Mime::Type[:TEXT], [:foo], nil], collector.responses[1]
+ assert_equal [Mime::Type[:JS], [:bar]], collector.responses[2][0,2]
assert_equal :baz, collector.responses[2][2].call
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 3c498960e4..ef7aab72c6 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -104,7 +104,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
middleware.use ActionDispatch::DebugExceptions
middleware.use ActionDispatch::Callbacks
- middleware.use ActionDispatch::ParamsParser
middleware.use ActionDispatch::Cookies
middleware.use ActionDispatch::Flash
middleware.use Rack::Head
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index beeafc2e53..7dfeadceb0 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -65,7 +65,7 @@ class ActionPackAssertionsController < ActionController::Base
end
def render_text_with_custom_content_type
- render body: "Hello!", content_type: Mime::RSS
+ render body: "Hello!", content_type: Mime::Type[:RSS]
end
def session_stuffing
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index c5bbc479c9..4a86f1bad3 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
class OldContentTypeController < ActionController::Base
# :ported:
def render_content_type_from_body
- response.content_type = Mime::RSS
+ response.content_type = Mime::Type[:RSS]
render body: "hello world!"
end
@@ -14,7 +14,7 @@ class OldContentTypeController < ActionController::Base
# :ported:
def render_content_type_from_render
- render body: "hello world!", :content_type => Mime::RSS
+ render body: "hello world!", :content_type => Mime::Type[:RSS]
end
# :ported:
@@ -36,7 +36,7 @@ class OldContentTypeController < ActionController::Base
end
def render_change_for_builder
- response.content_type = Mime::HTML
+ response.content_type = Mime::Type[:HTML]
render :action => "render_default_for_builder"
end
@@ -45,7 +45,7 @@ class OldContentTypeController < ActionController::Base
format.html { render body: "hello world!" }
format.xml { render action: "render_default_content_types_for_respond_to" }
format.js { render body: "hello world!" }
- format.rss { render body: "hello world!", content_type: Mime::XML }
+ format.rss { render body: "hello world!", content_type: Mime::Type[:XML] }
end
end
end
@@ -64,68 +64,68 @@ class ContentTypeTest < ActionController::TestCase
def test_render_defaults
get :render_defaults
assert_equal "utf-8", @response.charset
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
end
def test_render_changed_charset_default
with_default_charset "utf-16" do
get :render_defaults
assert_equal "utf-16", @response.charset
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
end
end
# :ported:
def test_content_type_from_body
get :render_content_type_from_body
- assert_equal Mime::RSS, @response.content_type
+ assert_equal Mime::Type[:RSS], @response.content_type
assert_equal "utf-8", @response.charset
end
# :ported:
def test_content_type_from_render
get :render_content_type_from_render
- assert_equal Mime::RSS, @response.content_type
+ assert_equal Mime::Type[:RSS], @response.content_type
assert_equal "utf-8", @response.charset
end
# :ported:
def test_charset_from_body
get :render_charset_from_body
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
assert_equal "utf-16", @response.charset
end
# :ported:
def test_nil_charset_from_body
get :render_nil_charset_from_body
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
assert_equal "utf-8", @response.charset, @response.headers.inspect
end
def test_nil_default_for_erb
with_default_charset nil do
get :render_default_for_erb
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
assert_nil @response.charset, @response.headers.inspect
end
end
def test_default_for_erb
get :render_default_for_erb
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
assert_equal "utf-8", @response.charset
end
def test_default_for_builder
get :render_default_for_builder
- assert_equal Mime::XML, @response.content_type
+ assert_equal Mime::Type[:XML], @response.content_type
assert_equal "utf-8", @response.charset
end
def test_change_for_builder
get :render_change_for_builder
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
assert_equal "utf-8", @response.charset
end
@@ -144,24 +144,24 @@ class AcceptBasedContentTypeTest < ActionController::TestCase
tests OldContentTypeController
def test_render_default_content_types_for_respond_to
- @request.accept = Mime::HTML.to_s
+ @request.accept = Mime::Type[:HTML].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
- @request.accept = Mime::JS.to_s
+ @request.accept = Mime::Type[:JS].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::JS, @response.content_type
+ assert_equal Mime::Type[:JS], @response.content_type
end
def test_render_default_content_types_for_respond_to_with_template
- @request.accept = Mime::XML.to_s
+ @request.accept = Mime::Type[:XML].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::XML, @response.content_type
+ assert_equal Mime::Type[:XML], @response.content_type
end
def test_render_default_content_types_for_respond_to_with_overwrite
- @request.accept = Mime::RSS.to_s
+ @request.accept = Mime::Type[:RSS].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::XML, @response.content_type
+ assert_equal Mime::Type[:XML], @response.content_type
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index e9c19b7acf..4d1c23cbee 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -112,7 +112,7 @@ module ActionController
class TestController < ActionController::Base
include ActionController::Live
- attr_accessor :latch, :tc
+ attr_accessor :latch, :tc, :error_latch
def self.controller_path
'test'
@@ -204,6 +204,12 @@ module ActionController
end
def overfill_buffer_and_die
+ logger = ActionController::Base.logger || Logger.new($stdout)
+ response.stream.on_error do
+ logger.warn 'Error while streaming'
+ error_latch.count_down
+ end
+
# Write until the buffer is full. It doesn't expose that
# information directly, so we must hard-code its size:
10.times do
@@ -256,20 +262,12 @@ module ActionController
end
def test_set_cookie
- @controller = TestController.new
get :set_cookie
assert_equal({'hello' => 'world'}, @response.cookies)
assert_equal "hello world", @response.body
end
- def test_set_response!
- @controller.set_response!(@request)
- assert_kind_of(Live::Response, @controller.response)
- assert_equal @request, @controller.response.request
- end
-
def test_write_to_stream
- @controller = TestController.new
get :basic_stream
assert_equal "helloworld", @response.body
assert_equal 'text/event-stream', @response.headers['Content-Type']
@@ -281,10 +279,9 @@ module ActionController
@controller.latch = Concurrent::CountDownLatch.new
parts = ['hello', 'world']
- @controller.request = @request
- @controller.response = @response
+ get :blocking_stream
- t = Thread.new(@response) { |resp|
+ t = Thread.new(response) { |resp|
resp.await_commit
resp.stream.each do |part|
assert_equal parts.shift, part
@@ -294,38 +291,28 @@ module ActionController
end
}
- @controller.process :blocking_stream
-
assert t.join(3), 'timeout expired before the thread terminated'
end
def test_abort_with_full_buffer
@controller.latch = Concurrent::CountDownLatch.new
-
- @request.parameters[:format] = 'plain'
- @controller.request = @request
- @controller.response = @response
-
- got_error = Concurrent::CountDownLatch.new
- @response.stream.on_error do
- ActionController::Base.logger.warn 'Error while streaming'
- got_error.count_down
- end
-
- t = Thread.new(@response) { |resp|
- resp.await_commit
- _, _, body = resp.to_a
- body.each do
- @controller.latch.wait
- body.close
- break
- end
- }
+ @controller.error_latch = Concurrent::CountDownLatch.new
capture_log_output do |output|
- @controller.process :overfill_buffer_and_die
+ get :overfill_buffer_and_die, :format => 'plain'
+
+ t = Thread.new(response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do
+ @controller.latch.wait
+ body.close
+ break
+ end
+ }
+
t.join
- got_error.wait
+ @controller.error_latch.wait
assert_match 'Error while streaming', output.rewind && output.read
end
end
@@ -333,20 +320,18 @@ module ActionController
def test_ignore_client_disconnect
@controller.latch = Concurrent::CountDownLatch.new
- @controller.request = @request
- @controller.response = @response
+ capture_log_output do |output|
+ get :ignore_client_disconnect
- t = Thread.new(@response) { |resp|
- resp.await_commit
- _, _, body = resp.to_a
- body.each do
- body.close
- break
- end
- }
+ t = Thread.new(response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do
+ body.close
+ break
+ end
+ }
- capture_log_output do |output|
- @controller.process :ignore_client_disconnect
t.join
Timeout.timeout(3) do
@controller.latch.wait
@@ -364,11 +349,8 @@ module ActionController
end
def test_live_stream_default_header
- @controller.request = @request
- @controller.response = @response
- @controller.process :default_header
- _, headers, _ = @response.prepare!
- assert headers['Content-Type']
+ get :default_header
+ assert response.headers['Content-Type']
end
def test_render_text
@@ -437,13 +419,13 @@ module ActionController
def test_stale_without_etag
get :with_stale
- assert_equal 200, @response.status.to_i
+ assert_equal 200, response.status.to_i
end
def test_stale_with_etag
@request.if_none_match = Digest::MD5.hexdigest("123")
get :with_stale
- assert_equal 304, @response.status.to_i
+ assert_equal 304, response.status.to_i
end
end
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 0445a837ca..d9899fe01f 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -7,12 +7,12 @@ module ContentType
end
def set_on_response_obj
- response.content_type = Mime::RSS
+ response.content_type = Mime::Type[:RSS]
render body: "Hello world!"
end
def set_on_render
- render body: "Hello world!", content_type: Mime::RSS
+ render body: "Hello world!", content_type: Mime::Type[:RSS]
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 91b30ede6a..631ff7d02a 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -266,15 +266,17 @@ class RedirectTest < ActionController::TestCase
end
def test_redirect_to_nil
- assert_raise(ActionController::ActionControllerError) do
+ error = assert_raise(ActionController::ActionControllerError) do
get :redirect_to_nil
end
+ assert_equal "Cannot redirect to nil!", error.message
end
def test_redirect_to_params
- assert_raise(ActionController::ActionControllerError) do
+ error = assert_raise(ActionController::ActionControllerError) do
get :redirect_to_params
end
+ assert_equal "Cannot redirect to a parameter hash!", error.message
end
def test_redirect_to_with_block
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
index af50e11261..8891f6177f 100644
--- a/actionpack/test/controller/render_other_test.rb
+++ b/actionpack/test/controller/render_other_test.rb
@@ -12,7 +12,7 @@ class RenderOtherTest < ActionController::TestCase
def test_using_custom_render_option
ActionController.add_renderer :simon do |says, options|
- self.content_type = Mime::TEXT
+ self.content_type = Mime::Type[:TEXT]
self.response_body = "Simon says: #{says}"
end
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index 7a91577b17..094d3ea1d2 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -92,6 +92,6 @@ class RenderXmlTest < ActionController::TestCase
def test_should_use_implicit_content_type
get :implicit_content_type, format: 'atom'
- assert_equal Mime::ATOM, @response.content_type
+ assert_equal Mime::Type[:ATOM], @response.content_type
end
end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index c0ddcf7f50..c712c75c88 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -20,6 +20,47 @@ class SendFileController < ActionController::Base
send_file(file_path, options)
end
+ def test_send_file_headers_bang
+ options = {
+ :type => Mime::Type[:PNG],
+ :disposition => 'disposition',
+ :filename => 'filename'
+ }
+
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_disposition_as_a_symbol
+ options = {
+ :type => Mime::Type[:PNG],
+ :disposition => :disposition,
+ :filename => 'filename'
+ }
+
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_mime_lookup_with_symbol
+ options = { :type => :png }
+
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_bad_symbol
+ options = { :type => :this_type_is_not_registered }
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_nil_content_type
+ options = { :type => nil }
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_guess_type_from_extension
+ options = { :filename => params[:filename] }
+ send_data "foo", options
+ end
+
def data
send_data(file_data, options)
end
@@ -88,62 +129,39 @@ class SendFileTest < ActionController::TestCase
# Test that send_file_headers! is setting the correct HTTP headers.
def test_send_file_headers_bang
- options = {
- :type => Mime::PNG,
- :disposition => 'disposition',
- :filename => 'filename'
- }
-
# Do it a few times: the resulting headers should be identical
# no matter how many times you send with the same options.
# Test resolving Ticket #458.
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
- @controller.send(:send_file_headers!, options)
- @controller.send(:send_file_headers!, options)
-
- h = @controller.headers
- assert_equal 'image/png', @controller.content_type
- assert_equal 'disposition; filename="filename"', h['Content-Disposition']
- assert_equal 'binary', h['Content-Transfer-Encoding']
+ 5.times do
+ get :test_send_file_headers_bang
- # test overriding Cache-Control: no-cache header to fix IE open/save dialog
- @controller.send(:send_file_headers!, options)
- @controller.response.prepare!
- assert_equal 'private', h['Cache-Control']
+ assert_equal 'image/png', response.content_type
+ assert_equal 'disposition; filename="filename"', response.get_header('Content-Disposition')
+ assert_equal 'binary', response.get_header('Content-Transfer-Encoding')
+ assert_equal 'private', response.get_header('Cache-Control')
+ end
end
def test_send_file_headers_with_disposition_as_a_symbol
- options = {
- :type => Mime::PNG,
- :disposition => :disposition,
- :filename => 'filename'
- }
+ get :test_send_file_headers_with_disposition_as_a_symbol
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
- assert_equal 'disposition; filename="filename"', @controller.headers['Content-Disposition']
+ assert_equal 'disposition; filename="filename"', response.get_header('Content-Disposition')
end
def test_send_file_headers_with_mime_lookup_with_symbol
- options = {
- :type => :png
- }
-
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
-
- assert_equal 'image/png', @controller.content_type
+ get __method__
+ assert_equal 'image/png', response.content_type
end
def test_send_file_headers_with_bad_symbol
- options = {
- :type => :this_type_is_not_registered
- }
+ error = assert_raise(ArgumentError) { get __method__ }
+ assert_equal "Unknown MIME type this_type_is_not_registered", error.message
+ end
- @controller.headers = {}
- assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) }
+ def test_send_file_headers_with_nil_content_type
+ error = assert_raise(ArgumentError) { get __method__ }
+ assert_equal ":type option required", error.message
end
def test_send_file_headers_guess_type_from_extension
@@ -158,10 +176,8 @@ class SendFileTest < ActionController::TestCase
'file.unk' => 'application/octet-stream',
'zip' => 'application/octet-stream'
}.each do |filename,expected_type|
- options = { :filename => filename }
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
- assert_equal expected_type, @controller.content_type
+ get __method__, params: { filename: filename }
+ assert_equal expected_type, response.content_type
end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 06bf9dec74..40c97abd35 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -974,6 +974,11 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
headers.delete params[:header]
head :ok, 'C' => '3'
end
+
+ # Render a head response, but don't touch default headers
+ def leave_alone
+ head :ok
+ end
end
def before_setup
@@ -999,9 +1004,13 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
end
test "response contains default headers" do
+ get :leave_alone
+
# Response headers start out with the defaults
- assert_equal @defaults, response.headers
+ assert_equal @defaults.merge('Content-Type' => 'text/html'), response.headers
+ end
+ test "response deletes a default header" do
get :remove_header, params: { header: 'A' }
assert_response :ok
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index b26f037c36..2aee914a24 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -65,7 +65,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_register_and_use_json_simple
with_test_route_set do
- with_params_parsers Mime::JSON => Proc.new { |data| ActiveSupport::JSON.decode(data)['request'].with_indifferent_access } do
+ with_params_parsers Mime::Type[:JSON] => Proc.new { |data| ActiveSupport::JSON.decode(data)['request'].with_indifferent_access } do
post "/",
params: '{"request":{"summary":"content...","title":"JSON"}}',
headers: { 'CONTENT_TYPE' => 'application/json' }
@@ -97,24 +97,28 @@ class WebServiceTest < ActionDispatch::IntegrationTest
end
def test_parsing_json_doesnot_rescue_exception
- with_test_route_set do
- with_params_parsers Mime::JSON => Proc.new { |data| raise Interrupt } do
- assert_raises(Interrupt) do
- post "/",
- params: '{"title":"JSON"}}',
- headers: { 'CONTENT_TYPE' => 'application/json' }
- end
+ req = Class.new(ActionDispatch::Request) do
+ def params_parsers
+ { Mime::Type[:JSON] => Proc.new { |data| raise Interrupt } }
end
+
+ def content_length; get_header('rack.input').length; end
+ end.new({ 'rack.input' => StringIO.new('{"title":"JSON"}}'), 'CONTENT_TYPE' => 'application/json' })
+
+ assert_raises(Interrupt) do
+ req.request_parameters
end
end
private
def with_params_parsers(parsers = {})
old_session = @integration_session
- @app = ActionDispatch::ParamsParser.new(app.routes, parsers)
+ original_parsers = ActionDispatch::Request.parameter_parsers
+ ActionDispatch::Request.parameter_parsers = original_parsers.merge parsers
reset!
yield
ensure
+ ActionDispatch::Request.parameter_parsers = original_parsers
@integration_session = old_session
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 3017a9c2d6..68083ed747 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -13,76 +13,75 @@ class MimeTypeTest < ActiveSupport::TestCase
test "unregister" do
begin
Mime::Type.register("text/x-mobile", :mobile)
- assert defined?(Mime::MOBILE)
- assert_equal Mime::MOBILE, Mime::LOOKUP['text/x-mobile']
- assert_equal Mime::MOBILE, Mime::EXTENSION_LOOKUP['mobile']
+ assert Mime::Type.registered?(:MOBILE)
+ assert_equal Mime::Type[:MOBILE], Mime::LOOKUP['text/x-mobile']
+ assert_equal Mime::Type[:MOBILE], Mime::EXTENSION_LOOKUP['mobile']
Mime::Type.unregister(:mobile)
- assert !defined?(Mime::MOBILE), "Mime::MOBILE should not be defined"
+ assert !Mime::Type.registered?(:MOBILE), "Mime::MOBILE should not be defined"
assert !Mime::LOOKUP.has_key?('text/x-mobile'), "Mime::LOOKUP should not have key ['text/x-mobile]"
assert !Mime::EXTENSION_LOOKUP.has_key?('mobile'), "Mime::EXTENSION_LOOKUP should not have key ['mobile]"
ensure
- Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
end
end
test "parse text with trailing star at the beginning" do
accept = "text/*, text/html, application/json, multipart/form-data"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
+ expect = [Mime::Type[:HTML], Mime::Type[:TEXT], Mime::Type[:JS], Mime::Type[:CSS], Mime::Type[:ICS], Mime::Type[:CSV], Mime::Type[:VCF], Mime::Type[:XML], Mime::Type[:YAML], Mime::Type[:JSON], Mime::Type[:MULTIPART_FORM]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star in the end" do
accept = "text/html, application/json, multipart/form-data, text/*"
- expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML]
+ expect = [Mime::Type[:HTML], Mime::Type[:JSON], Mime::Type[:MULTIPART_FORM], Mime::Type[:TEXT], Mime::Type[:JS], Mime::Type[:CSS], Mime::Type[:ICS], Mime::Type[:CSV], Mime::Type[:VCF], Mime::Type[:XML], Mime::Type[:YAML]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star" do
accept = "text/*"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON]
+ expect = [Mime::Type[:HTML], Mime::Type[:TEXT], Mime::Type[:JS], Mime::Type[:CSS], Mime::Type[:ICS], Mime::Type[:CSV], Mime::Type[:VCF], Mime::Type[:XML], Mime::Type[:YAML], Mime::Type[:JSON]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse application with trailing star" do
accept = "application/*"
- expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF, Mime::ZIP]
+ expect = [Mime::Type[:HTML], Mime::Type[:JS], Mime::Type[:XML], Mime::Type[:RSS], Mime::Type[:ATOM], Mime::Type[:YAML], Mime::Type[:URL_ENCODED_FORM], Mime::Type[:JSON], Mime::Type[:PDF], Mime::Type[:ZIP]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse without q" do
accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*"
- expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL]
+ expect = [Mime::Type[:HTML], Mime::Type[:XML], Mime::Type[:YAML], Mime::Type[:PNG], Mime::Type[:TEXT], Mime::Type[:PDF], Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept)
end
test "parse with q" do
accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,application/pdf,*/*; q=0.2"
- expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PDF, Mime::TEXT, Mime::YAML, Mime::ALL]
+ expect = [Mime::Type[:HTML], Mime::Type[:XML], Mime::Type[:PNG], Mime::Type[:PDF], Mime::Type[:TEXT], Mime::Type[:YAML], Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept)
end
test "parse single media range with q" do
accept = "text/html;q=0.9"
- expect = [Mime::HTML]
+ expect = [Mime::Type[:HTML]]
assert_equal expect, Mime::Type.parse(accept)
end
test "parse arbitrary media type parameters" do
accept = 'multipart/form-data; boundary="simple boundary"'
- expect = [Mime::MULTIPART_FORM]
+ expect = [Mime::Type[:MULTIPART_FORM]]
assert_equal expect, Mime::Type.parse(accept)
end
# Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP)
test "parse broken acceptlines" do
accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
- expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL]
+ expect = [Mime::Type[:HTML], Mime::Type[:XML], "image/*", Mime::Type[:TEXT], Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
@@ -90,16 +89,15 @@ class MimeTypeTest < ActiveSupport::TestCase
# (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1)
test "parse other broken acceptlines" do
accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*"
- expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::ALL]
+ expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
test "custom type" do
begin
- Mime::Type.register("image/foo", :foo)
- assert_nothing_raised do
- assert_equal Mime::FOO, Mime::SET.last
- end
+ type = Mime::Type.register("image/foo", :foo)
+ assert_equal Mime::Type[:FOO], type
+ assert Mime::Type.registered?(:FOO)
ensure
Mime::Type.unregister(:FOO)
end
@@ -109,7 +107,7 @@ class MimeTypeTest < ActiveSupport::TestCase
begin
Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"]
%w[text/foobar text/foo text/bar].each do |type|
- assert_equal Mime::FOOBAR, type
+ assert_equal Mime::Type[:FOOBAR], type
end
ensure
Mime::Type.unregister(:FOOBAR)
@@ -124,7 +122,7 @@ class MimeTypeTest < ActiveSupport::TestCase
end
Mime::Type.register("text/foo", :foo)
- assert_equal [Mime::FOO], registered_mimes
+ assert_equal [Mime::Type[:FOO]], registered_mimes
ensure
Mime::Type.unregister(:FOO)
end
@@ -134,7 +132,7 @@ class MimeTypeTest < ActiveSupport::TestCase
begin
Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
%w[foobar foo bar].each do |extension|
- assert_equal Mime::FOOBAR, Mime::EXTENSION_LOOKUP[extension]
+ assert_equal Mime::Type[:FOOBAR], Mime::EXTENSION_LOOKUP[extension]
end
ensure
Mime::Type.unregister(:FOOBAR)
@@ -144,60 +142,71 @@ class MimeTypeTest < ActiveSupport::TestCase
test "register alias" do
begin
Mime::Type.register_alias "application/xhtml+xml", :foobar
- assert_equal Mime::HTML, Mime::EXTENSION_LOOKUP['foobar']
+ assert_equal Mime::Type[:HTML], Mime::EXTENSION_LOOKUP['foobar']
ensure
Mime::Type.unregister(:FOOBAR)
end
end
test "type should be equal to symbol" do
- assert_equal Mime::HTML, 'application/xhtml+xml'
- assert_equal Mime::HTML, :html
+ assert_equal Mime::Type[:HTML], 'application/xhtml+xml'
+ assert_equal Mime::Type[:HTML], :html
end
test "type convenience methods" do
- # Don't test Mime::ALL, since it Mime::ALL#html? == true
+ # Don't test Mime::Type[:ALL], since it Mime::Type[:ALL].html? == true
types = Mime::SET.symbols.uniq - [:all, :iphone]
- # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- types.delete_if { |type| !Mime.const_defined?(type.upcase) }
-
+ # Remove custom Mime::Type instances set in other tests, like Mime::Type[:GIF] and Mime::Type[:IPHONE]
+ types.delete_if { |type| !Mime::Type.registered?(type.upcase) }
types.each do |type|
- mime = Mime.const_get(type.upcase)
+ mime = Mime::Type[type.upcase]
assert mime.respond_to?("#{type}?"), "#{mime.inspect} does not respond to #{type}?"
- assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?"
+ assert_equal type, mime.symbol, "#{mime.inspect} is not #{type}?"
invalid_types = types - [type]
- invalid_types.delete(:html) if Mime::Type.html_types.include?(type)
- invalid_types.each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" }
+ invalid_types.delete(:html)
+ invalid_types.each { |other_type|
+ assert_not_equal mime.symbol, other_type, "#{mime.inspect} is #{other_type}?"
+ }
end
end
test "mime all is html" do
- assert Mime::ALL.all?, "Mime::ALL is not all?"
- assert Mime::ALL.html?, "Mime::ALL is not html?"
+ assert Mime::Type[:ALL].all?, "Mime::ALL is not all?"
+ assert Mime::Type[:ALL].html?, "Mime::ALL is not html?"
+ end
+
+ test "deprecated lookup" do
+ assert_deprecated do
+ assert Mime::ALL.all?, "Mime::ALL is not all?"
+ end
+ end
+
+ test "deprecated const_defined?" do
+ assert_deprecated { Mime.const_defined?(:ALL) }
end
test "verifiable mime types" do
all_types = Mime::SET.symbols
all_types.uniq!
- # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- all_types.delete_if { |type| !Mime.const_defined?(type.upcase) }
+ # Remove custom Mime::Type instances set in other tests, like Mime::Type[:GIF] and Mime::Type[:IPHONE]
+ all_types.delete_if { |type| !Mime::Type.registered?(type.upcase) }
end
test "references gives preference to symbols before strings" do
- assert_equal :html, Mime::HTML.ref
+ assert_equal :html, Mime::Type[:HTML].ref
another = Mime::Type.lookup("foo/bar")
assert_nil another.to_sym
assert_equal "foo/bar", another.ref
end
test "regexp matcher" do
- assert Mime::JS =~ "text/javascript"
- assert Mime::JS =~ "application/javascript"
- assert Mime::JS !~ "text/html"
- assert !(Mime::JS !~ "text/javascript")
- assert !(Mime::JS !~ "application/javascript")
- assert Mime::HTML =~ 'application/xhtml+xml'
+ assert Mime::Type[:JS] =~ "text/javascript"
+ assert Mime::Type[:JS] =~ "application/javascript"
+ assert Mime::Type[:JS] !~ "text/html"
+ assert !(Mime::Type[:JS] !~ "text/javascript")
+ assert !(Mime::Type[:JS] !~ "application/javascript")
+ assert Mime::Type[:HTML] =~ 'application/xhtml+xml'
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 258d097b7c..40866595ed 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -750,33 +750,33 @@ class RequestFormat < BaseRequestTest
test "xml format" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :xml}) do
- assert_equal Mime::XML, request.format
+ assert_equal Mime::Type[:XML], request.format
end
end
test "xhtml format" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :xhtml}) do
- assert_equal Mime::HTML, request.format
+ assert_equal Mime::Type[:HTML], request.format
end
end
test "txt format" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :txt}) do
- assert_equal Mime::TEXT, request.format
+ assert_equal Mime::Type[:TEXT], request.format
end
end
test "XMLHttpRequest" do
request = stub_request(
'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
- 'HTTP_ACCEPT' => [Mime::JS, Mime::HTML, Mime::XML, "text/xml", Mime::ALL].join(",")
+ 'HTTP_ACCEPT' => [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], "text/xml", Mime::Type[:ALL]].join(",")
)
assert_called(request, :parameters, times: 1, returns: {}) do
assert request.xhr?
- assert_equal Mime::JS, request.format
+ assert_equal Mime::Type[:JS], request.format
end
end
@@ -796,29 +796,29 @@ class RequestFormat < BaseRequestTest
test "formats text/html with accept header" do
request = stub_request 'HTTP_ACCEPT' => 'text/html'
- assert_equal [Mime::HTML], request.formats
+ assert_equal [Mime::Type[:HTML]], request.formats
end
test "formats blank with accept header" do
request = stub_request 'HTTP_ACCEPT' => ''
- assert_equal [Mime::HTML], request.formats
+ assert_equal [Mime::Type[:HTML]], request.formats
end
test "formats XMLHttpRequest with accept header" do
request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
- assert_equal [Mime::JS], request.formats
+ assert_equal [Mime::Type[:JS]], request.formats
end
test "formats application/xml with accept header" do
request = stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest")
- assert_equal [Mime::XML], request.formats
+ assert_equal [Mime::Type[:XML]], request.formats
end
test "formats format:text with accept header" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :txt}) do
- assert_equal [Mime::TEXT], request.formats
+ assert_equal [Mime::Type[:TEXT]], request.formats
end
end
@@ -848,7 +848,7 @@ class RequestFormat < BaseRequestTest
test "formats with xhr request" do
request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [Mime::JS], request.formats
+ assert_equal [Mime::Type[:JS]], request.formats
end
end
@@ -859,35 +859,35 @@ class RequestFormat < BaseRequestTest
begin
request = stub_request 'HTTP_ACCEPT' => 'application/xml'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'koz-asked/something-crazy'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => '*/*;q=0.1'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'application/jxw'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'application/xml',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::JS ], request.formats
+ assert_equal [ Mime::Type[:JS] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'application/xml',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
assert_called(request, :parameters, times: 2, returns: {format: :json}) do
- assert_equal [ Mime::JSON ], request.formats
+ assert_equal [ Mime::Type[:JSON] ], request.formats
end
ensure
ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header
@@ -897,7 +897,7 @@ end
class RequestMimeType < BaseRequestTest
test "content type" do
- assert_equal Mime::HTML, stub_request('CONTENT_TYPE' => 'text/html').content_mime_type
+ assert_equal Mime::Type[:HTML], stub_request('CONTENT_TYPE' => 'text/html').content_mime_type
end
test "no content type" do
@@ -905,11 +905,11 @@ class RequestMimeType < BaseRequestTest
end
test "content type is XML" do
- assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml').content_mime_type
+ assert_equal Mime::Type[:XML], stub_request('CONTENT_TYPE' => 'application/xml').content_mime_type
end
test "content type with charset" do
- assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8').content_mime_type
+ assert_equal Mime::Type[:XML], stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8').content_mime_type
end
test "user agent" do
@@ -922,9 +922,9 @@ class RequestMimeType < BaseRequestTest
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
)
- assert_equal nil, request.negotiate_mime([Mime::XML, Mime::JSON])
- assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::HTML])
- assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::ALL])
+ assert_equal nil, request.negotiate_mime([Mime::Type[:XML], Mime::Type[:JSON]])
+ assert_equal Mime::Type[:HTML], request.negotiate_mime([Mime::Type[:XML], Mime::Type[:HTML]])
+ assert_equal Mime::Type[:HTML], request.negotiate_mime([Mime::Type[:XML], Mime::Type[:ALL]])
end
test "negotiate_mime with content_type" do
@@ -933,7 +933,7 @@ class RequestMimeType < BaseRequestTest
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
)
- assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
+ assert_equal Mime::Type[:XML], request.negotiate_mime([Mime::Type[:XML], Mime::Type[:CSV]])
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 5d74424de7..85cdcda655 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -4,7 +4,7 @@ require 'rack/content_length'
class ResponseTest < ActiveSupport::TestCase
def setup
- @response = ActionDispatch::Response.new
+ @response = ActionDispatch::Response.create
end
def test_can_wait_until_commit
@@ -149,12 +149,19 @@ class ResponseTest < ActiveSupport::TestCase
status, headers, body = @response.to_a
assert_equal "user_name=david; path=/", headers["Set-Cookie"]
assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+ test "multiple cookies" do
+ @response.set_cookie("user_name", :value => "david", :path => "/")
@response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5))
status, headers, body = @response.to_a
assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"]
assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies)
+ end
+ test "delete cookies" do
+ @response.set_cookie("user_name", :value => "david", :path => "/")
+ @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5))
@response.delete_cookie("login")
status, headers, body = @response.to_a
assert_equal({"user_name" => "david", "login" => nil}, @response.cookies)
@@ -178,13 +185,13 @@ class ResponseTest < ActiveSupport::TestCase
test "read charset and content type" do
resp = ActionDispatch::Response.new.tap { |response|
response.charset = 'utf-16'
- response.content_type = Mime::XML
+ response.content_type = Mime::Type[:XML]
response.body = 'Hello'
}
resp.to_a
assert_equal('utf-16', resp.charset)
- assert_equal(Mime::XML, resp.content_type)
+ assert_equal(Mime::Type[:XML], resp.content_type)
assert_equal('application/xml; charset=utf-16', resp.headers['Content-Type'])
end
@@ -210,7 +217,7 @@ class ResponseTest < ActiveSupport::TestCase
'X-Content-Type-Options' => 'nosniff',
'X-XSS-Protection' => '1;'
}
- resp = ActionDispatch::Response.new.tap { |response|
+ resp = ActionDispatch::Response.create.tap { |response|
response.body = 'Hello'
}
resp.to_a
@@ -229,7 +236,7 @@ class ResponseTest < ActiveSupport::TestCase
ActionDispatch::Response.default_headers = {
'X-XX-XXXX' => 'Here is my phone number'
}
- resp = ActionDispatch::Response.new.tap { |response|
+ resp = ActionDispatch::Response.create.tap { |response|
response.body = 'Hello'
}
resp.to_a
@@ -317,7 +324,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
@app = lambda { |env|
ActionDispatch::Response.new.tap { |resp|
resp.charset = 'utf-16'
- resp.content_type = Mime::XML
+ resp.content_type = Mime::Type[:XML]
resp.body = 'Hello'
}.to_a
}
@@ -326,7 +333,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('utf-16', @response.charset)
- assert_equal(Mime::XML, @response.content_type)
+ assert_equal(Mime::Type[:XML], @response.content_type)
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
@@ -342,7 +349,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('utf-16', @response.charset)
- assert_equal(Mime::XML, @response.content_type)
+ assert_equal(Mime::Type[:XML], @response.content_type)
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index d512dae4e7..15d51e5d6c 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -1,4 +1,3 @@
-# encoding: UTF-8
require 'abstract_unit'
module ActionDispatch
@@ -498,17 +497,6 @@ module ActionDispatch
private
- def add_routes router, paths, anchor = true
- paths.each do |path|
- if String === path
- path = Path::Pattern.from_string path
- else
- path
- end
- add_route @app, path, {}, [], {}, {}
- end
- end
-
def rails_env env, klass = ActionDispatch::Request
klass.new(rack_env(env))
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 9ea250643a..82a88952c3 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Add a `hidden_field` on the `collection_radio_buttons` to avoid raising a error
+ when the only input on the form is the `collection_radio_buttons`.
+
+ *Mauro George*
+
* `url_for` does not modify its arguments when generating polymorphic URLs.
*Bernerd Schaefer*
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 6e8c7f8203..7716955fd9 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,9 +1,9 @@
-require 'thread_safe'
+require 'concurrent'
require 'action_view/path_set'
module ActionView
class DependencyTracker # :nodoc:
- @trackers = ThreadSafe::Cache.new
+ @trackers = Concurrent::Map.new
def self.find_dependencies(name, template, view_paths = nil)
tracker = @trackers[template.handler]
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 9a8a4feb2e..12e9723a02 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,11 +1,11 @@
-require 'thread_safe'
+require 'concurrent'
require 'action_view/dependency_tracker'
require 'monitor'
module ActionView
class Digestor
cattr_reader(:cache)
- @@cache = ThreadSafe::Cache.new
+ @@cache = Concurrent::Map.new
@@digest_monitor = Monitor.new
class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
@@ -28,7 +28,7 @@ module ActionView
cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.')
# this is a correctly done double-checked locking idiom
- # (ThreadSafe::Cache's lookups have volatile semantics)
+ # (Concurrent::Map's lookups have volatile semantics)
@@cache[cache_key] || @@digest_monitor.synchronize do
@@cache.fetch(cache_key) do # re-check under lock
compute_and_store_digest(cache_key, options)
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index ebf5ea5b77..2a367b85af 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -849,8 +849,8 @@ module ActionView
# file_field(:user, :avatar)
# # => <input type="file" id="user_avatar" name="user[avatar]" />
#
- # file_field(:post, :image, :multiple => true)
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
+ # file_field(:post, :image, multiple: true)
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
#
# file_field(:post, :attached, accept: 'text/html')
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
@@ -1038,7 +1038,7 @@ module ActionView
# date_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="date" />
#
- # The default value is generated by trying to call "to_date"
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
# on the object's value, which makes it behave as expected for instances
# of DateTime and ActiveSupport::TimeWithZone. You can still override that
# by passing the "value" option explicitly, e.g.
@@ -1617,7 +1617,14 @@ module ActionView
@auto_index
end
- record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
+ record_name = if index
+ "#{object_name}[#{index}][#{record_name}]"
+ elsif record_name.to_s.end_with?('[]')
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
+ "#{object_name}#{record_name}"
+ else
+ "#{object_name}[#{record_name}]"
+ end
fields_options[:child_index] = index
@template.fields_for(record_name, record_object, fields_options, &block)
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 728e633bbf..430051379d 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -548,7 +548,7 @@ module ActionView
end
# Returns a string of option tags for pretty much any time zone in the
- # world. Supply a ActiveSupport::TimeZone name as +selected+ to have it
+ # world. Supply an ActiveSupport::TimeZone name as +selected+ to have it
# marked as the selected option tag. You can also supply an array of
# ActiveSupport::TimeZone objects as +priority_zones+, so that they will
# be listed above the rest of the (long) list. (You can use
@@ -556,7 +556,7 @@ module ActionView
# of the US time zones, or a Regexp to select the zones of your choice)
#
# The +selected+ parameter must be either +nil+, or a string that names
- # a ActiveSupport::TimeZone.
+ # an ActiveSupport::TimeZone.
#
# By default, +model+ is the ActiveSupport::TimeZone constant (which can
# be obtained in Active Record as a value object). The only requirement
@@ -644,6 +644,24 @@ module ActionView
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
# b.label(:"data-value" => b.value) { b.radio_button + b.text }
# end
+ #
+ # ==== Gotcha
+ #
+ # The HTML specification says when nothing is select on a collection of radio buttons
+ # web browsers do not send any value to server.
+ # Unfortunately this introduces a gotcha:
+ # if a +User+ model has a +category_id+ field, and in the form none category is selected no +category_id+ parameter is sent. So,
+ # any strong parameters idiom like
+ #
+ # params.require(:user).permit(...)
+ #
+ # will raise an error since no +{user: ...}+ will be present.
+ #
+ # To prevent this the helper generates an auxiliary hidden field before
+ # every collection of radio buttons. The hidden field has the same name as collection radio button and blank value.
+ #
+ # In case if you don't want the helper to generate this hidden field you can specify
+ # <tt>include_hidden: false</tt> option.
def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
end
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 13effa592d..d7182d1fac 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -1,4 +1,3 @@
-
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/string/output_safety'
require 'active_support/number_helper'
@@ -140,7 +139,7 @@ module ActionView
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
# number_to_percentage("98a") # => 98a%
- # number_to_percentage(100, format: "%n %") # => 100 %
+ # number_to_percentage(100, format: "%n %") # => 100.000 %
#
# number_to_percentage("98a", raise: true) # => InvalidNumberError
def number_to_percentage(number, options = {})
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 1765fa6558..3256d44e18 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -9,29 +9,13 @@ module ActionView
class CheckBoxBuilder < Builder # :nodoc:
def check_box(extra_html_options={})
html_options = extra_html_options.merge(@input_html_options)
+ html_options[:multiple] = true
@template_object.check_box(@object_name, @method_name, html_options, @value, nil)
end
end
def render(&block)
- rendered_collection = render_collection do |item, value, text, default_html_options|
- default_html_options[:multiple] = true
- builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options)
-
- if block_given?
- @template_object.capture(builder, &block)
- else
- render_component(builder)
- end
- end
-
- # Append a hidden field to make sure something will be sent back to the
- # server if all check boxes are unchecked.
- if @options.fetch(:include_hidden, true)
- rendered_collection + hidden_field
- else
- rendered_collection
- end
+ render_collection_for(CheckBoxBuilder, &block)
end
private
@@ -39,11 +23,6 @@ module ActionView
def render_component(builder)
builder.check_box + builder.label
end
-
- def hidden_field
- hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]"
- @template_object.hidden_field_tag(hidden_name, "", id: nil)
- 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 8050638363..fea4c8d4ec 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -79,6 +79,32 @@ module ActionView
yield item, value, text, default_html_options.merge(additional_html_options)
end.join.html_safe
end
+
+ def render_collection_for(builder_class, &block) #:nodoc:
+ options = @options.stringify_keys
+ rendered_collection = render_collection do |item, value, text, default_html_options|
+ builder = instantiate_builder(builder_class, item, value, text, default_html_options)
+
+ if block_given?
+ @template_object.capture(builder, &block)
+ else
+ render_component(builder)
+ end
+ end
+
+ # 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
+ else
+ rendered_collection
+ end
+ end
+
+ def hidden_field #:nodoc:
+ hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]"
+ @template_object.hidden_field_tag(hidden_name, "", id: nil)
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
index 20be34c1f2..21aaf122f8 100644
--- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -14,15 +14,7 @@ module ActionView
end
def render(&block)
- render_collection do |item, value, text, default_html_options|
- builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options)
-
- if block_given?
- @template_object.capture(builder, &block)
- else
- render_component(builder)
- end
- end
+ render_collection_for(RadioButtonBuilder, &block)
end
private
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 0615bd2e0d..dde1ef22ac 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -88,7 +88,14 @@ module ActionView
raise e if raise_error
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
- content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
+ title = "translation missing: #{keys.join('.')}"
+
+ interpolations = options.except(:default)
+ if interpolations.any?
+ title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ')
+ end
+
+ content_tag('span', keys.last.to_s.titleize, class: 'translation_missing', title: title)
end
end
alias :t :translate
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index fba9a44cb1..ec6edfaaa3 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -1,4 +1,4 @@
-require 'thread_safe'
+require 'concurrent'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/module/attribute_accessors'
require 'action_view/template/resolver'
@@ -20,7 +20,7 @@ module ActionView
mattr_accessor :registered_details
self.registered_details = []
- def self.register_detail(name, options = {}, &block)
+ def self.register_detail(name, &block)
self.registered_details << name
initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
@@ -55,14 +55,14 @@ module ActionView
end
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
register_detail(:variants) { [] }
- register_detail(:handlers){ Template::Handlers.extensions }
+ register_detail(:handlers) { Template::Handlers.extensions }
class DetailsKey #:nodoc:
alias :eql? :equal?
alias :object_hash :hash
attr_reader :hash
- @details_keys = ThreadSafe::Cache.new
+ @details_keys = Concurrent::Map.new
def self.get(details)
if details[:formats]
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 1f9e960488..39c8658ffe 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,5 @@
require 'action_view/renderer/partial_renderer/collection_caching'
-require 'thread_safe'
+require 'concurrent'
module ActionView
class PartialIteration
@@ -283,8 +283,8 @@ module ActionView
class PartialRenderer < AbstractRenderer
include CollectionCaching
- PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
- h[k] = ThreadSafe::Cache.new
+ PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
+ h[k] = Concurrent::Map.new
end
def initialize(*)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 28967f40a6..7859c58b43 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -3,7 +3,7 @@ require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
require "action_view/template"
require "thread"
-require "thread_safe"
+require "concurrent"
module ActionView
# = Action View Resolver
@@ -35,7 +35,7 @@ module ActionView
# Threadsafe template cache
class Cache #:nodoc:
- class SmallCache < ThreadSafe::Cache
+ class SmallCache < Concurrent::Map
def initialize(options = {})
super(options.merge(:initial_capacity => 2))
end
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 12870acaa6..2354e91822 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -151,7 +151,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
middleware.use ActionDispatch::DebugExceptions
middleware.use ActionDispatch::Callbacks
- middleware.use ActionDispatch::ParamsParser
middleware.use ActionDispatch::Cookies
middleware.use ActionDispatch::Flash
middleware.use Rack::Head
diff --git a/actionview/test/actionpack/controller/capture_test.rb b/actionview/test/actionpack/controller/capture_test.rb
index f8387b27b0..933456ce9d 100644
--- a/actionview/test/actionpack/controller/capture_test.rb
+++ b/actionview/test/actionpack/controller/capture_test.rb
@@ -54,7 +54,7 @@ class CaptureTest < ActionController::TestCase
assert_equal expected_content_for_output, @response.body
end
- def test_should_concatentate_content_for
+ def test_should_concatenate_content_for
get :content_for_concatenated
assert_equal expected_content_for_output, @response.body
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 8d048ddbcb..27150c7d5f 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -1122,7 +1122,7 @@ class RenderTest < ActionController::TestCase
assert_equal "<title>Putting stuff in the title!</title>\nGreat stuff!\n", @response.body
end
- def test_overwritting_rendering_relative_file_with_extension
+ def test_overwriting_rendering_relative_file_with_extension
get :hello_world_from_rxml_using_template
assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index 672b4747ec..3ece9e50cd 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -1,4 +1,3 @@
-
require 'abstract_unit'
require 'action_view/dependency_tracker'
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index b193d387c3..41932d15ee 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -198,6 +198,41 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'input[type=radio][value=false][checked=checked]'
end
+ test 'collection radio buttons generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection' do
+ 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
+ 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][]" }
+
+ 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
+ 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
+ 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
+ end
+
# COLLECTION CHECK BOXES
test 'collection check boxes accepts a collection and generate a series of checkboxes for value method' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
@@ -235,6 +270,13 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select "input[type=hidden][name='user[category_ids][]'][value='']", :count => 0
end
+ test 'collection check boxes 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_check_boxes :user, :category_ids, collection, :id, :name, 'include_hidden' => false
+
+ assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0
+ end
+
test 'collection check boxes accepts a collection and generate a series of checkboxes with labels for label method' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_check_boxes :user, :category_ids, collection, :id, :name
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index aef137935a..41f31f1582 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -366,7 +366,7 @@ class FormHelperTest < ActionView::TestCase
)
end
- def test_label_with_to_model_and_overriden_model_name
+ def test_label_with_to_model_and_overridden_model_name
with_locale :label do
assert_dom_equal(
%{<label for="post_delegator_title">Delegate model_name title</label>},
@@ -1596,7 +1596,8 @@ class FormHelperTest < ActionView::TestCase
"<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>"
+ "<label for='post_active_false'>false</label>" +
+ "<input type='hidden' name='post[active][]' value='' />"
end
assert_dom_equal expected, output_buffer
@@ -1619,7 +1620,8 @@ class FormHelperTest < ActionView::TestCase
"true</label>" +
"<label for='post_active_false'>"+
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
- "false</label>"
+ "false</label>" +
+ "<input type='hidden' name='post[active][]' value='' />"
end
assert_dom_equal expected, output_buffer
@@ -1645,6 +1647,7 @@ class FormHelperTest < ActionView::TestCase
"<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
@@ -1663,7 +1666,8 @@ class FormHelperTest < ActionView::TestCase
"<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>"
+ "<label for='foo_post_active_false'>false</label>" +
+ "<input type='hidden' name='post[active][]' value='' />"
end
assert_dom_equal expected, output_buffer
@@ -1681,7 +1685,8 @@ class FormHelperTest < ActionView::TestCase
"<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>"
+ "<label for='post_1_active_false'>false</label>" +
+ "<input type='hidden' name='post[1][active][]' value='' />"
end
assert_dom_equal expected, output_buffer
@@ -2292,6 +2297,27 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_deep_nested_fields_for
+ @comment.save
+ form_for(:posts) do |f|
+ f.fields_for('post[]', @post) do |f2|
+ f2.text_field(:id)
+ @post.comments.each do |comment|
+ concat f2.fields_for('comment[]', comment) { |c|
+ concat c.text_field(:name)
+ }
+ end
+ end
+ end
+
+ expected = whole_form do
+ "<input name='posts[post][0][comment][1][name]' type='text' id='posts_post_0_comment_1_name' value='comment #1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+
def test_nested_fields_for_with_nested_collections
form_for(@post, as: 'post[]') do |f|
concat f.text_field(:title)
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 7e1324c011..8db35d02e1 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -450,21 +450,21 @@ class FormTagHelperTest < ActionView::TestCase
ActionView::Base.automatically_disable_submit_tag = true
end
- def test_data_disable_with_string
+ def test_submit_tag_having_data_disable_with_string
assert_dom_equal(
%(<input data-disable-with="Processing..." data-confirm="Are you sure?" name='commit' type="submit" value="Save" />),
submit_tag("Save", { "data-disable-with" => "Processing...", "data-confirm" => "Are you sure?" })
)
end
- def test_data_disable_with_boolean
+ def test_submit_tag_having_data_disable_with_boolean
assert_dom_equal(
%(<input data-confirm="Are you sure?" name='commit' type="submit" value="Save" />),
submit_tag("Save", { "data-disable-with" => false, "data-confirm" => "Are you sure?" })
)
end
- def test_data_hash_disable_with_boolean
+ def test_submit_tag_having_data_hash_disable_with_boolean
assert_dom_equal(
%(<input data-confirm="Are you sure?" name='commit' type="submit" value="Save" />),
submit_tag("Save", { :data => { :confirm => "Are you sure?", :disable_with => false } })
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 14dafd7d63..1184cf7da8 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -27,7 +27,7 @@ class LookupContextTest < ActiveSupport::TestCase
end
test "normalizes details on initialization" do
- assert_equal Mime::SET, @lookup_context.formats
+ assert_equal Mime::SET.to_a, @lookup_context.formats
assert_equal :en, @lookup_context.locale
end
@@ -48,11 +48,11 @@ class LookupContextTest < ActiveSupport::TestCase
test "handles */* formats" do
@lookup_context.formats = ["*/*"]
- assert_equal Mime::SET, @lookup_context.formats
+ assert_equal Mime::SET.to_a, @lookup_context.formats
end
test "handles explicitly defined */* formats fallback to :js" do
- @lookup_context.formats = [:js, Mime::ALL]
+ @lookup_context.formats = [:js, Mime::Type[:ALL]]
assert_equal [:js, *Mime::SET.symbols], @lookup_context.formats
end
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index 631d45586b..ace3e950b8 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "abstract_unit"
class NumberHelperTest < ActionView::TestCase
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 9c2c9507b7..00fc28a522 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -627,6 +627,16 @@ class CachedCollectionViewRenderTest < CachedViewRenderTest
@view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'key'] })
end
+ test "with caching with custom key and rendering with different key" do
+ customer = Customer.new("david")
+ key = cache_key([customer, 'key'], "test/_customer")
+
+ ActionView::PartialRenderer.collection_cache.write(key, 'Hello')
+
+ assert_equal "Hello: david",
+ @view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'another_key'] })
+ end
+
test "automatic caching with inferred cache name" do
customer = CachedCustomer.new("david")
key = cache_key(customer, "test/_cached_customer")
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 749d0dd7fd..261576bead 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -60,6 +60,12 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translate(:"translations.missing").html_safe?
end
+ def test_returns_missing_translation_message_with_unescaped_interpolation
+ expected = '<span class="translation_missing" title="translation missing: en.translations.missing, name: Kir, year: 2015, vulnerable: &amp;quot; onclick=&amp;quot;alert()&amp;quot;">Missing</span>'
+ assert_equal expected, translate(:"translations.missing", name: "Kir", year: "2015", vulnerable: %{" onclick="alert()"})
+ assert translate(:"translations.missing").html_safe?
+ end
+
def test_raises_missing_translation_message_with_raise_config_option
ActionView::Base.raise_on_missing_translations = true
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 965b556fab..b5742191eb 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Add job priorities to Active Job.
+
+ *wvengen*
+
* Implement a simple `AsyncJob` processor and associated `AsyncAdapter` that
queue jobs to a `concurrent-ruby` thread pool.
diff --git a/activejob/lib/active_job/async_job.rb b/activejob/lib/active_job/async_job.rb
index 7fcffc4c24..6c1c070994 100644
--- a/activejob/lib/active_job/async_job.rb
+++ b/activejob/lib/active_job/async_job.rb
@@ -1,5 +1,4 @@
require 'concurrent'
-require 'thread_safe'
module ActiveJob
# == Active Job Async Job
@@ -31,7 +30,7 @@ module ActiveJob
fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
}.freeze
- QUEUES = ThreadSafe::Cache.new do |hash, queue_name| #:nodoc:
+ QUEUES = Concurrent::Map.new do |hash, queue_name| #:nodoc:
hash.compute_if_absent(queue_name) { ActiveJob::AsyncJob.create_thread_pool }
end
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index 5d7c4cfb91..e5f09f65fb 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -1,6 +1,7 @@
require 'active_job/core'
require 'active_job/queue_adapter'
require 'active_job/queue_name'
+require 'active_job/queue_priority'
require 'active_job/enqueuing'
require 'active_job/execution'
require 'active_job/callbacks'
@@ -57,6 +58,7 @@ module ActiveJob #:nodoc:
include Core
include QueueAdapter
include QueueName
+ include QueuePriority
include Enqueuing
include Execution
include Callbacks
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index eac7279309..19b900a285 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -18,6 +18,9 @@ module ActiveJob
# Queue in which the job will reside.
attr_writer :queue_name
+ # Priority that the job will have (lower is more priority).
+ attr_writer :priority
+
# ID optionally provided by adapter
attr_accessor :provider_job_id
@@ -43,6 +46,7 @@ module ActiveJob
# * <tt>:wait</tt> - Enqueues the job with the specified delay
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
# * <tt>:queue</tt> - Enqueues the job on the specified queue
+ # * <tt>:priority</tt> - Enqueues the job with the specified priority
#
# ==== Examples
#
@@ -51,6 +55,7 @@ module ActiveJob
# VideoJob.set(wait_until: Time.now.tomorrow).perform_later(Video.last)
# VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last)
# VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last)
+ # VideoJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later(Video.last)
def set(options={})
ConfiguredJob.new(self, options)
end
@@ -62,6 +67,7 @@ module ActiveJob
@arguments = arguments
@job_id = SecureRandom.uuid
@queue_name = self.class.queue_name
+ @priority = self.class.priority
end
# Returns a hash with the job data that can safely be passed to the
@@ -71,6 +77,7 @@ module ActiveJob
'job_class' => self.class.name,
'job_id' => job_id,
'queue_name' => queue_name,
+ 'priority' => priority,
'arguments' => serialize_arguments(arguments),
'locale' => I18n.locale
}
@@ -99,6 +106,7 @@ module ActiveJob
def deserialize(job_data)
self.job_id = job_data['job_id']
self.queue_name = job_data['queue_name']
+ self.priority = job_data['priority']
self.serialized_arguments = job_data['arguments']
self.locale = job_data['locale'] || I18n.locale
end
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 98d92385dd..22154457fd 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -32,6 +32,7 @@ module ActiveJob
# * <tt>:wait</tt> - Enqueues the job with the specified delay
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
# * <tt>:queue</tt> - Enqueues the job on the specified queue
+ # * <tt>:priority</tt> - Enqueues the job with the specified priority
#
# ==== Examples
#
@@ -54,6 +55,7 @@ module ActiveJob
# * <tt>:wait</tt> - Enqueues the job with the specified delay
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
# * <tt>:queue</tt> - Enqueues the job on the specified queue
+ # * <tt>:priority</tt> - Enqueues the job with the specified priority
#
# ==== Examples
#
@@ -61,10 +63,12 @@ module ActiveJob
# my_job_instance.enqueue wait: 5.minutes
# my_job_instance.enqueue queue: :important
# my_job_instance.enqueue wait_until: Date.tomorrow.midnight
+ # my_job_instance.enqueue priority: 10
def enqueue(options={})
self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
+ self.priority = options[:priority].to_i if options[:priority]
run_callbacks :enqueue do
if self.scheduled_at
self.class.queue_adapter.enqueue_at self, self.scheduled_at
diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
index ac83da2b9c..0a785fad3b 100644
--- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -14,13 +14,13 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :delayed_job
class DelayedJobAdapter
def enqueue(job) #:nodoc:
- delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, priority: job.priority)
job.provider_job_id = delayed_job.id
delayed_job
end
def enqueue_at(job, timestamp) #:nodoc:
- delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, priority: job.priority, run_at: Time.at(timestamp))
job.provider_job_id = delayed_job.id
delayed_job
end
diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb
index 90947aa98d..ab13689747 100644
--- a/activejob/lib/active_job/queue_adapters/que_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -16,13 +16,13 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :que
class QueAdapter
def enqueue(job) #:nodoc:
- que_job = JobWrapper.enqueue job.serialize
+ que_job = JobWrapper.enqueue job.serialize, priority: job.priority
job.provider_job_id = que_job.attrs["job_id"]
que_job
end
def enqueue_at(job, timestamp) #:nodoc:
- que_job = JobWrapper.enqueue job.serialize, run_at: Time.at(timestamp)
+ que_job = JobWrapper.enqueue job.serialize, priority: job.priority, run_at: Time.at(timestamp)
job.provider_job_id = que_job.attrs["job_id"]
que_job
end
diff --git a/activejob/lib/active_job/queue_priority.rb b/activejob/lib/active_job/queue_priority.rb
new file mode 100644
index 0000000000..01d84910ff
--- /dev/null
+++ b/activejob/lib/active_job/queue_priority.rb
@@ -0,0 +1,44 @@
+module ActiveJob
+ module QueuePriority
+ extend ActiveSupport::Concern
+
+ # Includes the ability to override the default queue priority.
+ module ClassMethods
+ mattr_accessor(:default_priority)
+
+ # Specifies the priority of the queue to create the job with.
+ #
+ # class PublishToFeedJob < ActiveJob::Base
+ # queue_with_priority 50
+ #
+ # def perform(post)
+ # post.to_feed!
+ # end
+ # end
+ #
+ # Specify either an argument or a block.
+ def queue_with_priority(priority=nil, &block)
+ if block_given?
+ self.priority = block
+ else
+ self.priority = priority
+ end
+ end
+ end
+
+ included do
+ class_attribute :priority, instance_accessor: false
+
+ self.priority = default_priority
+ end
+
+ # Returns the priority that the job will be created with
+ def priority
+ if @priority.is_a?(Proc)
+ @priority = instance_exec(&@priority)
+ end
+ @priority
+ end
+
+ end
+end
diff --git a/activejob/test/cases/queue_priority_test.rb b/activejob/test/cases/queue_priority_test.rb
new file mode 100644
index 0000000000..ca17b51dad
--- /dev/null
+++ b/activejob/test/cases/queue_priority_test.rb
@@ -0,0 +1,47 @@
+require 'helper'
+require 'jobs/hello_job'
+
+class QueuePriorityTest < ActiveSupport::TestCase
+ test 'priority unset by default' do
+ assert_equal nil, HelloJob.priority
+ end
+
+ test 'uses given priority' do
+ original_priority = HelloJob.priority
+
+ begin
+ HelloJob.queue_with_priority 90
+ assert_equal 90, HelloJob.new.priority
+ ensure
+ HelloJob.priority = original_priority
+ end
+ end
+
+ test 'evals block given to priority to determine priority' do
+ original_priority = HelloJob.priority
+
+ begin
+ HelloJob.queue_with_priority { 25 }
+ assert_equal 25, HelloJob.new.priority
+ ensure
+ HelloJob.priority = original_priority
+ end
+ end
+
+ test 'can use arguments to determine priority in priority block' do
+ original_priority = HelloJob.priority
+
+ begin
+ HelloJob.queue_with_priority { self.arguments.first=='1' ? 99 : 11 }
+ assert_equal 99, HelloJob.new('1').priority
+ assert_equal 11, HelloJob.new('3').priority
+ ensure
+ HelloJob.priority = original_priority
+ end
+ end
+
+ test 'uses priority passed to #set' do
+ job = HelloJob.set(priority: 123).perform_later
+ assert_equal 123, job.priority
+ end
+end
diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb
index ee816e1dd5..616454a4b6 100644
--- a/activejob/test/cases/test_case_test.rb
+++ b/activejob/test/cases/test_case_test.rb
@@ -9,7 +9,7 @@ class ActiveJobTestCaseTest < ActiveJob::TestCase
# the `class_attribute` inheritance
class TestClassAttributeInheritanceJob < ActiveJob::Base
def self.queue_adapter=(*)
- raise 'Attemping to break `class_attribute` inheritance, bad!'
+ raise 'Attempting to break `class_attribute` inheritance, bad!'
end
end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 55f690bda8..7e86415f48 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -11,6 +11,7 @@ GlobalID.app = 'aj'
if ENV['AJ_INTEGRATION_TESTS']
require 'support/integration/helper'
else
+ ActiveJob::Base.logger = Logger.new(nil)
require "adapters/#{@adapter}"
end
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index 125ba54302..e435ed4aa6 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -84,4 +84,16 @@ class QueuingTest < ActiveSupport::TestCase
I18n.locale = :en
end
end
+
+ test 'should run job with higher priority first' do
+ skip unless adapter_is?(:delayed_job, :que)
+
+ wait_until = Time.now + 3.seconds
+ TestJob.set(wait_until: wait_until, priority: 20).perform_later "#{@id}.1"
+ TestJob.set(wait_until: wait_until, priority: 10).perform_later "#{@id}.2"
+ wait_for_jobs_to_finish_for(10.seconds)
+ assert job_executed "#{@id}.1"
+ assert job_executed "#{@id}.2"
+ assert job_executed_at("#{@id}.2") < job_executed_at("#{@id}.1")
+ end
end
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index d9938600f2..9aa07bcb52 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -58,7 +58,7 @@ module SidekiqJobsManager
timeout: 1,
})
Sidekiq.average_scheduled_poll_interval = 0.5
- Sidekiq::Scheduled.const_set :INITIAL_WAIT, 1
+ Sidekiq.options[:poll_interval_average] = 1
begin
sidekiq.run
continue_write.puts "started"
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 4ffdb8cffa..0c062a025e 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -1,8 +1,9 @@
if ENV['AJ_ADAPTER'] == 'delayed_job'
generate "delayed_job:active_record", "--quiet"
- rake("db:migrate")
end
+rake("db:migrate")
+
initializer 'activejob.rb', <<-CODE
require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
JobsManager.current_manager.setup
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
index 8c2e5a86c2..4a1b0bfbcb 100644
--- a/activejob/test/support/integration/helper.rb
+++ b/activejob/test/support/integration/helper.rb
@@ -1,4 +1,4 @@
-puts "*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n"
+puts "\n\n*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n"
ENV["RAILS_ENV"] = "test"
ActiveJob::Base.queue_name_prefix = nil
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index 39aee6d407..8319d09520 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -42,8 +42,12 @@ module TestCaseHelpers
end
end
- def job_executed
- Dummy::Application.root.join("tmp/#{@id}").exist?
+ def job_executed(id=@id)
+ Dummy::Application.root.join("tmp/#{id}").exist?
+ end
+
+ def job_executed_at(id=@id)
+ File.new(Dummy::Application.root.join("tmp/#{id}")).ctime
end
def job_output
diff --git a/activejob/test/support/que/inline.rb b/activejob/test/support/que/inline.rb
index 0232da1370..0950e52d28 100644
--- a/activejob/test/support/que/inline.rb
+++ b/activejob/test/support/que/inline.rb
@@ -6,6 +6,7 @@ Que::Job.class_eval do
if args.last.is_a?(Hash)
options = args.pop
options.delete(:run_at)
+ options.delete(:priority)
args << options unless options.empty?
end
self.run(*args)
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 77e4ce3afe..1963a3fc4e 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,4 +1,4 @@
-require 'thread_safe'
+require 'concurrent'
require 'mutex_m'
module ActiveModel
@@ -350,7 +350,7 @@ module ActiveModel
# significantly (in our case our test suite finishes 10% faster with
# this cache).
def attribute_method_matchers_cache #:nodoc:
- @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4)
+ @attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4)
end
def attribute_method_matchers_matching(method_name) #:nodoc:
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 2cf39b68fb..0d6a3dc52d 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -103,6 +103,7 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
+ terminator: deprecated_false_terminator,
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 0169c20e0b..0ab8df42f5 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -203,7 +203,7 @@ module ActiveModel
# Returns +true+ if attr_name were changed before the model was saved,
# +false+ otherwise.
def previous_changes_include?(attr_name)
- @previously_changed.include?(attr_name)
+ previous_changes.include?(attr_name)
end
# Removes current changes and makes them accessible through +previous_changes+.
@@ -225,7 +225,7 @@ module ActiveModel
# Handles <tt>*_previous_change</tt> for +method_missing+.
def attribute_previous_change(attr)
- @previously_changed[attr] if attribute_previously_changed?(attr)
+ previous_changes[attr] if attribute_previously_changed?(attr)
end
# Handles <tt>*_will_change!</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 29e0c977ce..4726a68f69 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/deep_dup'
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 213f2d5b6a..de5fb27467 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -164,7 +164,7 @@ module ActiveModel
@route_key << "_index" if @plural == @singular
end
- # Transform the model name into a more humane format, using I18n. By default,
+ # Transform the model name into a more human format, using I18n. By default,
# it will underscore then humanize the class name.
#
# class BlogPost
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
new file mode 100644
index 0000000000..f8ca7d0512
--- /dev/null
+++ b/activemodel/lib/active_model/type.rb
@@ -0,0 +1,57 @@
+require 'active_model/type/helpers'
+require 'active_model/type/value'
+
+require 'active_model/type/big_integer'
+require 'active_model/type/binary'
+require 'active_model/type/boolean'
+require 'active_model/type/date'
+require 'active_model/type/date_time'
+require 'active_model/type/decimal'
+require 'active_model/type/decimal_without_scale'
+require 'active_model/type/float'
+require 'active_model/type/integer'
+require 'active_model/type/string'
+require 'active_model/type/text'
+require 'active_model/type/time'
+require 'active_model/type/unsigned_integer'
+
+require 'active_model/type/registry'
+
+module ActiveModel
+ module Type
+ @registry = Registry.new
+
+ class << self
+ attr_accessor :registry # :nodoc:
+ delegate :add_modifier, to: :registry
+
+ # Add a new type to the registry, allowing it to be referenced as a
+ # symbol by ActiveModel::Attributes::ClassMethods#attribute. If your
+ # type is only meant to be used with a specific database adapter, you can
+ # do so by passing +adapter: :postgresql+. If your type has the same
+ # name as a native type for the current adapter, an exception will be
+ # raised unless you specify an +:override+ option. +override: true+ will
+ # cause your type to be used instead of the native type. +override:
+ # false+ will cause the native type to be used over yours if one exists.
+ def register(type_name, klass = nil, **options, &block)
+ registry.register(type_name, klass, **options, &block)
+ end
+
+ def lookup(*args, **kwargs) # :nodoc:
+ registry.lookup(*args, **kwargs)
+ end
+ end
+
+ register(:big_integer, Type::BigInteger)
+ register(:binary, Type::Binary)
+ register(:boolean, Type::Boolean)
+ register(:date, Type::Date)
+ register(:date_time, Type::DateTime)
+ register(:decimal, Type::Decimal)
+ register(:float, Type::Float)
+ register(:integer, Type::Integer)
+ register(:string, Type::String)
+ register(:text, Type::Text)
+ register(:time, Type::Time)
+ end
+end
diff --git a/activerecord/lib/active_record/type/big_integer.rb b/activemodel/lib/active_model/type/big_integer.rb
index 0c72d8914f..4168cbfce7 100644
--- a/activerecord/lib/active_record/type/big_integer.rb
+++ b/activemodel/lib/active_model/type/big_integer.rb
@@ -1,6 +1,6 @@
-require 'active_record/type/integer'
+require 'active_model/type/integer'
-module ActiveRecord
+module ActiveModel
module Type
class BigInteger < Integer # :nodoc:
private
diff --git a/activerecord/lib/active_record/type/binary.rb b/activemodel/lib/active_model/type/binary.rb
index 0baf8c63ad..a0cc45b4c3 100644
--- a/activerecord/lib/active_record/type/binary.rb
+++ b/activemodel/lib/active_model/type/binary.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Binary < Value # :nodoc:
def type
diff --git a/activerecord/lib/active_record/type/boolean.rb b/activemodel/lib/active_model/type/boolean.rb
index f6a75512fd..c1bce98c87 100644
--- a/activerecord/lib/active_record/type/boolean.rb
+++ b/activemodel/lib/active_model/type/boolean.rb
@@ -1,6 +1,8 @@
-module ActiveRecord
+module ActiveModel
module Type
class Boolean < Value # :nodoc:
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
+
def type
:boolean
end
@@ -11,7 +13,7 @@ module ActiveRecord
if value == ''
nil
else
- !ConnectionAdapters::Column::FALSE_VALUES.include?(value)
+ !FALSE_VALUES.include?(value)
end
end
end
diff --git a/activemodel/lib/active_model/type/date.rb b/activemodel/lib/active_model/type/date.rb
new file mode 100644
index 0000000000..f74243a22c
--- /dev/null
+++ b/activemodel/lib/active_model/type/date.rb
@@ -0,0 +1,50 @@
+module ActiveModel
+ module Type
+ class Date < Value # :nodoc:
+ include Helpers::AcceptsMultiparameterTime.new
+
+ def type
+ :date
+ end
+
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ end
+
+ private
+
+ def cast_value(value)
+ if value.is_a?(::String)
+ return if value.empty?
+ fast_string_to_date(value) || fallback_string_to_date(value)
+ elsif value.respond_to?(:to_date)
+ value.to_date
+ else
+ value
+ end
+ end
+
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
+ def fast_string_to_date(string)
+ if string =~ ISO_DATE
+ new_date $1.to_i, $2.to_i, $3.to_i
+ end
+ end
+
+ def fallback_string_to_date(string)
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
+ end
+
+ def new_date(year, mon, mday)
+ if year && year != 0
+ ::Date.new(year, mon, mday) rescue nil
+ end
+ end
+
+ def value_from_multiparameter_assignment(*)
+ time = super
+ time && time.to_date
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/type/date_time.rb b/activemodel/lib/active_model/type/date_time.rb
new file mode 100644
index 0000000000..2f2df4320f
--- /dev/null
+++ b/activemodel/lib/active_model/type/date_time.rb
@@ -0,0 +1,44 @@
+module ActiveModel
+ module Type
+ class DateTime < Value # :nodoc:
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 4 => 0, 5 => 0 }
+ )
+
+ def type
+ :datetime
+ end
+
+ private
+
+ def cast_value(value)
+ return apply_seconds_precision(value) unless value.is_a?(::String)
+ return if value.empty?
+
+ fast_string_to_time(value) || fallback_string_to_time(value)
+ end
+
+ # '0.123456' -> 123456
+ # '1.123456' -> 123456
+ def microseconds(time)
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
+ end
+
+ def fallback_string_to_time(string)
+ time_hash = ::Date._parse(string)
+ time_hash[:sec_fraction] = microseconds(time_hash)
+
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
+ end
+
+ def value_from_multiparameter_assignment(values_hash)
+ missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
+ if missing_parameter
+ raise ArgumentError, missing_parameter
+ end
+ super
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb
index f5b145230d..d19d8baada 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activemodel/lib/active_model/type/decimal.rb
@@ -1,4 +1,6 @@
-module ActiveRecord
+require "bigdecimal/util"
+
+module ActiveModel
module Type
class Decimal < Value # :nodoc:
include Helpers::Numeric
diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activemodel/lib/active_model/type/decimal_without_scale.rb
index ff5559e300..129baa0c10 100644
--- a/activerecord/lib/active_record/type/decimal_without_scale.rb
+++ b/activemodel/lib/active_model/type/decimal_without_scale.rb
@@ -1,6 +1,6 @@
-require 'active_record/type/big_integer'
+require 'active_model/type/big_integer'
-module ActiveRecord
+module ActiveModel
module Type
class DecimalWithoutScale < BigInteger # :nodoc:
def type
diff --git a/activerecord/lib/active_record/type/float.rb b/activemodel/lib/active_model/type/float.rb
index d88482b85d..0f925bc7e1 100644
--- a/activerecord/lib/active_record/type/float.rb
+++ b/activemodel/lib/active_model/type/float.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Float < Value # :nodoc:
include Helpers::Numeric
diff --git a/activemodel/lib/active_model/type/helpers.rb b/activemodel/lib/active_model/type/helpers.rb
new file mode 100644
index 0000000000..a805a359ab
--- /dev/null
+++ b/activemodel/lib/active_model/type/helpers.rb
@@ -0,0 +1,4 @@
+require 'active_model/type/helpers/accepts_multiparameter_time'
+require 'active_model/type/helpers/numeric'
+require 'active_model/type/helpers/mutable'
+require 'active_model/type/helpers/time_value'
diff --git a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
index be571fc1c7..facea12704 100644
--- a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb
+++ b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
module Helpers
class AcceptsMultiparameterTime < Module # :nodoc:
@@ -11,16 +11,21 @@ module ActiveRecord
end
end
+ define_method(:assert_valid_value) do |value|
+ if value.is_a?(Hash)
+ value_from_multiparameter_assignment(value)
+ else
+ super(value)
+ end
+ end
+
define_method(:value_from_multiparameter_assignment) do |values_hash|
defaults.each do |k, v|
values_hash[k] ||= v
end
return unless values_hash[1] && values_hash[2] && values_hash[3]
values = values_hash.sort.map(&:last)
- ::Time.send(
- ActiveRecord::Base.default_timezone,
- *values
- )
+ ::Time.send(default_timezone, *values)
end
private :value_from_multiparameter_assignment
end
diff --git a/activerecord/lib/active_record/type/helpers/mutable.rb b/activemodel/lib/active_model/type/helpers/mutable.rb
index 88a9099277..4dddbe4e5e 100644
--- a/activerecord/lib/active_record/type/helpers/mutable.rb
+++ b/activemodel/lib/active_model/type/helpers/mutable.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
module Helpers
module Mutable # :nodoc:
diff --git a/activerecord/lib/active_record/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb
index a755a02a59..c883010506 100644
--- a/activerecord/lib/active_record/type/helpers/numeric.rb
+++ b/activemodel/lib/active_model/type/helpers/numeric.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
module Helpers
module Numeric # :nodoc:
diff --git a/activerecord/lib/active_record/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb
index 7eb41557cb..63993c0d93 100644
--- a/activerecord/lib/active_record/type/helpers/time_value.rb
+++ b/activemodel/lib/active_model/type/helpers/time_value.rb
@@ -1,16 +1,14 @@
-module ActiveRecord
+require "active_support/core_ext/time/zones"
+
+module ActiveModel
module Type
module Helpers
module TimeValue # :nodoc:
def serialize(value)
- if precision && value.respond_to?(:usec)
- number_of_insignificant_digits = 6 - precision
- round_power = 10 ** number_of_insignificant_digits
- value = value.change(usec: value.usec / round_power * round_power)
- end
+ value = apply_seconds_precision(value)
if value.acts_like?(:time)
- zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
+ zone_conversion_method = is_utc? ? :getutc : :getlocal
if value.respond_to?(zone_conversion_method)
value = value.send(zone_conversion_method)
@@ -20,6 +18,25 @@ module ActiveRecord
value
end
+ def is_utc?
+ ::Time.zone_default.nil? || ::Time.zone_default =~ 'UTC'
+ end
+
+ def default_timezone
+ if is_utc?
+ :utc
+ else
+ :local
+ end
+ end
+
+ def apply_seconds_precision(value)
+ return value unless precision && value.respond_to?(:usec)
+ number_of_insignificant_digits = 6 - precision
+ round_power = 10 ** number_of_insignificant_digits
+ value.change(usec: value.usec / round_power * round_power)
+ end
+
def type_cast_for_schema(value)
"'#{value.to_s(:db)}'"
end
@@ -39,15 +56,17 @@ module ActiveRecord
return unless time
time -= offset
- Base.default_timezone == :utc ? time : time.getlocal
+ is_utc? ? time : time.getlocal
else
- ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
end
end
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
+
# Doesn't handle time zones.
def fast_string_to_time(string)
- if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME
+ if string =~ ISO_DATETIME
microsec = ($7.to_r * 1_000_000).to_i
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activemodel/lib/active_model/type/integer.rb
index c5040c6d3b..2f73ede009 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activemodel/lib/active_model/type/integer.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Integer < Value # :nodoc:
include Helpers::Numeric
diff --git a/activemodel/lib/active_model/type/registry.rb b/activemodel/lib/active_model/type/registry.rb
new file mode 100644
index 0000000000..adc88eb624
--- /dev/null
+++ b/activemodel/lib/active_model/type/registry.rb
@@ -0,0 +1,64 @@
+module ActiveModel
+ # :stopdoc:
+ module Type
+ class Registry
+ def initialize
+ @registrations = []
+ end
+
+ def register(type_name, klass = nil, **options, &block)
+ block ||= proc { |_, *args| klass.new(*args) }
+ registrations << registration_klass.new(type_name, block, **options)
+ end
+
+ def lookup(symbol, *args)
+ registration = find_registration(symbol, *args)
+
+ if registration
+ registration.call(self, symbol, *args)
+ else
+ raise ArgumentError, "Unknown type #{symbol.inspect}"
+ end
+ end
+
+ protected
+
+ attr_reader :registrations
+
+ private
+
+ def registration_klass
+ Registration
+ end
+
+ def find_registration(symbol, *args)
+ registrations.find { |r| r.matches?(symbol, *args) }
+ end
+ end
+
+ class Registration
+ # Options must be taken because of https://bugs.ruby-lang.org/issues/10856
+ def initialize(name, block, **)
+ @name = name
+ @block = block
+ end
+
+ def call(_registry, *args, **kwargs)
+ if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
+ block.call(*args, **kwargs)
+ else
+ block.call(*args)
+ end
+ end
+
+ def matches?(type_name, *args, **kwargs)
+ type_name == name
+ end
+
+ protected
+
+ attr_reader :name, :block
+ end
+ end
+ # :startdoc:
+end
diff --git a/activerecord/lib/active_record/type/string.rb b/activemodel/lib/active_model/type/string.rb
index 2662b7e874..fd1630c751 100644
--- a/activerecord/lib/active_record/type/string.rb
+++ b/activemodel/lib/active_model/type/string.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class String < Value # :nodoc:
def type
diff --git a/activerecord/lib/active_record/type/text.rb b/activemodel/lib/active_model/type/text.rb
index 26f980f060..1ad04daba4 100644
--- a/activerecord/lib/active_record/type/text.rb
+++ b/activemodel/lib/active_model/type/text.rb
@@ -1,6 +1,6 @@
-require 'active_record/type/string'
+require 'active_model/type/string'
-module ActiveRecord
+module ActiveModel
module Type
class Text < String # :nodoc:
def type
diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb
new file mode 100644
index 0000000000..7101bad566
--- /dev/null
+++ b/activemodel/lib/active_model/type/time.rb
@@ -0,0 +1,42 @@
+module ActiveModel
+ module Type
+ class Time < Value # :nodoc:
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
+ )
+
+ def type
+ :time
+ end
+
+ def user_input_in_time_zone(value)
+ return unless value.present?
+
+ case value
+ when ::String
+ value = "2000-01-01 #{value}"
+ when ::Time
+ value = value.change(year: 2000, day: 1, month: 1)
+ end
+
+ super(value)
+ end
+
+ private
+
+ def cast_value(value)
+ return value unless value.is_a?(::String)
+ return if value.empty?
+
+ dummy_time_value = "2000-01-01 #{value}"
+
+ 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))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/unsigned_integer.rb b/activemodel/lib/active_model/type/unsigned_integer.rb
index ed3e527483..3f49f9f5f7 100644
--- a/activerecord/lib/active_record/type/unsigned_integer.rb
+++ b/activemodel/lib/active_model/type/unsigned_integer.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class UnsignedInteger < Integer # :nodoc:
private
diff --git a/activerecord/lib/active_record/type/value.rb b/activemodel/lib/active_model/type/value.rb
index 6b9d147ecc..5fea0561a6 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activemodel/lib/active_model/type/value.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Value
attr_reader :precision, :scale, :limit
@@ -91,6 +91,9 @@ module ActiveRecord
limit == other.limit
end
+ def assert_valid_value(*)
+ end
+
private
# Convenience method for types which do not need separate type casting
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 1bcfedb35d..c5c0cd4636 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -14,16 +14,63 @@ module ActiveModel
end
private
+
def setup!(klass)
- attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
- attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
- klass.send(:attr_reader, *attr_readers)
- klass.send(:attr_writer, *attr_writers)
+ klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes)))
end
def acceptable_option?(value)
Array(options[:accept]).include?(value)
end
+
+ class LazilyDefineAttributes < Module
+ def initialize(attribute_definition)
+ define_method(:respond_to_missing?) do |method_name, include_private=false|
+ super(method_name, include_private) || attribute_definition.matches?(method_name)
+ end
+
+ define_method(:method_missing) do |method_name, *args, &block|
+ if attribute_definition.matches?(method_name)
+ attribute_definition.define_on(self.class)
+ send(method_name, *args, &block)
+ else
+ super(method_name, *args, &block)
+ end
+ end
+ end
+ end
+
+ class AttributeDefinition
+ def initialize(attributes)
+ @attributes = attributes.map(&:to_s)
+ end
+
+ def matches?(method_name)
+ attr_name = convert_to_reader_name(method_name)
+ attributes.include?(attr_name)
+ end
+
+ def define_on(klass)
+ attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
+ attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
+ klass.send(:attr_reader, *attr_readers)
+ klass.send(:attr_writer, *attr_writers)
+ end
+
+ protected
+
+ attr_reader :attributes
+
+ private
+
+ def convert_to_reader_name(method_name)
+ attr_name = method_name.to_s
+ if attr_name.end_with?("=")
+ attr_name = attr_name[0..-2]
+ end
+ attr_name
+ end
+ end
end
module HelperMethods
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 4b58ef66e3..52111e5442 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -23,6 +23,7 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
define_callbacks :validation,
+ terminator: deprecated_false_terminator,
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name]
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index bb2f0bd064..8f8ade90bb 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -9,7 +9,7 @@ module ActiveModel
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation"))
- unless confimation_value_equal?(record, attribute, value, confirmed)
+ unless confirmation_value_equal?(record, attribute, value, confirmed)
human_attribute_name = record.class.human_attribute_name(attribute)
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
end
@@ -27,7 +27,7 @@ module ActiveModel
end.compact)
end
- def confimation_value_equal?(record, attribute, value, confirmed)
+ def confirmation_value_equal?(record, attribute, value, confirmed)
if !options[:case_sensitive] && value.is_a?(String)
value.casecmp(confirmed) == 0
else
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
index 64a85e01eb..3336691841 100644
--- a/activemodel/test/cases/attribute_assignment_test.rb
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -70,7 +70,7 @@ class AttributeAssignmentTest < ActiveModel::TestCase
end
end
- test "an ArgumentError is raised if a non-hash-like obejct is passed" do
+ test "an ArgumentError is raised if a non-hash-like object is passed" do
assert_raises(ArgumentError) do
Model.new(1)
end
diff --git a/activerecord/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb
index 01ee36b892..353dbf84ad 100644
--- a/activerecord/test/cases/type/decimal_test.rb
+++ b/activemodel/test/cases/type/decimal_test.rb
@@ -1,8 +1,9 @@
require "cases/helper"
+require "active_model/type"
-module ActiveRecord
+module ActiveModel
module Type
- class DecimalTest < ActiveRecord::TestCase
+ class DecimalTest < ActiveModel::TestCase
def test_type_cast_decimal
type = Decimal.new
assert_equal BigDecimal.new("0"), type.cast(BigDecimal.new("0"))
diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb
new file mode 100644
index 0000000000..dac922db42
--- /dev/null
+++ b/activemodel/test/cases/type/integer_test.rb
@@ -0,0 +1,108 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class IntegerTest < ActiveModel::TestCase
+ test "simple values" do
+ type = Type::Integer.new
+ assert_equal 1, type.cast(1)
+ assert_equal 1, type.cast('1')
+ assert_equal 1, type.cast('1ignore')
+ assert_equal 0, type.cast('bad1')
+ assert_equal 0, type.cast('bad')
+ assert_equal 1, type.cast(1.7)
+ assert_equal 0, type.cast(false)
+ assert_equal 1, type.cast(true)
+ assert_nil type.cast(nil)
+ end
+
+ test "random objects cast to nil" do
+ type = Type::Integer.new
+ assert_nil type.cast([1,2])
+ assert_nil type.cast({1 => 2})
+ assert_nil type.cast(1..2)
+ end
+
+ test "casting objects without to_i" do
+ type = Type::Integer.new
+ assert_nil type.cast(::Object.new)
+ end
+
+ test "casting nan and infinity" do
+ type = Type::Integer.new
+ assert_nil type.cast(::Float::NAN)
+ assert_nil type.cast(1.0/0.0)
+ end
+
+ test "casting booleans for database" do
+ type = Type::Integer.new
+ assert_equal 1, type.serialize(true)
+ assert_equal 0, type.serialize(false)
+ end
+
+ test "changed?" do
+ type = Type::Integer.new
+
+ assert type.changed?(5, 5, '5wibble')
+ assert_not type.changed?(5, 5, '5')
+ assert_not type.changed?(5, 5, '5.0')
+ assert_not type.changed?(-5, -5, '-5')
+ assert_not type.changed?(-5, -5, '-5.0')
+ assert_not type.changed?(nil, nil, nil)
+ end
+
+ test "values below int min value are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(-2147483649)
+ end
+ end
+
+ test "values above int max value are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(2147483648)
+ end
+ end
+
+ test "very small numbers are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(-9999999999999999999999999999999)
+ end
+ end
+
+ test "very large numbers are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(9999999999999999999999999999999)
+ end
+ end
+
+ test "normal numbers are in range" do
+ type = Integer.new
+ assert_equal(0, type.serialize(0))
+ assert_equal(-1, type.serialize(-1))
+ assert_equal(1, type.serialize(1))
+ end
+
+ test "int max value is in range" do
+ assert_equal(2147483647, Integer.new.serialize(2147483647))
+ end
+
+ test "int min value is in range" do
+ assert_equal(-2147483648, Integer.new.serialize(-2147483648))
+ end
+
+ test "columns with a larger limit have larger ranges" do
+ type = Integer.new(limit: 8)
+
+ assert_equal(9223372036854775807, type.serialize(9223372036854775807))
+ assert_equal(-9223372036854775808, type.serialize(-9223372036854775808))
+ assert_raises(::RangeError) do
+ type.serialize(-9999999999999999999999999999999)
+ end
+ assert_raises(::RangeError) do
+ type.serialize(9999999999999999999999999999999)
+ end
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/registry_test.rb b/activemodel/test/cases/type/registry_test.rb
new file mode 100644
index 0000000000..2a48998a62
--- /dev/null
+++ b/activemodel/test/cases/type/registry_test.rb
@@ -0,0 +1,39 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ class RegistryTest < ActiveModel::TestCase
+ test "a class can be registered for a symbol" do
+ registry = Type::Registry.new
+ registry.register(:foo, ::String)
+ registry.register(:bar, ::Array)
+
+ assert_equal "", registry.lookup(:foo)
+ assert_equal [], registry.lookup(:bar)
+ end
+
+ test "a block can be registered" do
+ registry = Type::Registry.new
+ registry.register(:foo) do |*args|
+ [*args, "block for foo"]
+ end
+ registry.register(:bar) do |*args|
+ [*args, "block for bar"]
+ end
+
+ assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1)
+ assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2)
+ assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3)
+ end
+
+ test "a reasonable error is given when no type is found" do
+ registry = Type::Registry.new
+
+ e = assert_raises(ArgumentError) do
+ registry.lookup(:foo)
+ end
+
+ assert_equal "Unknown type :foo", e.message
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb
new file mode 100644
index 0000000000..8ec771ea42
--- /dev/null
+++ b/activemodel/test/cases/type/string_test.rb
@@ -0,0 +1,20 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ class StringTypeTest < ActiveModel::TestCase
+ test "type casting" do
+ type = Type::String.new
+ assert_equal "t", type.cast(true)
+ assert_equal "f", type.cast(false)
+ assert_equal "123", type.cast(123)
+ end
+
+ test "values are duped coming out" do
+ s = "foo"
+ type = Type::String.new
+ assert_not_same s, type.cast(s)
+ assert_not_same s, type.deserialize(s)
+ end
+ end
+end
diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activemodel/test/cases/type/unsigned_integer_test.rb
index f2c910eade..16301b3ac0 100644
--- a/activerecord/test/cases/type/unsigned_integer_test.rb
+++ b/activemodel/test/cases/type/unsigned_integer_test.rb
@@ -1,8 +1,9 @@
require "cases/helper"
+require "active_model/type"
-module ActiveRecord
+module ActiveModel
module Type
- class UnsignedIntegerTest < ActiveRecord::TestCase
+ class UnsignedIntegerTest < ActiveModel::TestCase
test "unsigned int max value is in range" do
assert_equal(4294967295, UnsignedInteger.new.serialize(4294967295))
end
diff --git a/activemodel/test/cases/types_test.rb b/activemodel/test/cases/types_test.rb
new file mode 100644
index 0000000000..f937208580
--- /dev/null
+++ b/activemodel/test/cases/types_test.rb
@@ -0,0 +1,122 @@
+require "cases/helper"
+require "active_model/type"
+require "active_support/core_ext/numeric/time"
+
+module ActiveModel
+ class TypesTest < ActiveModel::TestCase
+ def test_type_cast_boolean
+ type = Type::Boolean.new
+ assert type.cast('').nil?
+ assert type.cast(nil).nil?
+
+ assert type.cast(true)
+ assert type.cast(1)
+ assert type.cast('1')
+ assert type.cast('t')
+ assert type.cast('T')
+ assert type.cast('true')
+ assert type.cast('TRUE')
+ assert type.cast('on')
+ assert type.cast('ON')
+ assert type.cast(' ')
+ assert type.cast("\u3000\r\n")
+ assert type.cast("\u0000")
+ assert type.cast('SOMETHING RANDOM')
+
+ # explicitly check for false vs nil
+ assert_equal false, type.cast(false)
+ assert_equal false, type.cast(0)
+ assert_equal false, type.cast('0')
+ assert_equal false, type.cast('f')
+ assert_equal false, type.cast('F')
+ assert_equal false, type.cast('false')
+ assert_equal false, type.cast('FALSE')
+ assert_equal false, type.cast('off')
+ assert_equal false, type.cast('OFF')
+ end
+
+ def test_type_cast_float
+ type = Type::Float.new
+ assert_equal 1.0, type.cast("1")
+ end
+
+ def test_changing_float
+ type = Type::Float.new
+
+ assert type.changed?(5.0, 5.0, '5wibble')
+ assert_not type.changed?(5.0, 5.0, '5')
+ assert_not type.changed?(5.0, 5.0, '5.0')
+ assert_not type.changed?(nil, nil, nil)
+ end
+
+ def test_type_cast_binary
+ type = Type::Binary.new
+ assert_equal nil, type.cast(nil)
+ assert_equal "1", type.cast("1")
+ assert_equal 1, type.cast(1)
+ end
+
+ def test_type_cast_time
+ type = Type::Time.new
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast('ABC')
+
+ time_string = Time.now.utc.strftime("%T")
+ assert_equal time_string, type.cast(time_string).strftime("%T")
+ end
+
+ def test_type_cast_datetime_and_timestamp
+ type = Type::DateTime.new
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast(' ')
+ assert_equal nil, type.cast('ABC')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T")
+ end
+
+ def test_type_cast_date
+ type = Type::Date.new
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast(' ')
+ assert_equal nil, type.cast('ABC')
+
+ date_string = Time.now.utc.strftime("%F")
+ assert_equal date_string, type.cast(date_string).strftime("%F")
+ end
+
+ def test_type_cast_duration_to_integer
+ type = Type::Integer.new
+ assert_equal 1800, type.cast(30.minutes)
+ assert_equal 7200, type.cast(2.hours)
+ end
+
+ def test_string_to_time_with_timezone
+ ["UTC", "US/Eastern"].each do |zone|
+ with_timezone_config default: zone do
+ type = Type::DateTime.new
+ assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
+ end
+ end
+ end
+
+ def test_type_equality
+ assert_equal Type::Value.new, Type::Value.new
+ assert_not_equal Type::Value.new, Type::Integer.new
+ assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
+ end
+
+ private
+
+ def with_timezone_config(default:)
+ old_zone_default = ::Time.zone_default
+ ::Time.zone_default = ::Time.find_zone(default)
+ yield
+ ensure
+ ::Time.zone_default = old_zone_default
+ end
+ end
+end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 918097449b..6c0722e43c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,92 @@
+* Don't require a database connection to load a class which uses acceptance
+ validations.
+
+ *Sean Griffin*
+
+* Correctly apply `unscope` when preloading through associations.
+
+ *Jimmy Bourassa*
+
+* Fixed taking precision into count when assigning a value to timestamp attribute
+
+ Timestamp column can have less precision than ruby timestamp
+ In result in how big a fraction of a second can be stored in the
+ database.
+
+
+ m = Model.create!
+ m.created_at.usec == m.reload.created_at.usec
+ # => false
+ # due to different precision in Time.now and database column
+
+ If the precision is low enough, (mysql default is 0, so it is always low
+ enough by default) the value changes when model is reloaded from the
+ database. This patch fixes that issue ensuring that any timestamp
+ assigned as an attribute is converted to column precision under the
+ attribute.
+
+ *Bogdan Gusiev*
+
+* Introduce `connection.data_sources` and `connection.data_source_exists?`.
+ These methods determine what relations can be used to back Active Record
+ models (usually tables and views).
+
+ Also deprecate `SchemaCache#tables`, `SchemaCache#table_exists?` and
+ `SchemaCache#clear_table_cache!` in favor of their new data source
+ counterparts.
+
+ *Yves Senn*, *Matthew Draper*
+
+* Add `ActiveRecord::Base.ignored_columns` to make some columns
+ invisible from ActiveRecord.
+
+ *Jean Boussier*
+
+* `ActiveRecord::Tasks::MySQLDatabaseTasks` fails if shellout to
+ mysql commands (like `mysqldump`) is not successful.
+
+ *Steve Mitchell*
+
+* Ensure `select` quotes aliased attributes, even when using `from`.
+
+ Fixes #21488
+
+ *Sean Griffin & @johanlunds*
+
+* MySQL: support `unsigned` numeric data types.
+
+ Example:
+
+ create_table :foos do |t|
+ t.unsigned_integer :quantity
+ t.unsigned_bigint :total
+ t.unsigned_float :percentage
+ t.unsigned_decimal :price, precision: 10, scale: 2
+ end
+
+ The `unsigned: true` option may be used for the primary key:
+
+ create_table :foos, id: :bigint, unsigned: true do |t|
+ …
+ end
+
+ *Ryuta Kamizono*
+
+* Add `#views` and `#view_exists?` methods on connection adapters.
+
+ *Ryuta Kamizono*
+
+* Correctly dump composite primary key.
+
+ Example:
+
+ create_table :barcodes, primary_key: ["region", "code"] do |t|
+ t.string :region
+ t.integer :code
+ end
+
+ *Ryuta Kamizono*
+
* Lookup the attribute name for `restrict_with_error` messages on the
model class that defines the association.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 049c5d2b3b..3eac8cc422 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -26,7 +26,7 @@ The Product class is automatically mapped to the table named "products",
which might look like this:
CREATE TABLE products (
- id int(11) NOT NULL auto_increment,
+ id int NOT NULL auto_increment,
name varchar(255),
PRIMARY KEY (id)
);
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index f7b50cd25a..a2aea63bdd 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -25,7 +25,7 @@ module ActiveRecord
end
# Active Record implements aggregation through a macro-like class method called +composed_of+
- # for representing attributes as value objects. It expresses relationships like "Account [is]
+ # for representing attributes as value objects. It expresses relationships like "Account [is]
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
# to the macro adds a description of how the value objects are created from the attributes of
# the entity object (when the entity is initialized either as a new object or from finding an
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 65eb6fba27..cf3e63b4a3 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -446,14 +446,14 @@ module ActiveRecord
# The tables for these classes could look something like:
#
# CREATE TABLE users (
- # id int(11) NOT NULL auto_increment,
- # account_id int(11) default NULL,
+ # id int NOT NULL auto_increment,
+ # account_id int default NULL,
# name varchar default NULL,
# PRIMARY KEY (id)
# )
#
# CREATE TABLE accounts (
- # id int(11) NOT NULL auto_increment,
+ # id int NOT NULL auto_increment,
# name varchar default NULL,
# PRIMARY KEY (id)
# )
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 2b7e4f28c5..021bc32237 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -2,8 +2,7 @@ require 'active_support/core_ext/string/conversions'
module ActiveRecord
module Associations
- # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
- # ActiveRecord::Associations::ThroughAssociationScope
+ # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
class AliasTracker # :nodoc:
attr_reader :aliases
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 79b6b80d84..9994b72158 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -127,7 +127,7 @@ module ActiveRecord
# # ]
#
# person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
- # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
+ # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4
#
# person.pets.find(2) { |pet| pet.name.downcase! }
# # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
@@ -443,7 +443,7 @@ module ActiveRecord
# person.pets.delete_all
#
# Pet.find(1, 2, 3)
- # # => ActiveRecord::RecordNotFound
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
#
# If it is set to <tt>:delete_all</tt>, all the objects are deleted
# *without* calling their +destroy+ method.
@@ -463,7 +463,7 @@ module ActiveRecord
# person.pets.delete_all
#
# Pet.find(1, 2, 3)
- # # => ActiveRecord::RecordNotFound
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
def delete_all(dependent = nil)
@association.delete_all(dependent)
end
@@ -557,7 +557,7 @@ module ActiveRecord
# # => [#<Pet id: 2, name: "Spook", person_id: 1>]
#
# Pet.find(1, 3)
- # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
#
# If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
# *without* calling their +destroy+ method.
@@ -585,7 +585,7 @@ module ActiveRecord
# # ]
#
# Pet.find(1)
- # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
#
# You can pass +Fixnum+ or +String+ values, it finds the records
# responding to the +id+ and executes delete on them.
@@ -649,7 +649,7 @@ module ActiveRecord
# person.pets.size # => 0
# person.pets # => []
#
- # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
#
# You can pass +Fixnum+ or +String+ values, it finds the records
# responding to the +id+ and then deletes them from the database.
@@ -681,7 +681,7 @@ module ActiveRecord
# person.pets.size # => 0
# person.pets # => []
#
- # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
def destroy(*records)
@association.destroy(*records)
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 38bda0d2a5..7da20d8eea 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -103,7 +103,7 @@ module ActiveRecord
counter = reflection.counter_cache_column
owner[counter] ||= 0
owner[counter] += difference
- owner.send(:clear_attribute_changes, counter) # eww
+ owner.send(:clear_attribute_change, counter) # eww
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index b1e05c32b5..0fe9b2e81b 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,5 +1,5 @@
module ActiveRecord
- # = Active Record Belongs To Has One Association
+ # = Active Record Has One Association
module Associations
class HasOneAssociation < SingularAssociation #:nodoc:
include ForeignAssociation
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 1dc8bff193..92792a7a15 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -154,7 +154,7 @@ module ActiveRecord
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
end
- scope.unscope_values = Array(values[:unscope])
+ scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
klass.default_scoped.merge(scope)
end
end
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 73dd3fa041..74b44810d4 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -34,14 +34,10 @@ module ActiveRecord
def value
# `defined?` is cheaper than `||=` when we get back falsy values
- @value = original_value unless defined?(@value)
+ @value = type_cast(value_before_type_cast) unless defined?(@value)
@value
end
- def original_value
- type_cast(value_before_type_cast)
- end
-
def value_for_database
type.serialize(value)
end
@@ -55,6 +51,7 @@ module ActiveRecord
end
def with_value_from_user(value)
+ type.assert_valid_value(value)
self.class.from_user(name, value, type)
end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index e0bee8c17e..501590cf0e 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -16,7 +16,7 @@ module ActiveRecord
end
end
- def changed_in_place_from?(old_value)
+ def changed_from?(old_value)
super || changed_from?(database_default.value)
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 0862306749..ca6ba18fe0 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/string/filters'
require 'mutex_m'
-require 'thread_safe'
+require 'concurrent'
module ActiveRecord
# = Active Record Attribute Methods
@@ -37,7 +37,7 @@ module ActiveRecord
class AttributeMethodCache
def initialize
@module = Module.new
- @method_cache = ThreadSafe::Cache.new
+ @method_cache = Concurrent::Map.new
end
def [](name)
@@ -96,7 +96,7 @@ module ActiveRecord
end
end
- # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
+ # Raises an <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
# \Active \Record method is defined in the model, otherwise +false+.
#
# class Person < ActiveRecord::Base
@@ -106,7 +106,7 @@ module ActiveRecord
# end
#
# Person.instance_method_already_implemented?(:save)
- # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
+ # # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
#
# Person.instance_method_already_implemented?(:name)
# # => false
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 0171ef3bdf..43c15841c2 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_record/attribute_mutation_tracker'
module ActiveRecord
module AttributeMethods
@@ -38,19 +39,40 @@ module ActiveRecord
end
end
+ def init_internals
+ super
+ @mutation_tracker = AttributeMutationTracker.new(@attributes, @attributes.dup)
+ end
+
def initialize_dup(other) # :nodoc:
super
- calculate_changes_from_defaults
+ @mutation_tracker = AttributeMutationTracker.new(@attributes,
+ self.class._default_attributes.deep_dup)
end
def changes_applied
- super
- store_original_raw_attributes
+ @previous_mutation_tracker = @mutation_tracker
+ @changed_attributes = HashWithIndifferentAccess.new
+ store_original_attributes
end
def clear_changes_information
+ @previous_mutation_tracker = nil
+ @changed_attributes = HashWithIndifferentAccess.new
+ store_original_attributes
+ end
+
+ def raw_write_attribute(attr_name, *)
+ result = super
+ clear_attribute_change(attr_name)
+ result
+ end
+
+ def clear_attribute_changes(attr_names)
super
- original_raw_attributes.clear
+ attr_names.each do |attr_name|
+ clear_attribute_change(attr_name)
+ end
end
def changed_attributes
@@ -59,7 +81,7 @@ module ActiveRecord
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
- super.reverse_merge(attributes_changed_in_place).freeze
+ super.reverse_merge(@mutation_tracker.changed_values).freeze
end
end
@@ -69,59 +91,22 @@ module ActiveRecord
end
end
+ def previous_changes
+ previous_mutation_tracker.changes
+ end
+
def attribute_changed_in_place?(attr_name)
- old_value = original_raw_attribute(attr_name)
- @attributes[attr_name].changed_in_place_from?(old_value)
+ @mutation_tracker.changed_in_place?(attr_name)
end
private
def changes_include?(attr_name)
- super || attribute_changed_in_place?(attr_name)
- end
-
- def calculate_changes_from_defaults
- @changed_attributes = nil
- self.class.column_defaults.each do |attr, orig_value|
- set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
- end
- end
-
- # Wrap write_attribute to remember original attribute value.
- def write_attribute(attr, value)
- attr = attr.to_s
-
- old_value = old_attribute_value(attr)
-
- result = super
- store_original_raw_attribute(attr)
- save_changed_attribute(attr, old_value)
- result
- end
-
- def raw_write_attribute(attr, value)
- attr = attr.to_s
-
- result = super
- original_raw_attributes[attr] = value
- result
- end
-
- def save_changed_attribute(attr, old_value)
- clear_changed_attributes_cache
- if attribute_changed_by_setter?(attr)
- clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
- else
- set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
- end
+ super || @mutation_tracker.changed?(attr_name)
end
- def old_attribute_value(attr)
- if attribute_changed?(attr)
- changed_attributes[attr]
- else
- clone_attribute_value(:_read_attribute, attr)
- end
+ def clear_attribute_change(attr_name)
+ @mutation_tracker.forget_change(attr_name)
end
def _update_record(*)
@@ -136,41 +121,12 @@ module ActiveRecord
changed & self.class.column_names
end
- def _field_changed?(attr, old_value)
- @attributes[attr].changed_from?(old_value)
- end
-
- def attributes_changed_in_place
- changed_in_place.each_with_object({}) do |attr_name, h|
- orig = @attributes[attr_name].original_value
- h[attr_name] = orig
- end
- end
-
- def changed_in_place
- self.class.attribute_names.select do |attr_name|
- attribute_changed_in_place?(attr_name)
- end
- end
-
- def original_raw_attribute(attr_name)
- original_raw_attributes.fetch(attr_name) do
- read_attribute_before_type_cast(attr_name)
- end
- end
-
- def original_raw_attributes
- @original_raw_attributes ||= {}
+ def store_original_attributes
+ @mutation_tracker = @mutation_tracker.now_tracking(@attributes)
end
- def store_original_raw_attribute(attr_name)
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
- end
-
- def store_original_raw_attributes
- attribute_names.each do |attr|
- store_original_raw_attribute(attr)
- end
+ def previous_mutation_tracker
+ @previous_mutation_tracker ||= NullMutationTracker.new
end
def cache_changed_attributes
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index 553122a5fc..10498f4322 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -19,7 +19,7 @@ module ActiveRecord
if Numeric === value || value !~ /[^0-9]/
!value.to_i.zero?
else
- return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
!value.blank?
end
elsif value.respond_to?(:zero?)
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 f9beb43e4b..9e693b6aee 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -13,7 +13,7 @@ module ActiveRecord
set_time_zone_without_conversion(super)
elsif value.respond_to?(:in_time_zone)
begin
- user_input_in_time_zone(value) || super
+ super(user_input_in_time_zone(value)) || super
rescue ArgumentError
nil
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 07d5e7d38e..bbf2a51a0e 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -45,7 +45,7 @@ module ActiveRecord
write_attribute_with_type_cast(attr_name, value, true)
end
- def raw_write_attribute(attr_name, value)
+ def raw_write_attribute(attr_name, value) # :nodoc:
write_attribute_with_type_cast(attr_name, value, false)
end
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
new file mode 100644
index 0000000000..08eb8ba7ac
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -0,0 +1,84 @@
+module ActiveRecord
+ class AttributeMutationTracker # :nodoc:
+ def initialize(attributes, original_attributes)
+ @attributes = attributes
+ @original_attributes = original_attributes
+ end
+
+ def changed_values
+ attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
+ if changed?(attr_name)
+ result[attr_name] = original_attributes.fetch_value(attr_name)
+ end
+ end
+ end
+
+ def changes
+ attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
+ if changed?(attr_name)
+ result[attr_name] = [original_attributes.fetch_value(attr_name), attributes.fetch_value(attr_name)]
+ end
+ end
+ end
+
+ def changed?(attr_name)
+ attr_name = attr_name.to_s
+ modified?(attr_name) || changed_in_place?(attr_name)
+ end
+
+ def changed_in_place?(attr_name)
+ original_database_value = original_attributes[attr_name].value_before_type_cast
+ attributes[attr_name].changed_in_place_from?(original_database_value)
+ end
+
+ def forget_change(attr_name)
+ attr_name = attr_name.to_s
+ original_attributes[attr_name] = attributes[attr_name].dup
+ end
+
+ def now_tracking(new_attributes)
+ AttributeMutationTracker.new(new_attributes, clean_copy_of(new_attributes))
+ end
+
+ protected
+
+ attr_reader :attributes, :original_attributes
+
+ private
+
+ def attr_names
+ attributes.keys
+ end
+
+ def modified?(attr_name)
+ attributes[attr_name].changed_from?(original_attributes.fetch_value(attr_name))
+ end
+
+ def clean_copy_of(attributes)
+ attributes.map do |attr|
+ attr.with_value_from_database(attr.value_for_database)
+ end
+ end
+ end
+
+ class NullMutationTracker # :nodoc:
+ def changed_values
+ {}
+ end
+
+ def changes
+ {}
+ end
+
+ def changed?(*)
+ false
+ end
+
+ def changed_in_place?(*)
+ false
+ end
+
+ def forget_change(*)
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 013a7d0e01..026b3cf014 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -60,8 +60,14 @@ module ActiveRecord
super
end
+ def deep_dup
+ dup.tap do |copy|
+ copy.instance_variable_set(:@attributes, attributes.deep_dup)
+ end
+ end
+
def initialize_dup(_)
- @attributes = attributes.deep_dup
+ @attributes = attributes.dup
super
end
@@ -80,6 +86,11 @@ module ActiveRecord
attributes.select { |_, attr| attr.has_been_read? }.keys
end
+ def map(&block)
+ new_attributes = attributes.transform_values(&block)
+ AttributeSet.new(new_attributes)
+ end
+
protected
attr_reader :attributes
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 0c730d313f..f974b7a876 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -47,8 +47,14 @@ module ActiveRecord
delegate_hash[key] = value
end
+ def deep_dup
+ dup.tap do |copy|
+ copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
+ end
+ end
+
def initialize_dup(_)
- @delegate_hash = delegate_hash.transform_values(&:dup)
+ @delegate_hash = Hash[delegate_hash]
super
end
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 9ea22ed798..2456b8ad8c 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -14,10 +14,7 @@ module ActiveRecord
def dump(obj)
return if obj.nil?
- unless obj.is_a?(object_class)
- raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
- end
+ assert_valid_value(obj)
YAML.dump obj
end
@@ -26,15 +23,19 @@ module ActiveRecord
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
obj = YAML.load(yaml)
- unless obj.is_a?(object_class) || obj.nil?
- raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
- end
+ assert_valid_value(obj)
obj ||= object_class.new if object_class != Object
obj
end
+ def assert_valid_value(obj)
+ unless obj.nil? || obj.is_a?(object_class)
+ raise SerializationTypeMismatch,
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
+ end
+ end
+
private
def check_arity_of_constructor
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 6544b64f52..b579bc1e93 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,5 +1,5 @@
require 'thread'
-require 'thread_safe'
+require 'concurrent'
require 'monitor'
module ActiveRecord
@@ -337,7 +337,7 @@ module ActiveRecord
# that case +conn.owner+ attr should be consulted.
# Access and modification of +@thread_cached_conns+ does not require
# synchronization.
- @thread_cached_conns = ThreadSafe::Cache.new(:initial_capacity => @size)
+ @thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size)
@connections = []
@automatic_reconnect = true
@@ -824,11 +824,11 @@ module ActiveRecord
# These caches are keyed by klass.name, NOT klass. Keying them by klass
# alone would lead to memory leaks in development mode as all previous
# instances of the class would stay in memory.
- @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
- h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
+ @owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
+ h[k] = Concurrent::Map.new(:initial_capacity => 2)
end
- @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
- h[k] = ThreadSafe::Cache.new
+ @class_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
+ h[k] = Concurrent::Map.new
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 18d943f452..0ba4d94e3c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -14,8 +14,10 @@ module ActiveRecord
send m, o
end
- delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, to: :@conn
- private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql
+ delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
+ :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options, to: :@conn
+ private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
+ :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options
private
@@ -38,17 +40,32 @@ module ActiveRecord
end
def visit_TableDefinition(o)
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
- create_sql << "#{quote_table_name(o.name)} "
- create_sql << "(#{o.columns.map { |c| accept c }.join(', ')}) " unless o.as
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} "
+
+ statements = o.columns.map { |c| accept c }
+ statements << accept(o.primary_keys) if o.primary_keys
+
+ if supports_indexes_in_create?
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
+ end
+
+ if supports_foreign_keys?
+ statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
+ end
+
+ create_sql << "(#{statements.join(', ')}) " if statements.present?
create_sql << "#{o.options}"
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
end
- def visit_AddForeignKey(o)
+ def visit_PrimaryKeyDefinition(o)
+ "PRIMARY KEY (#{o.name.join(', ')})"
+ end
+
+ def visit_ForeignKeyDefinition(o)
sql = <<-SQL.strip_heredoc
- ADD CONSTRAINT #{quote_column_name(o.name)}
+ CONSTRAINT #{quote_column_name(o.name)}
FOREIGN KEY (#{quote_column_name(o.column)})
REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
SQL
@@ -57,6 +74,10 @@ module ActiveRecord
sql
end
+ def visit_AddForeignKey(o)
+ "ADD #{accept(o)}"
+ end
+
def visit_DropForeignKey(name)
"DROP CONSTRAINT #{quote_column_name(name)}"
end
@@ -89,8 +110,9 @@ module ActiveRecord
sql
end
- def options_include_default?(options)
- options.include?(:default) && !(options[:null] == false && options[:default].nil?)
+ def foreign_key_in_create(from_table, to_table, options)
+ options = foreign_key_options(from_table, to_table, options)
+ accept ForeignKeyDefinition.new(from_table, to_table, options)
end
def action_sql(action, dependency)
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 2c76dd1518..10329de5f4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -23,6 +23,9 @@ module ActiveRecord
class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
end
+ class PrimaryKeyDefinition < Struct.new(:name) # :nodoc:
+ end
+
class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
def name
options[:name]
@@ -207,6 +210,7 @@ module ActiveRecord
@columns_hash = {}
@indexes = {}
@foreign_keys = {}
+ @primary_keys = nil
@native = types
@temporary = temporary
@options = options
@@ -214,6 +218,11 @@ module ActiveRecord
@name = name
end
+ def primary_keys(name = nil) # :nodoc:
+ @primary_keys = PrimaryKeyDefinition.new(name) if name
+ @primary_keys
+ end
+
# Returns an array of ColumnDefinition objects for the columns of the table.
def columns; @columns_hash.values; end
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 b944a8631c..a2f58364a4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -18,7 +18,7 @@ module ActiveRecord
spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) })
end
- # This can be overridden on a Adapter level basis to support other
+ # This can be overridden on an Adapter level basis to support other
# extended datatypes (Example: Adding an array option in the
# PostgreSQLAdapter)
def prepare_column_options(column)
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 16aefc94ab..7e53f8958a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -23,6 +23,20 @@ module ActiveRecord
table_name[0...table_alias_length].tr('.', '_')
end
+ # Returns the relation names useable to back Active Record models.
+ # For most adapters this means all #tables and #views.
+ def data_sources
+ tables | views
+ end
+
+ # Checks to see if the data source +name+ exists on the database.
+ #
+ # data_source_exists?(:ebooks)
+ #
+ def data_source_exists?(name)
+ data_sources.include?(name.to_s)
+ end
+
# Returns an array of table names defined in the database.
def tables(name = nil)
raise NotImplementedError, "#tables is not implemented"
@@ -36,6 +50,19 @@ module ActiveRecord
tables.include?(table_name.to_s)
end
+ # Returns an array of view names defined in the database.
+ def views
+ raise NotImplementedError, "#views is not implemented"
+ end
+
+ # Checks to see if the view +view_name+ exists on the database.
+ #
+ # view_exists?(:ebooks)
+ #
+ def view_exists?(view_name)
+ views.include?(view_name.to_s)
+ end
+
# Returns an array of indexes for the given table.
# def indexes(table_name, name = nil) end
@@ -93,6 +120,12 @@ module ActiveRecord
(!options.key?(:null) || c.null == options[:null]) }
end
+ # Returns just a table's primary key
+ def primary_key(table_name)
+ pks = primary_keys(table_name)
+ pks.first if pks.one?
+ end
+
# Creates a new table with the name +table_name+. +table_name+ may either
# be a String or a Symbol.
#
@@ -158,7 +191,7 @@ module ActiveRecord
# generates:
#
# CREATE TABLE suppliers (
- # id int(11) auto_increment PRIMARY KEY
+ # id int auto_increment PRIMARY KEY
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
#
# ====== Rename the primary key column
@@ -170,7 +203,7 @@ module ActiveRecord
# generates:
#
# CREATE TABLE objects (
- # guid int(11) auto_increment PRIMARY KEY,
+ # guid int auto_increment PRIMARY KEY,
# name varchar(80)
# )
#
@@ -220,7 +253,11 @@ module ActiveRecord
Base.get_primary_key table_name.to_s.singularize
end
- td.primary_key pk, options.fetch(:id, :primary_key), options
+ if pk.is_a?(Array)
+ td.primary_keys pk
+ else
+ td.primary_key pk, options.fetch(:id, :primary_key), options
+ end
end
yield td if block_given?
@@ -237,10 +274,6 @@ module ActiveRecord
end
end
- td.foreign_keys.each_pair do |other_table_name, foreign_key_options|
- add_foreign_key(table_name, other_table_name, foreign_key_options)
- end
-
result
end
@@ -320,7 +353,7 @@ module ActiveRecord
# [<tt>:bulk</tt>]
# Set this to true to make this a bulk alter query, such as
#
- # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
+ # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
#
# Defaults to false.
#
@@ -474,7 +507,7 @@ module ActiveRecord
raise NotImplementedError, "change_column_default is not implemented"
end
- # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag
+ # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
# indicates whether the value can be +NULL+. For example
#
# change_column_null(:users, :nickname, false)
@@ -486,7 +519,7 @@ module ActiveRecord
# allows them to be +NULL+ (drops the constraint).
#
# The method accepts an optional fourth argument to replace existing
- # +NULL+s with some other value. Use that one when enabling the
+ # <tt>NULL</tt>s with some other value. Use that one when enabling the
# constraint if needed, since otherwise those rows would not be valid.
#
# Please note the fourth argument does not set a column's default.
@@ -540,6 +573,8 @@ module ActiveRecord
#
# CREATE INDEX by_name ON accounts(name(10))
#
+ # ====== Creating an index with specific key lengths for multiple keys
+ #
# add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
#
# generates:
@@ -769,15 +804,7 @@ module ActiveRecord
def add_foreign_key(from_table, to_table, options = {})
return unless supports_foreign_keys?
- options[:column] ||= foreign_key_column_for(to_table)
-
- options = {
- column: options[:column],
- primary_key: options[:primary_key],
- name: foreign_key_name(from_table, options),
- on_delete: options[:on_delete],
- on_update: options[:on_update]
- }
+ options = foreign_key_options(from_table, to_table, options)
at = create_alter_table from_table
at.add_foreign_key to_table, options
@@ -845,6 +872,13 @@ module ActiveRecord
"#{name.singularize}_id"
end
+ def foreign_key_options(from_table, to_table, options) # :nodoc:
+ options = options.dup
+ options[:column] ||= foreign_key_column_for(to_table)
+ options[:name] ||= foreign_key_name(from_table, options)
+ options
+ end
+
def dump_schema_information #:nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
@@ -986,6 +1020,10 @@ module ActiveRecord
[index_name, index_type, index_columns, index_options, algorithm, using]
end
+ def options_include_default?(options)
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -1012,10 +1050,6 @@ module ActiveRecord
column_names.map {|name| quote_column_name(name) + option_strings[name]}
end
- def options_include_default?(options)
- options.include?(:default) && !(options[:null] == false && options[:default].nil?)
- end
-
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
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 597f0d0597..79a24d1996 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -14,10 +14,26 @@ module ActiveRecord
def json(*args, **options)
args.each { |name| column(name, :json, options) }
end
+
+ def unsigned_integer(*args, **options)
+ args.each { |name| column(name, :unsigned_integer, options) }
+ end
+
+ def unsigned_bigint(*args, **options)
+ args.each { |name| column(name, :unsigned_bigint, options) }
+ end
+
+ def unsigned_float(*args, **options)
+ args.each { |name| column(name, :unsigned_float, options) }
+ end
+
+ def unsigned_decimal(*args, **options)
+ args.each { |name| column(name, :unsigned_decimal, options) }
+ end
end
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :charset
+ attr_accessor :charset, :unsigned
end
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
@@ -29,7 +45,11 @@ module ActiveRecord
when :primary_key
column.type = :integer
column.auto_increment = true
+ when /\Aunsigned_(?<type>.+)\z/
+ column.type = $~[:type].to_sym
+ column.unsigned = true
end
+ column.unsigned ||= options[:unsigned]
column.charset = options[:charset]
column
end
@@ -52,17 +72,9 @@ module ActiveRecord
"DROP FOREIGN KEY #{name}"
end
- def visit_TableDefinition(o)
- name = o.name
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
-
- statements = o.columns.map { |c| accept c }
- statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
-
- create_sql << "(#{statements.join(', ')}) " if statements.present?
- create_sql << "#{o.options}"
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
- create_sql
+ def visit_ColumnDefinition(o)
+ o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned)
+ super
end
def visit_AddColumnDefinition(o)
@@ -117,6 +129,7 @@ module ActiveRecord
spec = {}
if column.auto_increment?
spec[:id] = ':bigint' if column.bigint?
+ spec[:unsigned] = 'true' if column.unsigned?
return if spec.empty?
else
spec[:id] = column.type.inspect
@@ -125,6 +138,16 @@ module ActiveRecord
spec
end
+ def prepare_column_options(column)
+ spec = super
+ spec[:unsigned] = 'true' if column.unsigned?
+ spec
+ end
+
+ def migration_keys
+ super + [:unsigned]
+ end
+
private
def schema_limit(column)
@@ -171,6 +194,10 @@ module ActiveRecord
sql_type =~ /blob/i || type == :text
end
+ def unsigned?
+ /unsigned/ === sql_type
+ end
+
def case_sensitive?
collation && !collation.match(/_ci$/)
end
@@ -246,7 +273,7 @@ module ActiveRecord
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
NATIVE_DATABASE_TYPES = {
- primary_key: "int(11) auto_increment PRIMARY KEY",
+ primary_key: "int auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
text: { name: "text" },
integer: { name: "int", limit: 4 },
@@ -316,6 +343,10 @@ module ActiveRecord
version >= '5.0.0'
end
+ def supports_explain?
+ true
+ end
+
def supports_indexes_in_create?
true
end
@@ -417,6 +448,80 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
#++
+ def explain(arel, binds = [])
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
+ start = Time.now
+ result = exec_query(sql, 'EXPLAIN', binds)
+ elapsed = Time.now - start
+
+ ExplainPrettyPrinter.new.pp(result, elapsed)
+ end
+
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
+ # MySQL shell:
+ #
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # 2 rows in set (0.00 sec)
+ #
+ # This is an exercise in Ruby hyperrealism :).
+ def pp(result, elapsed)
+ widths = compute_column_widths(result)
+ separator = build_separator(widths)
+
+ pp = []
+
+ pp << separator
+ pp << build_cells(result.columns, widths)
+ pp << separator
+
+ result.rows.each do |row|
+ pp << build_cells(row, widths)
+ end
+
+ pp << separator
+ pp << build_footer(result.rows.length, elapsed)
+
+ pp.join("\n") + "\n"
+ end
+
+ private
+
+ def compute_column_widths(result)
+ [].tap do |widths|
+ result.columns.each_with_index do |column, i|
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
+ widths << cells_in_column.map(&:length).max
+ end
+ end
+ end
+
+ def build_separator(widths)
+ padding = 1
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
+ end
+
+ def build_cells(items, widths)
+ cells = []
+ items.each_with_index do |item, i|
+ item = 'NULL' if item.nil?
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
+ cells << item.to_s.send(justifier, widths[i])
+ end
+ '| ' + cells.join(' | ') + ' |'
+ end
+
+ def build_footer(nrows, elapsed)
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
+ end
+ end
+
def clear_cache!
super
reload_type_map
@@ -520,33 +625,42 @@ module ActiveRecord
show_variable 'collation_database'
end
- def tables(name = nil, database = nil, like = nil) #:nodoc:
- sql = "SHOW TABLES "
- sql << "IN #{quote_table_name(database)} " if database
- sql << "LIKE #{quote(like)}" if like
-
- execute_and_free(sql, 'SCHEMA') do |result|
- result.collect(&:first)
- end
+ def tables(name = nil) # :nodoc:
+ select_values("SHOW FULL TABLES", 'SCHEMA')
end
+ alias data_sources tables
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
end
- def table_exists?(name)
- return false unless name.present?
- return true if tables(nil, nil, name).any?
+ def table_exists?(table_name)
+ return false unless table_name.present?
- name = name.to_s
- schema, table = name.split('.', 2)
+ schema, name = table_name.to_s.split('.', 2)
+ schema, name = @config[:database], schema unless name # A table was provided without a schema
- unless table # A table was provided without a schema
- table = schema
- schema = nil
- end
+ sql = "SELECT table_name FROM information_schema.tables "
+ sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
+
+ select_values(sql, 'SCHEMA').any?
+ end
+ alias data_source_exists? table_exists?
- tables(nil, schema, table).any?
+ def views # :nodoc:
+ select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
+ end
+
+ def view_exists?(view_name) # :nodoc:
+ return false unless view_name.present?
+
+ schema, name = view_name.to_s.split('.', 2)
+ schema, name = @config[:database], schema unless name # A view was provided without a schema
+
+ sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
+ sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
+
+ select_values(sql, 'SCHEMA').any?
end
# Returns an array of indexes for the given table.
@@ -685,7 +799,7 @@ module ActiveRecord
AND fk.table_name = '#{table_name}'
SQL
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ create_table_info = create_table_info(table_name)
fk_info.map do |row|
options = {
@@ -702,7 +816,7 @@ module ActiveRecord
end
def table_options(table_name)
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ create_table_info = create_table_info(table_name)
# strip create_definitions and partition_options
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
@@ -712,8 +826,8 @@ module ActiveRecord
end
# Maps logical Rails types to MySQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- case type.to_s
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
+ sql = case type.to_s
when 'binary'
binary_to_sql(limit)
when 'integer'
@@ -721,8 +835,11 @@ module ActiveRecord
when 'text'
text_to_sql(limit)
else
- super
+ super(type, limit, precision, scale)
end
+
+ sql << ' unsigned' if unsigned && type != :primary_key
+ sql
end
# SHOW VARIABLES LIKE 'name'
@@ -735,21 +852,25 @@ module ActiveRecord
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table)
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
- create_table = each_hash(result).first[:"Create Table"]
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
- keys = $1.split(",").map { |key| key.delete('`"') }
- keys.length == 1 ? [keys.first, nil] : nil
- else
- nil
- end
+ if pk = primary_key(table)
+ [ pk, nil ]
end
end
- # Returns just a table's primary key
- def primary_key(table)
- pk_and_sequence = pk_and_sequence_for(table)
- pk_and_sequence && pk_and_sequence.first
+ def primary_keys(table_name) # :nodoc:
+ raise ArgumentError unless table_name.present?
+
+ schema, name = table_name.to_s.split('.', 2)
+ schema, name = @config[:database], schema unless name # A table was provided without a schema
+
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT column_name
+ FROM information_schema.key_column_usage
+ WHERE constraint_name = 'PRIMARY'
+ AND table_schema = #{quote(schema)}
+ AND table_name = #{quote(name)}
+ ORDER BY ordinal_position
+ SQL
end
def case_sensitive_modifier(node, table_attribute)
@@ -807,7 +928,6 @@ module ActiveRecord
register_integer_type m, %r(^tinyint)i, limit: 1
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
- m.alias_type %r(set)i, 'varchar'
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
@@ -816,6 +936,12 @@ module ActiveRecord
.split(',').map{|enum| enum.strip.length - 2}.max
MysqlString.new(limit: limit)
end
+
+ m.register_type(%r(^set)i) do |sql_type|
+ limit = sql_type[/^set\((.+)\)/i, 1]
+ .split(',').map{|set| set.strip.length - 1}.sum - 1
+ MysqlString.new(limit: limit)
+ end
end
def register_integer_type(mapping, key, options) # :nodoc:
@@ -1018,6 +1144,11 @@ module ActiveRecord
end
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"]
+ end
+
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
TableDefinition.new(native_database_types, name, temporary, options, as)
end
@@ -1036,8 +1167,9 @@ module ActiveRecord
when 1; 'tinyint'
when 2; 'smallint'
when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when nil, 4; 'int'
when 5..8; 'bigint'
+ when 11; 'int(11)' # backward compatibility with Rails 2.0
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
end
end
@@ -1084,6 +1216,8 @@ module ActiveRecord
ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql)
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 4b95b0681d..5e31efec4a 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,20 +5,13 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
-
- module Format
- ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
- ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
- end
-
attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
# Instantiates a new column in the table.
#
- # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
+ # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int</tt>.
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 734b384e80..4461722bb4 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -37,10 +37,6 @@ module ActiveRecord
configure_connection
end
- def supports_explain?
- true
- end
-
def supports_json?
version >= '5.7.8'
end
@@ -99,80 +95,6 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
#++
- def explain(arel, binds = [])
- sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
- start = Time.now
- result = exec_query(sql, 'EXPLAIN', binds)
- elapsed = Time.now - start
-
- ExplainPrettyPrinter.new.pp(result, elapsed)
- end
-
- class ExplainPrettyPrinter # :nodoc:
- # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
- # MySQL shell:
- #
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
- # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # 2 rows in set (0.00 sec)
- #
- # This is an exercise in Ruby hyperrealism :).
- def pp(result, elapsed)
- widths = compute_column_widths(result)
- separator = build_separator(widths)
-
- pp = []
-
- pp << separator
- pp << build_cells(result.columns, widths)
- pp << separator
-
- result.rows.each do |row|
- pp << build_cells(row, widths)
- end
-
- pp << separator
- pp << build_footer(result.rows.length, elapsed)
-
- pp.join("\n") + "\n"
- end
-
- private
-
- def compute_column_widths(result)
- [].tap do |widths|
- result.columns.each_with_index do |column, i|
- cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
- widths << cells_in_column.map(&:length).max
- end
- end
- end
-
- def build_separator(widths)
- padding = 1
- '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
- end
-
- def build_cells(items, widths)
- cells = []
- items.each_with_index do |item, i|
- item = 'NULL' if item.nil?
- justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
- cells << item.to_s.send(justifier, widths[i])
- end
- '| ' + cells.join(' | ') + ' |'
- end
-
- def build_footer(nrows, elapsed)
- rows_label = nrows == 1 ? 'row' : 'rows'
- "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
- end
- end
-
# 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
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index e4fcff8814..b3894481cc 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -80,8 +80,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super
- @statements = StatementPool.new(@connection,
- self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@client_encoding = nil
connect
end
@@ -162,6 +161,14 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
#++
+ def select_all(arel, name = nil, binds = [])
+ if ExplainRegistry.collect? && prepared_statements
+ unprepared_statement { super }
+ else
+ super
+ end
+ end
+
def select_rows(sql, name = nil, binds = [])
@connection.query_with_result = true
rows = exec_query(sql, name, binds).rows
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
index 2c04c46131..424769f765 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
@@ -4,18 +4,14 @@ module ActiveRecord
module OID # :nodoc:
class DateTime < Type::DateTime # :nodoc:
def cast_value(value)
- if value.is_a?(::String)
- case value
- when 'infinity' then ::Float::INFINITY
- when '-infinity' then -::Float::INFINITY
- when / BC$/
- astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
- super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
- else
- super
- end
+ case value
+ when 'infinity' then ::Float::INFINITY
+ when '-infinity' then -::Float::INFINITY
+ when / BC$/
+ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
+ super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
else
- value
+ super
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 69aa02ccf4..aaf5b2898b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -73,6 +73,16 @@ module ActiveRecord
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
+ def data_sources # :nodoc
+ select_values(<<-SQL, 'SCHEMA')
+ SELECT c.relname
+ FROM pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
+ AND n.nspname = ANY (current_schemas(false))
+ SQL
+ end
+
# Returns true if table exists.
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
@@ -89,6 +99,31 @@ module ActiveRecord
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
+ alias data_source_exists? table_exists?
+
+ def views # :nodoc:
+ select_values(<<-SQL, 'SCHEMA')
+ SELECT c.relname
+ FROM pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
+ AND n.nspname = ANY (current_schemas(false))
+ SQL
+ end
+
+ def view_exists?(view_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(view_name.to_s)
+ return false unless name.identifier
+
+ select_values(<<-SQL, 'SCHEMA').any?
+ SELECT c.relname
+ FROM pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
+ AND c.relname = '#{name.identifier}'
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
+ SQL
+ end
def drop_table(table_name, options = {}) # :nodoc:
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
@@ -353,17 +388,19 @@ module ActiveRecord
nil
end
- # Returns just a table's primary key
- def primary_key(table)
- pks = query(<<-end_sql, 'SCHEMA')
- SELECT attr.attname
- FROM pg_attribute attr
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
- WHERE cons.contype = 'p'
- AND cons.conrelid = '#{quote_table_name(table)}'::regclass
- end_sql
- return nil unless pks.count == 1
- pks[0][0]
+ def primary_keys(table_name) # :nodoc:
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
+ WITH pk_constraint AS (
+ SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
+ WHERE contype = 'p'
+ AND conrelid = '#{quote_table_name(table_name)}'::regclass
+ ), cons AS (
+ SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
+ )
+ SELECT attr.attname FROM pg_attribute attr
+ INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
+ ORDER BY cons.rownum
+ SQL
end
# Renames a table.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 27291bd2ea..25dfda9ef8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -211,7 +211,8 @@ module ActiveRecord
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
- super
+ super(max)
+ @connection = connection
@counter = 0
end
@@ -760,7 +761,7 @@ module ActiveRecord
end
def extract_table_ref_from_insert_sql(sql) # :nodoc:
- sql[/into\s+([^\(]*).*values\s*\(/im]
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
$1.strip if $1
end
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 981d5d7a3c..eee142378c 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -10,7 +10,7 @@ module ActiveRecord
@columns = {}
@columns_hash = {}
@primary_keys = {}
- @tables = {}
+ @data_sources = {}
end
def initialize_dup(other)
@@ -18,33 +18,38 @@ module ActiveRecord
@columns = @columns.dup
@columns_hash = @columns_hash.dup
@primary_keys = @primary_keys.dup
- @tables = @tables.dup
+ @data_sources = @data_sources.dup
end
def primary_keys(table_name)
- @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
+ @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
end
# A cached lookup for table existence.
- def table_exists?(name)
- prepare_tables if @tables.empty?
- return @tables[name] if @tables.key? name
+ def data_source_exists?(name)
+ prepare_data_sources if @data_sources.empty?
+ return @data_sources[name] if @data_sources.key? name
- @tables[name] = connection.table_exists?(name)
+ @data_sources[name] = connection.data_source_exists?(name)
end
+ alias table_exists? data_source_exists?
+ deprecate :table_exists? => "use #data_source_exists? instead"
+
# Add internal cache for table with +table_name+.
def add(table_name)
- if table_exists?(table_name)
+ if data_source_exists?(table_name)
primary_keys(table_name)
columns(table_name)
columns_hash(table_name)
end
end
- def tables(name)
- @tables[name]
+ def data_sources(name)
+ @data_sources[name]
end
+ alias tables data_sources
+ deprecate :tables => "use #data_sources instead"
# Get the columns for a table
def columns(table_name)
@@ -64,36 +69,38 @@ module ActiveRecord
@columns.clear
@columns_hash.clear
@primary_keys.clear
- @tables.clear
+ @data_sources.clear
@version = nil
end
def size
- [@columns, @columns_hash, @primary_keys, @tables].map(&:size).inject :+
+ [@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
end
- # Clear out internal caches for table with +table_name+.
- def clear_table_cache!(table_name)
- @columns.delete table_name
- @columns_hash.delete table_name
- @primary_keys.delete table_name
- @tables.delete table_name
+ # Clear out internal caches for the data source +name+.
+ def clear_data_source_cache!(name)
+ @columns.delete name
+ @columns_hash.delete name
+ @primary_keys.delete name
+ @data_sources.delete name
end
+ alias clear_table_cache! clear_data_source_cache!
+ deprecate :clear_table_cache! => "use #clear_data_source_cache! instead"
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
@version = ActiveRecord::Migrator.current_version
- [@version, @columns, @columns_hash, @primary_keys, @tables]
+ [@version, @columns, @columns_hash, @primary_keys, @data_sources]
end
def marshal_load(array)
- @version, @columns, @columns_hash, @primary_keys, @tables = array
+ @version, @columns, @columns_hash, @primary_keys, @data_sources = array
end
private
- def prepare_tables
- connection.tables.each { |table| @tables[table] = true }
+ def prepare_data_sources
+ connection.data_sources.each { |source| @data_sources[source] = true }
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 24fc67938d..f6a6e3eb4d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -81,8 +81,7 @@ module ActiveRecord
super(connection, logger)
@active = nil
- @statements = StatementPool.new(@connection,
- self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@config = config
@visitor = Arel::Visitors::SQLite.new self
@@ -320,10 +319,25 @@ module ActiveRecord
row['name']
end
end
+ alias data_sources tables
def table_exists?(table_name)
table_name && tables(nil, table_name).any?
end
+ alias data_source_exists? table_exists?
+
+ def views # :nodoc:
+ select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
+ end
+
+ def view_exists?(view_name) # :nodoc:
+ return false unless view_name.present?
+
+ sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
+ sql << " AND name = #{quote(view_name)}"
+
+ select_values(sql, 'SCHEMA').any?
+ end
# Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name) #:nodoc:
@@ -369,10 +383,9 @@ module ActiveRecord
end
end
- def primary_key(table_name) #:nodoc:
+ def primary_keys(table_name) # :nodoc:
pks = table_structure(table_name).select { |f| f['pk'] > 0 }
- return nil unless pks.count == 1
- pks[0]['name']
+ pks.sort_by { |f| f['pk'] }.map { |f| f['name'] }
end
def remove_index(table_name, options = {}) #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
index 82e9ef3d3d..57463dd749 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -3,10 +3,9 @@ module ActiveRecord
class StatementPool
include Enumerable
- def initialize(connection, max = 1000)
+ def initialize(max = 1000)
@cache = Hash.new { |h,pid| h[pid] = {} }
- @connection = connection
- @max = max
+ @max = max
end
def each(&block)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index ffce2173ec..894d18b79e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -296,7 +296,7 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil)
- @attributes = self.class._default_attributes.dup
+ @attributes = self.class._default_attributes.deep_dup
self.class.define_attribute_methods
init_internals
@@ -366,7 +366,7 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
- @attributes = @attributes.dup
+ @attributes = @attributes.deep_dup
@attributes.reset(self.class.primary_key)
_run_initialize_callbacks
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 4902fcb1a2..10b5fcab24 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -118,7 +118,7 @@ module ActiveRecord
elsif mapping.has_value?(value)
mapping.key(value)
else
- raise ArgumentError, "'#{value}' is not a valid #{name}"
+ assert_valid_value(value)
end
end
@@ -131,6 +131,12 @@ module ActiveRecord
mapping.fetch(value, value)
end
+ def assert_valid_value(value)
+ unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
+ raise ArgumentError, "'#{value}' is not a valid #{name}"
+ end
+ end
+
protected
attr_reader :name, :mapping
@@ -204,30 +210,22 @@ module ActiveRecord
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
if klass_method && dangerous_class_method?(method_name)
- raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
- enum: enum_name,
- klass: self.name,
- type: 'class',
- method: method_name,
- source: 'Active Record'
- }
+ raise_conflict_error(enum_name, method_name, type: 'class')
elsif !klass_method && dangerous_attribute_method?(method_name)
- raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
- enum: enum_name,
- klass: self.name,
- type: 'instance',
- method: method_name,
- source: 'Active Record'
- }
+ raise_conflict_error(enum_name, method_name)
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
- raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
- enum: enum_name,
- klass: self.name,
- type: 'instance',
- method: method_name,
- source: 'another enum'
- }
+ raise_conflict_error(enum_name, method_name, source: 'another enum')
end
end
+
+ def raise_conflict_error(enum_name, method_name, type: 'instance', source: 'Active Record')
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
+ enum: enum_name,
+ klass: self.name,
+ type: type,
+ method: method_name,
+ source: source
+ }
+ end
end
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 15b2f65dcb..370c1562c9 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -84,7 +84,7 @@ module ActiveRecord
# Values longer than 20 characters will be truncated. The value
# is truncated word by word.
#
- # user = User.find_by(name: 'David HeinemeierHansson')
+ # user = User.find_by(name: 'David Heinemeier Hansson')
# user.id # => 125
# user_path(user) # => "/users/125-david"
#
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index a09437b4b0..2336d23a1c 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# p1.save
#
# p2.first_name = "should fail"
- # p2.save # Raises a ActiveRecord::StaleObjectError
+ # p2.save # Raises an ActiveRecord::StaleObjectError
#
# Optimistic locking will also check for stale data when objects are destroyed. Example:
#
@@ -32,7 +32,7 @@ module ActiveRecord
# p1.first_name = "Michael"
# p1.save
#
- # p2.destroy # Raises a ActiveRecord::StaleObjectError
+ # p2.destroy # Raises an ActiveRecord::StaleObjectError
#
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
# or otherwise apply the business logic needed to resolve the conflict.
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 5bdb7213cd..0c50826c63 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -231,7 +231,7 @@ module ActiveRecord
# * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
# specified by +index_name+.
# * <tt>add_reference(:table_name, :reference_name)</tt>: Adds a new column
- # +reference_name_id+ by default a integer. See
+ # +reference_name_id+ by default an integer. See
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details.
#
# == Irreversible transformations
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 2b0c755ef4..a9bd094a66 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -50,6 +50,13 @@ module ActiveRecord
class_attribute :pluralize_table_names, instance_writer: false
self.pluralize_table_names = true
+ ##
+ # :singleton-method:
+ # Accessor for the list of columns names the model should ignore. Ignored columns won't have attribute
+ # accessors defined, and won't be referenced in SQL queries.
+ class_attribute :ignored_columns, instance_accessor: false
+ self.ignored_columns = [].freeze
+
self.inheritance_column = 'type'
delegate :type_for_attribute, to: :class
@@ -213,7 +220,7 @@ module ActiveRecord
# Indicates whether the table associated with this class exists
def table_exists?
- connection.schema_cache.table_exists?(table_name)
+ connection.schema_cache.data_source_exists?(table_name)
end
def attributes_builder # :nodoc:
@@ -290,7 +297,7 @@ module ActiveRecord
def reset_column_information
connection.clear_cache!
undefine_attribute_methods
- connection.schema_cache.clear_table_cache!(table_name)
+ connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
end
@@ -308,7 +315,7 @@ module ActiveRecord
end
def load_schema!
- @columns_hash = connection.schema_cache.columns_hash(table_name)
+ @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns)
@columns_hash.each do |name, column|
warn_if_deprecated_type(column)
define_attribute(
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 74894d0c37..0b500346bc 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
module ActiveRecord
module NullRelation # :nodoc:
def exec_queries
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 09c36d7b4d..7b53f6e5a0 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -211,6 +211,7 @@ module ActiveRecord
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
+ became.instance_variable_set("@mutation_tracker", @mutation_tracker)
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d543a84ff1..d940ac244a 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -255,7 +255,7 @@ db_namespace = namespace :db do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
con.schema_cache.clear!
- con.tables.each { |table| con.schema_cache.add(table) }
+ con.data_sources.each { |table| con.schema_cache.add(table) }
open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) }
end
@@ -353,7 +353,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
- # desc 'Check for pending migrations and load the test schema'
+ # desc 'Load the test schema'
task :prepare => %w(environment load_config) do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 4e0fc407bc..65ec356735 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -175,6 +175,20 @@ module ActiveRecord
end
end
+ def inverse_of
+ return unless inverse_name
+
+ @inverse_of ||= klass._reflect_on_association inverse_name
+ end
+
+ def check_validity_of_inverse!
+ unless polymorphic?
+ if has_inverse? && inverse_of.nil?
+ raise InverseOfAssociationNotFoundError.new(self)
+ end
+ end
+ end
+
# This shit is nasty. We need to avoid the following situation:
#
# * An associated record is deleted via record.destroy
@@ -377,14 +391,6 @@ module ActiveRecord
check_validity_of_inverse!
end
- def check_validity_of_inverse!
- unless polymorphic?
- if has_inverse? && inverse_of.nil?
- raise InverseOfAssociationNotFoundError.new(self)
- end
- end
- end
-
def check_preloadable!
return unless scope
@@ -436,12 +442,6 @@ module ActiveRecord
inverse_name
end
- def inverse_of
- return unless inverse_name
-
- @inverse_of ||= klass._reflect_on_association inverse_name
- end
-
def polymorphic_inverse_of(associated_class)
if has_inverse?
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
@@ -924,6 +924,8 @@ module ActiveRecord
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
end
+ def inverse_name; delegate_reflection.send(:inverse_name); end
+
private
def derive_class_name
# get the class_name of the belongs_to association of the through reflection
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index bf08cdbbf3..e596df8742 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "arel/collectors/bind"
module ActiveRecord
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 0f6015fa93..69f39e5ba9 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -70,9 +70,9 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.sum(:age) # => 4562
- def sum(*args)
- return super if block_given?
- calculate(:sum, *args)
+ def sum(column_name = nil, &block)
+ return super(&block) if block_given?
+ calculate(:sum, column_name)
end
# This calculates aggregate values in the given column. Methods for count, sum, average,
@@ -338,7 +338,6 @@ module ActiveRecord
# column_alias_for("sum(id)") # => "sum_id"
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
# column_alias_for("count(*)") # => "count_all"
- # column_alias_for("count", "id") # => "count_id"
def column_alias_for(keys)
if keys.respond_to? :name
keys = "#{keys.relation.name}.#{keys.name}"
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index e232516b0c..39e7b42629 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -67,7 +67,7 @@ module ActiveRecord
# Arel::Nodes::And.new([range.start, range.end])
# )
# end
- # ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
+ # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler)
def register_handler(klass, handler)
@handlers.unshift([klass, handler])
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index e25b889851..eb53a18f0f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -250,7 +250,7 @@ module ActiveRecord
def _select!(*fields) # :nodoc:
fields.flatten!
fields.map! do |field|
- klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
+ klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
end
self.select_values += fields
self
@@ -264,7 +264,7 @@ module ActiveRecord
# Returns an array with distinct records based on the +group+ attribute:
#
# User.select([:id, :name])
- # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
+ # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
#
# User.group(:name)
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
@@ -713,7 +713,7 @@ module ActiveRecord
#
# users = User.readonly
# users.first.save
- # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
+ # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
def readonly(value = true)
spawn.readonly!(value)
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index ba75ffa5a1..7f6664ea50 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -4,7 +4,7 @@ module ActiveRecord
module ClassMethods
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
- def sanitize(object) #:nodoc:
+ def sanitize(object) # :nodoc:
connection.quote(object)
end
alias_method :quote_value, :sanitize
@@ -13,9 +13,16 @@ module ActiveRecord
# Accepts an array or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a WHERE clause.
- # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
- # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition, table_name = self.table_name)
+ #
+ # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
+ # # => "name='foo''bar' and group_id='4'"
+ def sanitize_sql_for_conditions(condition)
return nil if condition.blank?
case condition
@@ -28,7 +35,15 @@ module ActiveRecord
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a SET clause.
- # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
+ #
+ # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
+ # # => "name=NULL and group_id=4"
+ #
+ # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
+ # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
+ #
+ # sanitize_sql_for_assignment("name=NULL and group_id='4'")
+ # # => "name=NULL and group_id='4'"
def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
case assignments
when Array; sanitize_sql_array(assignments)
@@ -40,14 +55,18 @@ module ActiveRecord
# Accepts a hash of SQL conditions and replaces those attributes
# that correspond to a +composed_of+ relationship with their expanded
# aggregate attribute values.
+ #
# Given:
- # class Person < ActiveRecord::Base
- # composed_of :address, class_name: "Address",
- # mapping: [%w(address_street street), %w(address_city city)]
- # end
+ #
+ # class Person < ActiveRecord::Base
+ # composed_of :address, class_name: "Address",
+ # mapping: [%w(address_street street), %w(address_city city)]
+ # end
+ #
# Then:
- # { address: Address.new("813 abc st.", "chicago") }
- # # => { address_street: "813 abc st.", address_city: "chicago" }
+ #
+ # { address: Address.new("813 abc st.", "chicago") }
+ # # => { address_street: "813 abc st.", address_city: "chicago" }
def expand_hash_conditions_for_aggregates(attrs)
expanded_attrs = {}
attrs.each do |attr, value|
@@ -68,8 +87,9 @@ module ActiveRecord
end
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
- # { status: nil, group_id: 1 }
- # # => "status = NULL , group_id = 1"
+ #
+ # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
+ # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
def sanitize_sql_hash_for_assignment(attrs, table)
c = connection
attrs.map do |attr, value|
@@ -79,7 +99,19 @@ module ActiveRecord
end
# Sanitizes a +string+ so that it is safe to use within an SQL
- # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
+ #
+ # sanitize_sql_like("100%")
+ # # => "100\\%"
+ #
+ # sanitize_sql_like("snake_cased_string")
+ # # => "snake\\_cased\\_string"
+ #
+ # sanitize_sql_like("100%", "!")
+ # # => "100!%"
+ #
+ # sanitize_sql_like("snake_cased_string", "!")
+ # # => "snake!_cased!_string"
def sanitize_sql_like(string, escape_character = "\\")
pattern = Regexp.union(escape_character, "%", "_")
string.gsub(pattern) { |x| [escape_character, x].join }
@@ -87,7 +119,12 @@ module ActiveRecord
# Accepts an array of conditions. The array has each value
# sanitized and interpolated into the SQL statement.
- # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
statement, *values = ary
if values.first.is_a?(Hash) && statement =~ /:\w+/
@@ -101,7 +138,7 @@ module ActiveRecord
end
end
- def replace_bind_variables(statement, values) #:nodoc:
+ def replace_bind_variables(statement, values) # :nodoc:
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
bound = values.dup
c = connection
@@ -110,7 +147,7 @@ module ActiveRecord
end
end
- def replace_bind_variable(value, c = connection) #:nodoc:
+ def replace_bind_variable(value, c = connection) # :nodoc:
if ActiveRecord::Relation === value
value.to_sql
else
@@ -118,7 +155,7 @@ module ActiveRecord
end
end
- def replace_named_bind_variables(statement, bind_vars) #:nodoc:
+ def replace_named_bind_variables(statement, bind_vars) # :nodoc:
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
if $1 == ':' # skip postgresql casts
match # return the whole match
@@ -130,7 +167,7 @@ module ActiveRecord
end
end
- def quote_bound_value(value, c = connection) #:nodoc:
+ def quote_bound_value(value, c = connection) # :nodoc:
if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
@@ -142,7 +179,7 @@ module ActiveRecord
end
end
- def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
+ def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc:
unless expected == provided
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index fd48ea402a..2362dae9fc 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -89,7 +89,7 @@ HEADER
end
def tables(stream)
- sorted_tables = @connection.tables.sort
+ sorted_tables = @connection.data_sources.sort - @connection.views
sorted_tables.each do |table_name|
table(table_name, stream) unless ignored?(table_name)
@@ -112,20 +112,27 @@ HEADER
tbl = StringIO.new
# first dump primary key column
- pk = @connection.primary_key(table)
+ if @connection.respond_to?(:primary_keys)
+ pk = @connection.primary_keys(table)
+ pk = pk.first unless pk.size > 1
+ else
+ pk = @connection.primary_key(table)
+ end
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
- pkcol = columns.detect { |c| c.name == pk }
- if pkcol
- if pk != 'id'
- tbl.print %Q(, primary_key: "#{pk}")
- end
+
+ case pk
+ when String
+ tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id'
+ pkcol = columns.detect { |c| c.name == pk }
pkcolspec = @connection.column_spec_for_primary_key(pkcol)
if pkcolspec
pkcolspec.each do |key, value|
tbl.print ", #{key}: #{value}"
end
end
+ when Array
+ tbl.print ", primary_key: #{pk.inspect}"
else
tbl.print ", id: false"
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index ba5dd0a0d9..8929aa85c8 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -56,22 +56,21 @@ module ActiveRecord
end
def structure_dump(filename)
- args = prepare_command_options('mysqldump')
+ args = prepare_command_options
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
args.concat(["--routines"])
args.concat(["#{configuration['database']}"])
- unless Kernel.system(*args)
- $stderr.puts "Could not dump the database structure. "\
- "Make sure `mysqldump` is in your PATH and check the command output for warnings."
- end
+
+ run_cmd('mysqldump', args, 'dumping')
end
def structure_load(filename)
- args = prepare_command_options('mysql')
+ args = prepare_command_options
args.concat(['--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
args.concat(["--database", "#{configuration['database']}"])
- Kernel.system(*args)
+
+ run_cmd('mysql', args, 'loading')
end
private
@@ -130,7 +129,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
$stdin.gets.strip
end
- def prepare_command_options(command)
+ def prepare_command_options
args = {
'host' => '--host',
'port' => '--port',
@@ -145,7 +144,17 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
'sslkey' => '--ssl-key'
}.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
- [command, *args]
+ args
+ end
+
+ def run_cmd(cmd, args, action)
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
+ end
+
+ def run_cmd_error(cmd, args, action)
+ msg = "failed to execute: `#{cmd}`\n"
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
+ msg
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4a569fc242..1a2988ea77 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -11,6 +11,7 @@ module ActiveRecord
:before_commit_without_transaction_enrollment,
:commit_without_transaction_enrollment,
:rollback_without_transaction_enrollment,
+ terminator: deprecated_false_terminator,
scope: [:kind, :name]
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 53f3b53bec..74dfe88349 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,27 +1,18 @@
-require 'active_record/type/helpers'
-require 'active_record/type/value'
+require 'active_model/type'
+
+require 'active_record/type/internal/abstract_json'
+require 'active_record/type/internal/timezone'
-require 'active_record/type/big_integer'
-require 'active_record/type/binary'
-require 'active_record/type/boolean'
require 'active_record/type/date'
require 'active_record/type/date_time'
-require 'active_record/type/decimal'
-require 'active_record/type/decimal_without_scale'
-require 'active_record/type/float'
-require 'active_record/type/integer'
-require 'active_record/type/serialized'
-require 'active_record/type/string'
-require 'active_record/type/text'
require 'active_record/type/time'
-require 'active_record/type/unsigned_integer'
+require 'active_record/type/serialized'
require 'active_record/type/adapter_specific_registry'
+
require 'active_record/type/type_map'
require 'active_record/type/hash_lookup_type_map'
-require 'active_record/type/internal/abstract_json'
-
module ActiveRecord
module Type
@registry = AdapterSpecificRegistry.new
@@ -53,6 +44,19 @@ module ActiveRecord
end
end
+ Helpers = ActiveModel::Type::Helpers
+ BigInteger = ActiveModel::Type::BigInteger
+ Binary = ActiveModel::Type::Binary
+ Boolean = ActiveModel::Type::Boolean
+ Decimal = ActiveModel::Type::Decimal
+ DecimalWithoutScale = ActiveModel::Type::DecimalWithoutScale
+ Float = ActiveModel::Type::Float
+ Integer = ActiveModel::Type::Integer
+ String = ActiveModel::Type::String
+ Text = ActiveModel::Type::Text
+ UnsignedInteger = ActiveModel::Type::UnsignedInteger
+ Value = ActiveModel::Type::Value
+
register(:big_integer, Type::BigInteger, override: false)
register(:binary, Type::Binary, override: false)
register(:boolean, Type::Boolean, override: false)
diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb
index 5f71b3cb94..d440eac619 100644
--- a/activerecord/lib/active_record/type/adapter_specific_registry.rb
+++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb
@@ -1,35 +1,24 @@
+require 'active_model/type/registry'
+
module ActiveRecord
# :stopdoc:
module Type
- class AdapterSpecificRegistry
- def initialize
- @registrations = []
- end
-
- def register(type_name, klass = nil, **options, &block)
- block ||= proc { |_, *args| klass.new(*args) }
- registrations << Registration.new(type_name, block, **options)
+ class AdapterSpecificRegistry < ActiveModel::Type::Registry
+ def add_modifier(options, klass, **args)
+ registrations << DecorationRegistration.new(options, klass, **args)
end
- def lookup(symbol, *args)
- registration = registrations
- .select { |r| r.matches?(symbol, *args) }
- .max
+ private
- if registration
- registration.call(self, symbol, *args)
- else
- raise ArgumentError, "Unknown type #{symbol.inspect}"
- end
+ def registration_klass
+ Registration
end
- def add_modifier(options, klass, **args)
- registrations << DecorationRegistration.new(options, klass, **args)
+ def find_registration(symbol, *args)
+ registrations
+ .select { |registration| registration.matches?(symbol, *args) }
+ .max
end
-
- protected
-
- attr_reader :registrations
end
class Registration
@@ -137,6 +126,5 @@ module ActiveRecord
class TypeConflictError < StandardError
end
-
# :startdoc:
end
diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb
index 3ceab59ebb..ccafed054e 100644
--- a/activerecord/lib/active_record/type/date.rb
+++ b/activerecord/lib/active_record/type/date.rb
@@ -1,49 +1,7 @@
module ActiveRecord
module Type
- class Date < Value # :nodoc:
- include Helpers::AcceptsMultiparameterTime.new
-
- def type
- :date
- end
-
- def type_cast_for_schema(value)
- "'#{value.to_s(:db)}'"
- end
-
- private
-
- def cast_value(value)
- if value.is_a?(::String)
- return if value.empty?
- fast_string_to_date(value) || fallback_string_to_date(value)
- elsif value.respond_to?(:to_date)
- value.to_date
- else
- value
- end
- end
-
- def fast_string_to_date(string)
- if string =~ ConnectionAdapters::Column::Format::ISO_DATE
- new_date $1.to_i, $2.to_i, $3.to_i
- end
- end
-
- def fallback_string_to_date(string)
- new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
- end
-
- def new_date(year, mon, mday)
- if year && year != 0
- ::Date.new(year, mon, mday) rescue nil
- end
- end
-
- def value_from_multiparameter_assignment(*)
- time = super
- time && time.to_date
- end
+ class Date < ActiveModel::Type::Date
+ include Internal::Timezone
end
end
end
diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb
index a5199959b9..1fb9380ecd 100644
--- a/activerecord/lib/active_record/type/date_time.rb
+++ b/activerecord/lib/active_record/type/date_time.rb
@@ -1,44 +1,7 @@
module ActiveRecord
module Type
- class DateTime < Value # :nodoc:
- include Helpers::TimeValue
- include Helpers::AcceptsMultiparameterTime.new(
- defaults: { 4 => 0, 5 => 0 }
- )
-
- def type
- :datetime
- end
-
- private
-
- def cast_value(string)
- return string unless string.is_a?(::String)
- return if string.empty?
-
- fast_string_to_time(string) || fallback_string_to_time(string)
- end
-
- # '0.123456' -> 123456
- # '1.123456' -> 123456
- def microseconds(time)
- time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
- end
-
- def fallback_string_to_time(string)
- time_hash = ::Date._parse(string)
- time_hash[:sec_fraction] = microseconds(time_hash)
-
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
- end
-
- def value_from_multiparameter_assignment(values_hash)
- missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
- if missing_parameter
- raise ArgumentError, missing_parameter
- end
- super
- end
+ class DateTime < ActiveModel::Type::DateTime
+ include Internal::Timezone
end
end
end
diff --git a/activerecord/lib/active_record/type/helpers.rb b/activerecord/lib/active_record/type/helpers.rb
deleted file mode 100644
index 634d417d13..0000000000
--- a/activerecord/lib/active_record/type/helpers.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'active_record/type/helpers/accepts_multiparameter_time'
-require 'active_record/type/helpers/numeric'
-require 'active_record/type/helpers/mutable'
-require 'active_record/type/helpers/time_value'
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 963a8245d0..097d1bd363 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module Type
module Internal # :nodoc:
- class AbstractJson < Type::Value # :nodoc:
- include Type::Helpers::Mutable
+ class AbstractJson < ActiveModel::Type::Value # :nodoc:
+ include ActiveModel::Type::Helpers::Mutable
def type
:json
diff --git a/activerecord/lib/active_record/type/internal/timezone.rb b/activerecord/lib/active_record/type/internal/timezone.rb
new file mode 100644
index 0000000000..947e06158a
--- /dev/null
+++ b/activerecord/lib/active_record/type/internal/timezone.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module Type
+ module Internal
+ module Timezone
+ def is_utc?
+ ActiveRecord::Base.default_timezone == :utc
+ end
+
+ def default_timezone
+ ActiveRecord::Base.default_timezone
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index ea3e0d6a45..4ff0740cfb 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Type
- class Serialized < DelegateClass(Type::Value) # :nodoc:
- include Helpers::Mutable
+ class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
+ include ActiveModel::Type::Helpers::Mutable
attr_reader :subtype, :coder
@@ -41,6 +41,12 @@ module ActiveRecord
ActiveRecord::Store::IndifferentHashAccessor
end
+ def assert_valid_value(value)
+ if coder.respond_to?(:assert_valid_value)
+ coder.assert_valid_value(value)
+ end
+ end
+
private
def default_value?(value)
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 19a10021bc..70988d84ff 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -1,42 +1,8 @@
module ActiveRecord
module Type
- class Time < Value # :nodoc:
- include Helpers::TimeValue
- include Helpers::AcceptsMultiparameterTime.new(
- defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
- )
-
- def type
- :time
- end
-
- def user_input_in_time_zone(value)
- return unless value.present?
-
- case value
- when ::String
- value = "2000-01-01 #{value}"
- when ::Time
- value = value.change(year: 2000, day: 1, month: 1)
- end
-
- super(value)
- end
-
- private
-
- def cast_value(value)
- return value unless value.is_a?(::String)
- return if value.empty?
-
- dummy_time_value = "2000-01-01 #{value}"
-
- 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))
- end
- end
+ class Time < ActiveModel::Type::Time
+ include Internal::Timezone
end
end
end
+
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index 09f5ba6b74..81d7ed39bb 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -1,12 +1,12 @@
-require 'thread_safe'
+require 'concurrent'
module ActiveRecord
module Type
class TypeMap # :nodoc:
def initialize
@mapping = {}
- @cache = ThreadSafe::Cache.new do |h, key|
- h.fetch_or_store(key, ThreadSafe::Cache.new)
+ @cache = Concurrent::Map.new do |h, key|
+ h.fetch_or_store(key, Concurrent::Map.new)
end
end
@@ -57,7 +57,7 @@ module ActiveRecord
end
def default_value
- @default_value ||= Value.new
+ @default_value ||= ActiveModel::Type::Value.new
end
end
end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 3878270770..868d08ed44 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -20,7 +20,7 @@ module ActiveRecord
private
def column_for(attribute_name)
- if connection.schema_cache.table_exists?(table_name)
+ if connection.schema_cache.data_source_exists?(table_name)
connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
end
end
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 49a68fb94c..43c817e057 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -7,7 +7,7 @@ module ActiveRecord
module ConnectionAdapters
class FakeAdapter < AbstractAdapter
- attr_accessor :tables, :primary_keys
+ attr_accessor :data_sources, :primary_keys
@columns = Hash.new { |h,k| h[k] = [] }
class << self
@@ -16,7 +16,7 @@ module ActiveRecord
def initialize(connection, logger)
super
- @tables = []
+ @data_sources = []
@primary_keys = {}
@columns = self.class.columns
end
@@ -37,7 +37,7 @@ module ActiveRecord
@columns[table_name]
end
- def table_exists?(*)
+ def data_source_exists?(*)
true
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 1712ff0ac6..62579a4a7a 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -36,6 +36,21 @@ module ActiveRecord
assert !@connection.table_exists?(nil)
end
+ def test_data_sources
+ data_sources = @connection.data_sources
+ assert data_sources.include?("accounts")
+ assert data_sources.include?("authors")
+ assert data_sources.include?("tasks")
+ assert data_sources.include?("topics")
+ end
+
+ def test_data_source_exists?
+ assert @connection.data_source_exists?("accounts")
+ assert @connection.data_source_exists?(:accounts)
+ assert_not @connection.data_source_exists?("nonexistingtable")
+ assert_not @connection.data_source_exists?(nil)
+ end
+
def test_indexes
idx_name = "accounts_idx"
@@ -63,7 +78,7 @@ module ActiveRecord
end
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_charset
assert_not_nil @connection.charset
assert_not_equal 'character_set_database', @connection.charset
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index f0fd95ac16..0b5c9e1798 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -100,17 +100,15 @@ class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase
assert_equal "DROP TABLE `people`", drop_table(:people)
end
- if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
- def test_create_mysql_database_with_encoding
- assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
- assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
- end
+ def test_create_mysql_database_with_encoding
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
+ end
- def test_recreate_mysql_database_with_encoding
- create_database(:luca, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
- end
+ def test_recreate_mysql_database_with_encoding
+ create_database(:luca, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
end
def test_add_column
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index ddbc007b87..8b62998964 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -183,7 +183,7 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase
def with_example_table(&block)
definition ||= <<-SQL
- `id` int(11) auto_increment PRIMARY KEY,
+ `id` int auto_increment PRIMARY KEY,
`data` varchar(255)
SQL
super(@connection, 'ex', definition, &block)
diff --git a/activerecord/test/cases/adapters/mysql/explain_test.rb b/activerecord/test/cases/adapters/mysql/explain_test.rb
new file mode 100644
index 0000000000..c44c1e6648
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/explain_test.rb
@@ -0,0 +1,21 @@
+require "cases/helper"
+require 'models/developer'
+require 'models/computer'
+
+class MysqlExplainTest < ActiveRecord::MysqlTestCase
+ fixtures :developers
+
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ end
+
+ def test_explain_with_eager_loading
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
+ assert_match %r(audit_logs |.* ALL), explain
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index b804cb45b9..29573d8e0d 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -1,4 +1,3 @@
-
require "cases/helper"
require 'support/ddl_helper'
@@ -66,14 +65,6 @@ module ActiveRecord
end
end
- def test_tables_quoting
- @conn.tables(nil, "foo-bar", nil)
- flunk
- rescue => e
- # assertion for *quoted* database properly
- assert_match(/database 'foo-bar'/, e.inspect)
- end
-
def test_pk_and_sequence_for
with_example_table do
pk, seq = @conn.pk_and_sequence_for('ex')
@@ -83,7 +74,7 @@ module ActiveRecord
end
def test_pk_and_sequence_for_with_non_standard_primary_key
- with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do
+ with_example_table '`code` INT auto_increment, PRIMARY KEY (`code`)' do
pk, seq = @conn.pk_and_sequence_for('ex')
assert_equal 'code', pk
assert_equal @conn.default_sequence_name('ex', 'code'), seq
@@ -91,7 +82,7 @@ module ActiveRecord
end
def test_pk_and_sequence_for_with_custom_index_type_pk
- with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do
+ with_example_table '`id` INT auto_increment, PRIMARY KEY USING BTREE (`id`)' do
pk, seq = @conn.pk_and_sequence_for('ex')
assert_equal 'id', pk
assert_equal @conn.default_sequence_name('ex', 'id'), seq
@@ -99,7 +90,7 @@ module ActiveRecord
end
def test_composite_primary_key
- with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do
+ with_example_table '`id` INT, `number` INT, foo INT, PRIMARY KEY (`id`, `number`)' do
assert_nil @conn.primary_key('ex')
end
end
@@ -141,7 +132,7 @@ module ActiveRecord
def with_example_table(definition = nil, &block)
definition ||= <<-SQL
- `id` int(11) auto_increment PRIMARY KEY,
+ `id` int auto_increment PRIMARY KEY,
`number` integer,
`data` varchar(255)
SQL
diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
index 6be36566de..0d1f968022 100644
--- a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
@@ -3,7 +3,7 @@ require 'cases/helper'
class MysqlStatementPoolTest < ActiveRecord::MysqlTestCase
if Process.respond_to?(:fork)
def test_cache_is_per_pid
- cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new nil, 10
+ cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new(10)
cache['foo'] = 'bar'
assert_equal 'bar', cache['foo']
diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
index ed9398a918..84c5394c2e 100644
--- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
@@ -1,6 +1,8 @@
require "cases/helper"
+require "support/schema_dumping_helper"
class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase
+ include SchemaDumpingHelper
self.use_transactional_tests = false
class UnsignedType < ActiveRecord::Base
@@ -9,12 +11,15 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.create_table("unsigned_types", force: true) do |t|
- t.column :unsigned_integer, "int unsigned"
+ t.integer :unsigned_integer, unsigned: true
+ t.bigint :unsigned_bigint, unsigned: true
+ t.float :unsigned_float, unsigned: true
+ t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2
end
end
teardown do
- @connection.drop_table "unsigned_types"
+ @connection.drop_table "unsigned_types", if_exists: true
end
test "unsigned int max value is in range" do
@@ -26,5 +31,35 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase
assert_raise(RangeError) do
UnsignedType.create(unsigned_integer: -10)
end
+ assert_raise(RangeError) do
+ UnsignedType.create(unsigned_bigint: -10)
+ end
+ assert_raise(ActiveRecord::StatementInvalid) do
+ UnsignedType.create(unsigned_float: -10.0)
+ end
+ assert_raise(ActiveRecord::StatementInvalid) do
+ UnsignedType.create(unsigned_decimal: -10.0)
+ end
+ end
+
+ test "schema definition can use unsigned as the type" do
+ @connection.change_table("unsigned_types") do |t|
+ t.unsigned_integer :unsigned_integer_t
+ t.unsigned_bigint :unsigned_bigint_t
+ t.unsigned_float :unsigned_float_t
+ t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2
+ end
+
+ @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column|
+ assert column.unsigned?
+ end
+ end
+
+ test "schema dump includes unsigned option" do
+ schema = dump_table_schema "unsigned_types"
+ assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema
+ assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema
+ assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
+ assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 6558d60aa1..31dc69a45b 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -100,17 +100,15 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
assert_equal "DROP TABLE `people`", drop_table(:people)
end
- if current_adapter?(:Mysql2Adapter)
- def test_create_mysql_database_with_encoding
- assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
- assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
- end
+ def test_create_mysql_database_with_encoding
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
+ end
- def test_recreate_mysql_database_with_encoding
- create_database(:luca, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
- end
+ def test_recreate_mysql_database_with_encoding
+ create_database(:luca, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
end
def test_add_column
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 880a2123d2..faf2acb9cb 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -36,14 +36,6 @@ module ActiveRecord
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
end
- def test_tables_quoting
- @connection.tables(nil, "foo-bar", nil)
- flunk
- rescue => e
- # assertion for *quoted* database properly
- assert_match(/database 'foo-bar'/, e.inspect)
- end
-
def test_dump_indexes
index_a_name = 'index_key_tests_on_snack'
index_b_name = 'index_key_tests_on_pizza'
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index 9e06db2519..a6f6dd21bb 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -1,6 +1,8 @@
require "cases/helper"
+require "support/schema_dumping_helper"
class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
self.use_transactional_tests = false
class UnsignedType < ActiveRecord::Base
@@ -9,12 +11,15 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.create_table("unsigned_types", force: true) do |t|
- t.column :unsigned_integer, "int unsigned"
+ t.integer :unsigned_integer, unsigned: true
+ t.bigint :unsigned_bigint, unsigned: true
+ t.float :unsigned_float, unsigned: true
+ t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2
end
end
teardown do
- @connection.drop_table "unsigned_types"
+ @connection.drop_table "unsigned_types", if_exists: true
end
test "unsigned int max value is in range" do
@@ -26,5 +31,35 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
assert_raise(RangeError) do
UnsignedType.create(unsigned_integer: -10)
end
+ assert_raise(RangeError) do
+ UnsignedType.create(unsigned_bigint: -10)
+ end
+ assert_raise(ActiveRecord::StatementInvalid) do
+ UnsignedType.create(unsigned_float: -10.0)
+ end
+ assert_raise(ActiveRecord::StatementInvalid) do
+ UnsignedType.create(unsigned_decimal: -10.0)
+ end
+ end
+
+ test "schema definition can use unsigned as the type" do
+ @connection.change_table("unsigned_types") do |t|
+ t.unsigned_integer :unsigned_integer_t
+ t.unsigned_bigint :unsigned_bigint_t
+ t.unsigned_float :unsigned_float_t
+ t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2
+ end
+
+ @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column|
+ assert column.unsigned?
+ end
+ end
+
+ test "schema dump includes unsigned option" do
+ schema = dump_table_schema "unsigned_types"
+ assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema
+ assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema
+ assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
+ assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index f242f32496..b3b121b4fb 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/schema_dumping_helper'
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 6e6850c4a9..e361521155 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -123,6 +123,20 @@ module ActiveRecord
assert_equal expect.to_i, result.rows.first.first
end
+ def test_exec_insert_default_values_with_returning_disabled_and_no_sequence_name_given
+ connection = connection_without_insert_returning
+ result = connection.exec_insert("insert into postgresql_partitioned_table_parent DEFAULT VALUES", nil, [], 'id')
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect.to_i, result.rows.first.first
+ end
+
+ def test_exec_insert_default_values_quoted_schema_with_returning_disabled_and_no_sequence_name_given
+ connection = connection_without_insert_returning
+ result = connection.exec_insert('insert into "public"."postgresql_partitioned_table_parent" DEFAULT VALUES', nil, [], 'id')
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect.to_i, result.rows.first.first
+ end
+
def test_sql_for_insert_with_returning_disabled
connection = connection_without_insert_returning
result = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index bee612d8d3..f89a394f96 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -5,9 +5,11 @@ require 'support/schema_dumping_helper'
module PGSchemaHelper
def with_schema_search_path(schema_search_path)
@connection.schema_search_path = schema_search_path
+ @connection.schema_cache.clear!
yield if block_given?
ensure
@connection.schema_search_path = "'$user', public"
+ @connection.schema_cache.clear!
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index ef324183a7..559b951109 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -6,7 +6,7 @@ module ActiveRecord::ConnectionAdapters
if Process.respond_to?(:fork)
def test_cache_is_per_pid
- cache = StatementPool.new nil, 10
+ cache = StatementPool.new(10)
cache['foo'] = 'bar'
assert_equal 'bar', cache['foo']
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 02b67f901f..938350627f 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -500,7 +500,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
line_item = LineItem.create!
invoice = Invoice.create!(line_items: [line_item])
initial = invoice.updated_at
- line_item.touch
+ travel(1.second) do
+ line_item.touch
+ end
assert_not_equal initial, invoice.reload.updated_at
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index d160c30375..20af436e02 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -95,6 +95,15 @@ class DeveloperWithExtendOption < Developer
has_and_belongs_to_many :projects, extend: NamedExtension
end
+class ProjectUnscopingDavidDefaultScope < ActiveRecord::Base
+ self.table_name = 'projects'
+ has_and_belongs_to_many :developers, -> { unscope(where: 'name') },
+ class_name: "LazyBlockDeveloperCalledDavid",
+ join_table: "developers_projects",
+ foreign_key: "project_id",
+ association_foreign_key: "developer_id"
+end
+
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers
@@ -936,4 +945,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, professor.courses.count
end
+
+ def test_habtm_scope_can_unscope
+ project = ProjectUnscopingDavidDefaultScope.new
+ project.save!
+
+ developer = LazyBlockDeveloperCalledDavid.new(name: "Not David")
+ developer.save!
+ project.developers << developer
+
+ projects = ProjectUnscopingDavidDefaultScope.includes(:developers).where(id: project.id)
+ assert_equal 1, projects.first.developers.size
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 0beaf0056a..cd19a7a5bc 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -170,7 +170,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
part = ShipPart.create(name: 'cockpit')
updated_at = part.updated_at
- ship.parts << part
+ travel(1.second) do
+ ship.parts << part
+ end
assert_equal part.ship, ship
assert_not_equal part.updated_at, updated_at
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 423b8238b1..ece4dab539 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -13,6 +13,9 @@ require 'models/mixed_case_monkey'
require 'models/admin'
require 'models/admin/account'
require 'models/admin/user'
+require 'models/developer'
+require 'models/company'
+require 'models/project'
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
@@ -198,6 +201,16 @@ class InverseAssociationTests < ActiveRecord::TestCase
belongs_to_ref = Sponsor.reflect_on_association(:sponsor_club)
assert_nil belongs_to_ref.inverse_of
end
+
+ def test_this_inverse_stuff
+ firm = Firm.create!(name: 'Adequate Holdings')
+ Project.create!(name: 'Project 1', firm: firm)
+ Developer.create!(name: 'Gorbypuff', firm: firm)
+
+ new_project = Project.last
+ assert Project.reflect_on_association(:lead_developer).inverse_of.present?, "Expected inverse of to be present"
+ assert new_project.lead_developer.present?, "Expected lead developer to be present on the project"
+ end
end
class InverseHasOneTests < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index e2608e3670..b67201d94d 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -175,7 +175,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal category_attrs , category.attributes_before_type_cast
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_read_attributes_before_type_cast_on_boolean
bool = Boolean.create({ "value" => false })
if RUBY_PLATFORM =~ /java/
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index 9d927481ec..5a0e463a48 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -29,7 +29,7 @@ module ActiveRecord
assert_equal :bar, attributes[:bar].name
end
- test "duping creates a new hash and dups each attribute" do
+ test "duping creates a new hash, but does not dup the attributes" do
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
attributes = builder.build_from_database(foo: 1, bar: 'foo')
@@ -43,6 +43,24 @@ module ActiveRecord
assert_equal 1, attributes[:foo].value
assert_equal 2, duped[:foo].value
+ assert_equal 'foobar', attributes[:bar].value
+ assert_equal 'foobar', duped[:bar].value
+ end
+
+ test "deep_duping creates a new hash and dups each attribute" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
+ attributes = builder.build_from_database(foo: 1, bar: 'foo')
+
+ # Ensure the type cast value is cached
+ attributes[:foo].value
+ attributes[:bar].value
+
+ duped = attributes.deep_dup
+ duped.write_from_database(:foo, 2)
+ duped[:bar].value << 'bar'
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2, duped[:foo].value
assert_equal 'foo', attributes[:bar].value
assert_equal 'foobar', duped[:bar].value
end
@@ -160,6 +178,9 @@ module ActiveRecord
return if value.nil?
value + " from database"
end
+
+ def assert_valid_value(*)
+ end
end
test "write_from_database sets the attribute with database typecasting" do
@@ -207,5 +228,16 @@ module ActiveRecord
assert_equal [:foo], attributes.accessed
end
+
+ test "#map returns a new attribute set with the changes applied" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
+ attributes = builder.build_from_database(foo: "1", bar: "2")
+ new_attributes = attributes.map do |attr|
+ attr.with_cast_value(attr.value + 1)
+ end
+
+ assert_equal 2, new_attributes.fetch_value(:foo)
+ assert_equal 3, new_attributes.fetch_value(:bar)
+ end
end
end
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 0ec368f51d..0f216b7a13 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -4,7 +4,6 @@ module ActiveRecord
class AttributeTest < ActiveRecord::TestCase
setup do
@type = Minitest::Mock.new
- @type.expect(:==, false, [false])
end
teardown do
@@ -108,6 +107,9 @@ module ActiveRecord
def deserialize(value)
value + " from database"
end
+
+ def assert_valid_value(*)
+ end
end
test "with_value_from_user returns a new attribute with the value from the user" do
@@ -187,5 +189,21 @@ module ActiveRecord
assert_not attribute.changed_in_place_from?("bar")
end
+
+ test "with_value_from_user validates the value" do
+ type = Type::Value.new
+ type.define_singleton_method(:assert_valid_value) do |value|
+ if value == 1
+ raise ArgumentError
+ end
+ end
+
+ attribute = Attribute.from_database(:foo, 1, type)
+ assert_equal 1, attribute.value
+ assert_equal 2, attribute.with_value_from_user(2).value
+ assert_raises ArgumentError do
+ attribute.with_value_from_user(1)
+ end
+ end
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5a93ea3832..dbbcaa075d 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
require "cases/helper"
require 'models/post'
require 'models/author'
@@ -206,7 +204,7 @@ class BasicsTest < ActiveRecord::TestCase
)
# For adapters which support microsecond resolution.
- if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) || mysql_56?
+ if subsecond_precision_supported?
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
@@ -1573,4 +1571,22 @@ class BasicsTest < ActiveRecord::TestCase
assert_not topic.id_changed?
end
+
+ test "ignored columns are not present in columns_hash" do
+ cache_columns = Developer.connection.schema_cache.columns_hash(Developer.table_name)
+ assert_includes cache_columns.keys, 'first_name'
+ refute_includes Developer.columns_hash.keys, 'first_name'
+ end
+
+ test "ignored columns have no attribute methods" do
+ refute Developer.new.respond_to?(:first_name)
+ refute Developer.new.respond_to?(:first_name=)
+ refute Developer.new.respond_to?(:first_name?)
+ end
+
+ test "ignored columns don't prevent explicit declaration of attribute methods" do
+ assert Developer.new.respond_to?(:last_name)
+ assert Developer.new.respond_to?(:last_name=)
+ assert Developer.new.respond_to?(:last_name?)
+ end
end
diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
index 80244d1439..2749273884 100644
--- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -22,6 +22,10 @@ module ActiveRecord
assert_lookup_type :string, "SET('one', 'two', 'three')"
end
+ def test_set_type_with_value_matching_other_type
+ assert_lookup_type :string, "SET('unicode', '8bit', 'none', 'time')"
+ end
+
def test_enum_type_with_value_matching_other_type
assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')"
end
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index c7531f5418..db832fe55d 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def test_clearing
@cache.columns('posts')
@cache.columns_hash('posts')
- @cache.tables('posts')
+ @cache.data_sources('posts')
@cache.primary_keys('posts')
@cache.clear!
@@ -40,17 +40,22 @@ module ActiveRecord
def test_dump_and_load
@cache.columns('posts')
@cache.columns_hash('posts')
- @cache.tables('posts')
+ @cache.data_sources('posts')
@cache.primary_keys('posts')
@cache = Marshal.load(Marshal.dump(@cache))
assert_equal 11, @cache.columns('posts').size
assert_equal 11, @cache.columns_hash('posts').size
- assert @cache.tables('posts')
+ assert @cache.data_sources('posts')
assert_equal 'id', @cache.primary_keys('posts')
end
+ def test_table_methods_deprecation
+ assert_deprecated { assert @cache.table_exists?('posts') }
+ assert_deprecated { assert @cache.tables('posts') }
+ assert_deprecated { @cache.clear_table_cache!('posts') }
+ end
end
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index f5aaf22e13..cd1967c373 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -89,7 +89,7 @@ class DirtyTest < ActiveRecord::TestCase
target = Class.new(ActiveRecord::Base)
target.table_name = 'pirates'
- pirate = target.create
+ pirate = target.create!
pirate.created_on = pirate.created_on
assert !pirate.created_on_changed?
end
@@ -467,8 +467,10 @@ class DirtyTest < ActiveRecord::TestCase
topic.save!
updated_at = topic.updated_at
- topic.content[:hello] = 'world'
- topic.save!
+ travel(1.second) do
+ topic.content[:hello] = 'world'
+ topic.save!
+ end
assert_not_equal updated_at, topic.updated_at
assert_equal 'world', topic.content[:hello]
@@ -521,6 +523,9 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal Hash.new, pirate.previous_changes
pirate = Pirate.find_by_catchphrase("arrr")
+
+ travel(1.second)
+
pirate.catchphrase = "Me Maties!"
pirate.save!
@@ -532,6 +537,9 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('created_on')
pirate = Pirate.find_by_catchphrase("Me Maties!")
+
+ travel(1.second)
+
pirate.catchphrase = "Thar She Blows!"
pirate.save
@@ -542,6 +550,8 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
+ travel(1.second)
+
pirate = Pirate.find_by_catchphrase("Thar She Blows!")
pirate.update(catchphrase: "Ahoy!")
@@ -552,6 +562,8 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
+ travel(1.second)
+
pirate = Pirate.find_by_catchphrase("Ahoy!")
pirate.update_attribute(:catchphrase, "Ninjas suck!")
@@ -561,6 +573,8 @@ class DirtyTest < ActiveRecord::TestCase
assert_not_nil pirate.previous_changes['updated_on'][1]
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
+ ensure
+ travel_back
end
if ActiveRecord::Base.connection.supports_migrations?
@@ -578,6 +592,7 @@ class DirtyTest < ActiveRecord::TestCase
end
def test_datetime_attribute_can_be_updated_with_fractional_seconds
+ skip "Fractional seconds are not supported" unless subsecond_precision_supported?
in_time_zone 'Paris' do
target = Class.new(ActiveRecord::Base)
target.table_name = 'topics'
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 0dc884497c..307b68764e 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -701,12 +701,12 @@ class FinderTest < ActiveRecord::TestCase
end
def test_bind_arity
- assert_nothing_raised { bind '' }
+ assert_nothing_raised { bind '' }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
- assert_nothing_raised { bind '?', 1 }
- assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
+ assert_nothing_raised { bind '?', 1 }
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
end
def test_named_bind_variables
@@ -721,6 +721,12 @@ class FinderTest < ActiveRecord::TestCase
assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on
end
+ def test_named_bind_arity
+ assert_nothing_raised { bind "name = :name", { name: "37signals" } }
+ assert_nothing_raised { bind "name = :name", { name: "37signals", id: 1 } }
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } }
+ end
+
class SimpleEnumerable
include Enumerable
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index b61a5126e0..8773986882 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -46,10 +46,10 @@ def in_memory_db?
ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"
end
-def mysql_56?
- current_adapter?(:MysqlAdapter, :Mysql2Adapter) &&
- ActiveRecord::Base.connection.send(:version) >= '5.6.0' &&
- ActiveRecord::Base.connection.send(:version) < '5.7.0'
+def subsecond_precision_supported?
+ !current_adapter?(:MysqlAdapter, :Mysql2Adapter) ||
+ (ActiveRecord::Base.connection.send(:version) >= '5.6.0' &&
+ ActiveRecord::Base.connection.send(:version) < '5.7.0')
end
def mysql_enforcing_gtid_consistency?
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 018b7b0d8f..80d17b8114 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -125,6 +125,7 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_cache_key_format_is_precise_enough
+ skip("Subsecond precision is not supported") unless subsecond_precision_supported?
dev = Developer.first
key = dev.cache_key
dev.touch
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
index 5bc0898f33..ad85684c0b 100644
--- a/activerecord/test/cases/migration/helper.rb
+++ b/activerecord/test/cases/migration/helper.rb
@@ -28,7 +28,7 @@ module ActiveRecord
super
TestModel.reset_table_name
TestModel.reset_sequence_name
- connection.drop_table :test_models rescue nil
+ connection.drop_table :test_models, if_exists: true
end
private
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 1594f99852..4f0da999d8 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -32,6 +32,14 @@ module ActiveRecord
assert_equal [], @connection.foreign_keys("testings")
end
+ test "foreign keys can be created in one query" do
+ assert_queries(1) do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, foreign_key: true
+ end
+ end
+ end
+
test "options hash can be passed" do
@connection.change_table :testing_parents do |t|
t.integer :other_id
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 0745a37ee9..5e4ba47988 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -241,6 +241,33 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
end
end
+class CompositePrimaryKeyTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t|
+ t.string :region
+ t.integer :code
+ end
+ end
+
+ def teardown
+ @connection.drop_table(:barcodes, if_exists: true)
+ end
+
+ def test_composite_primary_key
+ assert_equal ["region", "code"], @connection.primary_keys("barcodes")
+ end
+
+ def test_collectly_dump_composite_primary_key
+ schema = dump_table_schema "barcodes"
+ assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema
+ end
+end
+
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
self.use_transactional_tests = false
@@ -300,11 +327,12 @@ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
test "primary key column type with options" do
- @connection.create_table(:widgets, id: :primary_key, limit: 8, force: true)
+ @connection.create_table(:widgets, id: :primary_key, limit: 8, unsigned: true, force: true)
column = @connection.columns(:widgets).find { |c| c.name == 'id' }
assert column.auto_increment?
assert_equal :integer, column.type
assert_equal 8, column.limit
+ assert column.unsigned?
end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 37d3965022..b0fb905760 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -243,18 +243,23 @@ module ActiveRecord
end
def test_select_quotes_when_using_from_clause
- if sqlite3_version_includes_quoting_bug?
- skip <<-ERROR.squish
- You are using an outdated version of SQLite3 which has a bug in
- quoted column names. Please update SQLite3 and rebuild the sqlite3
- ruby gem
- ERROR
- end
+ skip_if_sqlite3_version_includes_quoting_bug
quoted_join = ActiveRecord::Base.connection.quote_table_name("join")
selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join)
assert_equal Post.pluck(:id), selected
end
+ def test_selecting_aliased_attribute_quotes_column_name_when_from_is_used
+ skip_if_sqlite3_version_includes_quoting_bug
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = :test_with_keyword_column_name
+ alias_attribute :description, :desc
+ end
+ klass.create!(description: "foo")
+
+ assert_equal ["foo"], klass.select(:description).from(klass.all).map(&:desc)
+ end
+
def test_relation_merging_with_merged_joins_as_strings
join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"
special_comments_with_ratings = SpecialComment.joins join_string
@@ -292,6 +297,16 @@ module ActiveRecord
private
+ def skip_if_sqlite3_version_includes_quoting_bug
+ if sqlite3_version_includes_quoting_bug?
+ skip <<-ERROR.squish
+ You are using an outdated version of SQLite3 which has a bug in
+ quoted column names. Please update SQLite3 and rebuild the sqlite3
+ ruby gem
+ ERROR
+ end
+ end
+
def sqlite3_version_includes_quoting_bug?
if current_adapter?(:SQLite3Adapter)
selected_quoted_column_names = ActiveRecord::Base.connection.exec_query(
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 262e0abc22..14e392ac30 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -9,11 +9,11 @@ class SanitizeTest < ActiveRecord::TestCase
def test_sanitize_sql_array_handles_string_interpolation
quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"])
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi".mb_chars])
+ assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi"])
+ assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper")
- assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper"])
- assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper".mb_chars])
+ assert_equal "name='#{quoted_bambi_and_thumper}'",Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"])
+ assert_equal "name='#{quoted_bambi_and_thumper}'",Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_bind_variables
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index d0deb4c273..a93fa57257 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -270,15 +270,16 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
- def test_warn_when_external_structure_dump_fails
+ def test_warn_when_external_structure_dump_command_execution_fails
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(false)
+ Kernel.expects(:system)
+ .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db")
+ .returns(false)
- warnings = capture(:stderr) do
+ e = assert_raise(RuntimeError) {
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
- end
-
- assert_match(/Could not dump the database structure/, warnings)
+ }
+ assert_match(/^failed to execute: `mysqldump`$/, e.message)
end
def test_structure_dump_with_port_number
@@ -311,6 +312,7 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
Kernel.expects(:system).with('mysql', '--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db")
+ .returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 7761ea5612..47e664f4e7 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -103,7 +103,7 @@ module ActiveRecord
# ignored SQL, or better yet, use a different notification for the queries
# instead examining the SQL content.
oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im]
- mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /]
+ mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]
postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb
index 3f4baf8378..1970fe82d0 100644
--- a/activerecord/test/cases/test_fixtures_test.rb
+++ b/activerecord/test/cases/test_fixtures_test.rb
@@ -28,7 +28,7 @@ class TestFixturesTest < ActiveRecord::TestCase
assert_equal true, @klass.use_transactional_tests
end
- def test_use_transactional_tests_can_be_overriden
+ def test_use_transactional_tests_can_be_overridden
@klass.use_transactional_tests = "foobar"
assert_equal "foobar", @klass.use_transactional_tests
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 5dab32995c..970f6bcf4a 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -84,7 +84,9 @@ class TimestampTest < ActiveRecord::TestCase
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
- @developer.touch(:created_at)
+ travel(1.second) do
+ @developer.touch(:created_at)
+ end
assert !@developer.created_at_changed? , 'created_at should not be changed'
assert !@developer.changed?, 'record should not be changed'
@@ -199,8 +201,10 @@ class TimestampTest < ActiveRecord::TestCase
owner = pet.owner
previously_owner_updated_at = owner.updated_at
- pet.name = "Fluffy the Third"
- pet.save
+ travel(1.second) do
+ pet.name = "Fluffy the Third"
+ pet.save
+ end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
@@ -210,7 +214,9 @@ class TimestampTest < ActiveRecord::TestCase
owner = pet.owner
previously_owner_updated_at = owner.updated_at
- pet.destroy
+ travel(1.second) do
+ pet.destroy
+ end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
@@ -254,8 +260,10 @@ class TimestampTest < ActiveRecord::TestCase
owner.update_columns(happy_at: 3.days.ago)
previously_owner_updated_at = owner.updated_at
- pet.name = "I'm a parrot"
- pet.save
+ travel(1.second) do
+ pet.name = "I'm a parrot"
+ pet.save
+ end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index b3c42c8e42..7058f4fbe2 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -72,7 +72,7 @@ class TouchLaterTest < ActiveRecord::TestCase
end
def test_touch_touches_immediately_with_a_custom_time
- time = Time.now.utc - 25.days
+ time = (Time.now.utc - 25.days).change(nsec: 0)
topic = Topic.create!(updated_at: time, created_at: time)
assert_equal time, topic.updated_at
assert_equal time, topic.created_at
diff --git a/activerecord/test/cases/type/date_time_test.rb b/activerecord/test/cases/type/date_time_test.rb
new file mode 100644
index 0000000000..bc4900e1c2
--- /dev/null
+++ b/activerecord/test/cases/type/date_time_test.rb
@@ -0,0 +1,14 @@
+require "cases/helper"
+require "models/task"
+
+module ActiveRecord
+ module Type
+ class IntegerTest < ActiveRecord::TestCase
+ def test_datetime_seconds_precision_applied_to_timestamp
+ skip "This test is invalid if subsecond precision isn't supported" unless subsecond_precision_supported?
+ p = Task.create!(starting: ::Time.now)
+ assert_equal p.starting.usec, p.reload.starting.usec
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 0dcdbd0667..c0932d5357 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -4,112 +4,12 @@ require "models/company"
module ActiveRecord
module Type
class IntegerTest < ActiveRecord::TestCase
- test "simple values" do
- type = Type::Integer.new
- assert_equal 1, type.cast(1)
- assert_equal 1, type.cast('1')
- assert_equal 1, type.cast('1ignore')
- assert_equal 0, type.cast('bad1')
- assert_equal 0, type.cast('bad')
- assert_equal 1, type.cast(1.7)
- assert_equal 0, type.cast(false)
- assert_equal 1, type.cast(true)
- assert_nil type.cast(nil)
- end
-
- test "random objects cast to nil" do
- type = Type::Integer.new
- assert_nil type.cast([1,2])
- assert_nil type.cast({1 => 2})
- assert_nil type.cast(1..2)
- end
-
test "casting ActiveRecord models" do
type = Type::Integer.new
firm = Firm.create(:name => 'Apple')
assert_nil type.cast(firm)
end
- test "casting objects without to_i" do
- type = Type::Integer.new
- assert_nil type.cast(::Object.new)
- end
-
- test "casting nan and infinity" do
- type = Type::Integer.new
- assert_nil type.cast(::Float::NAN)
- assert_nil type.cast(1.0/0.0)
- end
-
- test "casting booleans for database" do
- type = Type::Integer.new
- assert_equal 1, type.serialize(true)
- assert_equal 0, type.serialize(false)
- end
-
- test "changed?" do
- type = Type::Integer.new
-
- assert type.changed?(5, 5, '5wibble')
- assert_not type.changed?(5, 5, '5')
- assert_not type.changed?(5, 5, '5.0')
- assert_not type.changed?(-5, -5, '-5')
- assert_not type.changed?(-5, -5, '-5.0')
- assert_not type.changed?(nil, nil, nil)
- end
-
- test "values below int min value are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(-2147483649)
- end
- end
-
- test "values above int max value are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(2147483648)
- end
- end
-
- test "very small numbers are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(-9999999999999999999999999999999)
- end
- end
-
- test "very large numbers are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(9999999999999999999999999999999)
- end
- end
-
- test "normal numbers are in range" do
- type = Integer.new
- assert_equal(0, type.serialize(0))
- assert_equal(-1, type.serialize(-1))
- assert_equal(1, type.serialize(1))
- end
-
- test "int max value is in range" do
- assert_equal(2147483647, Integer.new.serialize(2147483647))
- end
-
- test "int min value is in range" do
- assert_equal(-2147483648, Integer.new.serialize(-2147483648))
- end
-
- test "columns with a larger limit have larger ranges" do
- type = Integer.new(limit: 8)
-
- assert_equal(9223372036854775807, type.serialize(9223372036854775807))
- assert_equal(-9223372036854775808, type.serialize(-9223372036854775808))
- assert_raises(::RangeError) do
- type.serialize(-9999999999999999999999999999999)
- end
- assert_raises(::RangeError) do
- type.serialize(9999999999999999999999999999999)
- end
- end
-
test "values which are out of range can be re-assigned" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'posts'
diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb
index 56e9bf434d..6fe6d46711 100644
--- a/activerecord/test/cases/type/string_test.rb
+++ b/activerecord/test/cases/type/string_test.rb
@@ -2,20 +2,6 @@ require 'cases/helper'
module ActiveRecord
class StringTypeTest < ActiveRecord::TestCase
- test "type casting" do
- type = Type::String.new
- assert_equal "t", type.cast(true)
- assert_equal "f", type.cast(false)
- assert_equal "123", type.cast(123)
- end
-
- test "values are duped coming out" do
- s = "foo"
- type = Type::String.new
- assert_not_same s, type.cast(s)
- assert_not_same s, type.deserialize(s)
- end
-
test "string mutations are detected" do
klass = Class.new(Base)
klass.table_name = 'authors'
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index 9b1859c2ce..81fcf04a27 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -3,111 +3,6 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class TypesTest < ActiveRecord::TestCase
- def test_type_cast_boolean
- type = Type::Boolean.new
- assert type.cast('').nil?
- assert type.cast(nil).nil?
-
- assert type.cast(true)
- assert type.cast(1)
- assert type.cast('1')
- assert type.cast('t')
- assert type.cast('T')
- assert type.cast('true')
- assert type.cast('TRUE')
- assert type.cast('on')
- assert type.cast('ON')
- assert type.cast(' ')
- assert type.cast("\u3000\r\n")
- assert type.cast("\u0000")
- assert type.cast('SOMETHING RANDOM')
-
- # explicitly check for false vs nil
- assert_equal false, type.cast(false)
- assert_equal false, type.cast(0)
- assert_equal false, type.cast('0')
- assert_equal false, type.cast('f')
- assert_equal false, type.cast('F')
- assert_equal false, type.cast('false')
- assert_equal false, type.cast('FALSE')
- assert_equal false, type.cast('off')
- assert_equal false, type.cast('OFF')
- end
-
- def test_type_cast_float
- type = Type::Float.new
- assert_equal 1.0, type.cast("1")
- end
-
- def test_changing_float
- type = Type::Float.new
-
- assert type.changed?(5.0, 5.0, '5wibble')
- assert_not type.changed?(5.0, 5.0, '5')
- assert_not type.changed?(5.0, 5.0, '5.0')
- assert_not type.changed?(nil, nil, nil)
- end
-
- def test_type_cast_binary
- type = Type::Binary.new
- assert_equal nil, type.cast(nil)
- assert_equal "1", type.cast("1")
- assert_equal 1, type.cast(1)
- end
-
- def test_type_cast_time
- type = Type::Time.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast('')
- assert_equal nil, type.cast('ABC')
-
- time_string = Time.now.utc.strftime("%T")
- assert_equal time_string, type.cast(time_string).strftime("%T")
- end
-
- def test_type_cast_datetime_and_timestamp
- type = Type::DateTime.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast('')
- assert_equal nil, type.cast(' ')
- assert_equal nil, type.cast('ABC')
-
- datetime_string = Time.now.utc.strftime("%FT%T")
- assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T")
- end
-
- def test_type_cast_date
- type = Type::Date.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast('')
- assert_equal nil, type.cast(' ')
- assert_equal nil, type.cast('ABC')
-
- date_string = Time.now.utc.strftime("%F")
- assert_equal date_string, type.cast(date_string).strftime("%F")
- end
-
- def test_type_cast_duration_to_integer
- type = Type::Integer.new
- assert_equal 1800, type.cast(30.minutes)
- assert_equal 7200, type.cast(2.hours)
- end
-
- def test_string_to_time_with_timezone
- [:utc, :local].each do |zone|
- with_timezone_config default: zone do
- type = Type::DateTime.new
- assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
- end
- end
- end
-
- def test_type_equality
- assert_equal Type::Value.new, Type::Value.new
- assert_not_equal Type::Value.new, Type::Integer.new
- assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
- end
-
def test_attributes_which_are_invalid_for_database_can_still_be_reassigned
type_which_cannot_go_to_the_database = Type::Value.new
def type_which_cannot_go_to_the_database.serialize(*)
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index f95f8f0b8f..c5d8f8895c 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'models/owner'
require 'models/pet'
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index a429d06aad..d04f4f7ce7 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -168,4 +168,15 @@ class ValidationsTest < ActiveRecord::TestCase
ensure
Topic.reset_column_information
end
+
+ def test_acceptance_validator_doesnt_require_db_connection
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'posts'
+ end
+ klass.reset_column_information
+
+ assert_no_queries do
+ klass.validates_acceptance_of(:foo)
+ end
+ end
end
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 1eb1430065..e80d8bd584 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -1,7 +1,9 @@
require "cases/helper"
require "models/book"
+require "support/schema_dumping_helper"
module ViewBehavior
+ include SchemaDumpingHelper
extend ActiveSupport::Concern
included do
@@ -31,11 +33,26 @@ module ViewBehavior
assert_equal ["Ruby for Rails"], books.map(&:name)
end
+ def test_views
+ assert_equal [Ebook.table_name], @connection.views
+ end
+
+ def test_view_exists
+ view_name = Ebook.table_name
+ assert @connection.view_exists?(view_name), "'#{view_name}' view should exist"
+ end
+
def test_table_exists
view_name = Ebook.table_name
+ # TODO: switch this assertion around once we changed #tables to not return views.
assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
end
+ def test_views_ara_valid_data_sources
+ view_name = Ebook.table_name
+ assert @connection.data_source_exists?(view_name), "'#{view_name}' should be a data source"
+ end
+
def test_column_definitions
assert_equal([["id", :integer],
["name", :string],
@@ -53,6 +70,11 @@ module ViewBehavior
end
assert_nil model.primary_key
end
+
+ def test_does_not_dump_view_as_table
+ schema = dump_table_schema "ebooks"
+ assert_no_match %r{create_table "ebooks"}, schema
+ end
end
if ActiveRecord::Base.connection.supports_views?
@@ -70,6 +92,7 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
end
class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
fixtures :books
class Paperback < ActiveRecord::Base; end
@@ -91,6 +114,15 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
assert_equal ["Agile Web Development with Rails"], books.map(&:name)
end
+ def test_views
+ assert_equal [Paperback.table_name], @connection.views
+ end
+
+ def test_view_exists
+ view_name = Paperback.table_name
+ assert @connection.view_exists?(view_name), "'#{view_name}' view should exist"
+ end
+
def test_table_exists
view_name = Paperback.table_name
assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
@@ -109,6 +141,11 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
def test_does_not_have_a_primary_key
assert_nil Paperback.primary_key
end
+
+ def test_does_not_dump_view_as_table
+ schema = dump_table_schema "paperbacks"
+ assert_no_match %r{create_table "paperbacks"}, schema
+ end
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 67936e8e5d..a96b8ef0f2 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -86,6 +86,9 @@ class Firm < Company
has_many :association_with_references, -> { references(:foo) }, :class_name => 'Client'
+ has_one :lead_developer, class_name: "Developer"
+ has_many :projects
+
def log
@log ||= []
end
diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb
index 3ea17c3abf..9f2f69e1ee 100644
--- a/activerecord/test/models/contact.rb
+++ b/activerecord/test/models/contact.rb
@@ -3,7 +3,7 @@ module ContactFakeColumns
base.class_eval do
establish_connection(:adapter => 'fake')
- connection.tables = [table_name]
+ connection.data_sources = [table_name]
connection.primary_keys = {
table_name => 'id'
}
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 8ac7a9df6a..7c5941b1af 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -7,6 +7,8 @@ module DeveloperProjectsAssociationExtension2
end
class Developer < ActiveRecord::Base
+ self.ignored_columns = %w(first_name last_name)
+
has_and_belongs_to_many :projects do
def find_most_recent
order("id DESC").first
@@ -52,6 +54,9 @@ class Developer < ActiveRecord::Base
has_many :ratings, through: :comments
has_one :ship, dependent: :nullify
+ belongs_to :firm
+ has_many :contracted_projects, class_name: "Project"
+
scope :jamises, -> { where(:name => 'Jamis') }
validates_inclusion_of :salary, :in => 50000..200000
@@ -61,6 +66,9 @@ class Developer < ActiveRecord::Base
developer.audit_logs.build :message => "Computer created"
end
+ attr_accessor :last_name
+ define_attribute_method 'last_name'
+
def log=(message)
audit_logs.build :message => message
end
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index 7f42a4b1f8..5328330653 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -11,6 +11,8 @@ class Project < ActiveRecord::Base
:before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
:after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}
has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, :class_name => "Developer"
+ belongs_to :firm
+ has_one :lead_developer, through: :firm, inverse_of: :contracted_projects
attr_accessor :developers_log
after_initialize :set_developers_log
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 994ece9244..d334a2740e 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -1,4 +1,3 @@
-
ActiveRecord::Schema.define do
def except(adapter_names_to_exclude)
unless [adapter_names_to_exclude].flatten.include?(adapter_name)
@@ -251,11 +250,20 @@ ActiveRecord::Schema.define do
create_table :developers, force: true do |t|
t.string :name
+ t.string :first_name
t.integer :salary, default: 70000
- t.datetime :created_at
- t.datetime :updated_at
- t.datetime :created_on
- t.datetime :updated_on
+ t.integer :firm_id
+ if subsecond_precision_supported?
+ t.datetime :created_at, precision: 6
+ t.datetime :updated_at, precision: 6
+ t.datetime :created_on, precision: 6
+ t.datetime :updated_on, precision: 6
+ else
+ t.datetime :created_at
+ t.datetime :updated_at
+ t.datetime :created_on
+ t.datetime :updated_on
+ end
end
create_table :developers_projects, force: true, id: false do |t|
@@ -354,7 +362,11 @@ ActiveRecord::Schema.define do
create_table :invoices, force: true do |t|
t.integer :balance
- t.datetime :updated_at
+ if subsecond_precision_supported?
+ t.datetime :updated_at, precision: 6
+ else
+ t.datetime :updated_at
+ end
end
create_table :iris, force: true do |t|
@@ -504,7 +516,11 @@ ActiveRecord::Schema.define do
create_table :owners, primary_key: :owner_id, force: true do |t|
t.string :name
- t.column :updated_at, :datetime
+ if subsecond_precision_supported?
+ t.column :updated_at, :datetime, precision: 6
+ else
+ t.column :updated_at, :datetime
+ end
t.column :happy_at, :datetime
t.string :essay_id
end
@@ -522,10 +538,17 @@ ActiveRecord::Schema.define do
t.column :color, :string
t.column :parrot_sti_class, :string
t.column :killer_id, :integer
- t.column :created_at, :datetime
- t.column :created_on, :datetime
- t.column :updated_at, :datetime
- t.column :updated_on, :datetime
+ if subsecond_precision_supported?
+ t.column :created_at, :datetime, precision: 0
+ t.column :created_on, :datetime, precision: 0
+ t.column :updated_at, :datetime, precision: 0
+ t.column :updated_on, :datetime, precision: 0
+ else
+ t.column :created_at, :datetime
+ t.column :created_on, :datetime
+ t.column :updated_at, :datetime
+ t.column :updated_on, :datetime
+ end
end
create_table :parrots_pirates, id: false, force: true do |t|
@@ -568,15 +591,24 @@ ActiveRecord::Schema.define do
create_table :pets, primary_key: :pet_id, force: true do |t|
t.string :name
t.integer :owner_id, :integer
- t.timestamps null: false
+ if subsecond_precision_supported?
+ t.timestamps null: false, precision: 6
+ else
+ t.timestamps null: false
+ end
end
create_table :pirates, force: true do |t|
t.column :catchphrase, :string
t.column :parrot_id, :integer
t.integer :non_validated_parrot_id
- t.column :created_on, :datetime
- t.column :updated_on, :datetime
+ if subsecond_precision_supported?
+ t.column :created_on, :datetime, precision: 6
+ t.column :updated_on, :datetime, precision: 6
+ else
+ t.column :created_on, :datetime
+ t.column :updated_on, :datetime
+ end
end
create_table :posts, force: true do |t|
@@ -627,6 +659,7 @@ ActiveRecord::Schema.define do
create_table :projects, force: true do |t|
t.string :name
t.string :type
+ t.integer :firm_id
end
create_table :randomly_named_table1, force: true do |t|
@@ -686,7 +719,11 @@ ActiveRecord::Schema.define do
create_table :ship_parts, force: true do |t|
t.string :name
t.integer :ship_id
- t.datetime :updated_at
+ if subsecond_precision_supported?
+ t.datetime :updated_at, precision: 6
+ else
+ t.datetime :updated_at
+ end
end
create_table :prisoners, force: true do |t|
@@ -756,7 +793,7 @@ ActiveRecord::Schema.define do
t.string :title, limit: 250
t.string :author_name
t.string :author_email_address
- if mysql_56?
+ if subsecond_precision_supported?
t.datetime :written_on, precision: 6
else
t.datetime :written_on
@@ -779,7 +816,11 @@ ActiveRecord::Schema.define do
t.string :parent_title
t.string :type
t.string :group
- t.timestamps null: true
+ if subsecond_precision_supported?
+ t.timestamps null: true, precision: 6
+ else
+ t.timestamps null: true
+ end
end
create_table :toys, primary_key: :toy_id, force: true do |t|
@@ -944,6 +985,10 @@ ActiveRecord::Schema.define do
t.string :token
t.string :auth_token
end
+
+ create_table :test_with_keyword_column_name, force: true do |t|
+ t.string :desc
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index addf5d521a..a39344e464 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,7 +1,29 @@
+* `assert_difference` and `assert_no_difference` now returns the result of the
+ yielded block.
+
+ Example:
+
+ post = assert_difference -> { Post.count }, 1 do
+ Post.create
+ end
+
+ *Lucas Mazza*
+
+* Short-circuit `blank?` on date and time values since they are never blank.
+
+ Fixes #21657
+
+ *Andrew White*
+
+* Replaced deprecated `ThreadSafe::Cache` with its successor `Concurrent::Map` now that
+ the thread_safe gem has been merged into concurrent-ruby.
+
+ *Jerry D'Antonio*
+
* Updated Unicode version to 8.0.0
*Anshul Sharma*
-
+
* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option
to handle placement of delimiter, to support currency formats like INR
@@ -266,20 +288,17 @@
The preferred method to halt a callback chain from now on is to explicitly
`throw(:abort)`.
- In the past, returning `false` in an ActiveSupport callback had the side
- effect of halting the callback chain. This is not recommended anymore and,
- depending on the value of
- `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
- either not work at all or display a deprecation warning.
+ In the past, callbacks could only be halted by explicitly providing a
+ terminator and by having a callback match the conditions of the terminator.
* Add `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
- to `true` will let an app support the deprecated way of halting callback
- chains by returning `false`.
+ to `true` will let an app support the deprecated way of halting Active Record,
+ Active Model and Active Model validations callback chains by returning `false`.
Setting the value to `false` will tell the app to ignore any `false` value
- returned by callbacks, and only halt the chain upon `throw(:abort)`.
+ returned by those callbacks, and only halt the chain upon `throw(:abort)`.
The value can also be set with the Rails configuration option
`config.active_support.halt_callback_chains_on_return_false`.
@@ -289,7 +308,7 @@
For new Rails 5.0 apps, its value is set to `false` in an initializer, so
these apps will support the new behavior by default.
- *claudiob*
+ *claudiob*, *Roque Pinel*
* Changes arguments and default value of CallbackChain's `:terminator` option
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 67b6663915..d236dbd200 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -24,7 +24,6 @@ Gem::Specification.new do |s|
s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
- s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
- s.add_dependency 'concurrent-ruby', '~> 0.9.1'
+ s.add_dependency 'concurrent-ruby', '~> 1.0.0.pre2', '< 2.0.0'
s.add_dependency 'method_source'
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 80c5fdba17..3db9ea2ac0 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -536,23 +536,12 @@ module ActiveSupport
Proc.new do |target, result_lambda|
terminate = true
catch(:abort) do
- result = result_lambda.call if result_lambda.is_a?(Proc)
- if halt_and_display_warning_on_return_false && result == false
- display_deprecation_warning_for_false_terminator
- else
- terminate = false
- end
+ result_lambda.call if result_lambda.is_a?(Proc)
+ terminate = false
end
terminate
end
end
-
- def display_deprecation_warning_for_false_terminator
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails.
- To explicitly halt a callback chain, please use `throw :abort` instead.
- MSG
- end
end
module ClassMethods
@@ -686,7 +675,8 @@ module ActiveSupport
#
# In this example, if any before validate callbacks returns +false+,
# any successive before and around callback is not executed.
- # Defaults to +false+, meaning no value halts the chain.
+ #
+ # The default terminator halts the chain when a callback throws +:abort+.
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
@@ -764,6 +754,30 @@ module ActiveSupport
def set_callbacks(name, callbacks)
send "_#{name}_callbacks=", callbacks
end
+
+ def deprecated_false_terminator
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if CallbackChain.halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
+ end
+ end
+
+ private
+
+ def display_deprecation_warning_for_false_terminator
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails.
+ To explicitly halt the callback chain, please use `throw :abort` instead.
+ MSG
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index c5cc31abc5..8718b7e1e5 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -85,7 +85,9 @@ class Array
# Extends <tt>Array#to_s</tt> to convert a collection of elements into a
# comma separated id list if <tt>:db</tt> argument is given as the format.
#
- # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.none.to_formatted_s(:db) # => "null"
+ # [1,2].to_formatted_s # => "[1, 2]"
def to_formatted_s(format = :default)
case format
when :db
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 465fedda80..7f0f4639a2 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/date/acts_like'
+require 'active_support/core_ext/date/blank'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
-
diff --git a/activesupport/lib/active_support/core_ext/date/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb
new file mode 100644
index 0000000000..71627b6a6f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class Date #:nodoc:
+ # No Date is blank:
+ #
+ # Date.today.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 31479a1269..ed8bca77ac 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -75,10 +75,10 @@ class Date
#
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
- # date.to_time # => Sat Nov 10 00:00:00 0800 2007
- # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007
+ # date.to_time # => 2007-11-10 00:00:00 0800
+ # date.to_time(:local) # => 2007-11-10 00:00:00 0800
#
- # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
+ # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
def to_time(form = :local)
::Time.send(form, year, month, day)
end
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index e8a27b9f38..bcb228b09a 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/date_time/acts_like'
+require 'active_support/core_ext/date_time/blank'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date_time/zones'
diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb
new file mode 100644
index 0000000000..56981b75fb
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class DateTime #:nodoc:
+ # No DateTime is ever blank:
+ #
+ # DateTime.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index 2a9c09fc29..f59d05b214 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -40,6 +40,8 @@ class DateTime
alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
alias_method :to_s, :to_formatted_s
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index c30044b9ff..05679f1dd0 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,10 +1,14 @@
class Hash
- # Returns a new hash with all keys converted using the block operation.
+ # Returns a new hash with all keys converted using the +block+ operation.
#
# hash = { name: 'Rob', age: '28' }
#
- # hash.transform_keys{ |key| key.to_s.upcase }
- # # => {"NAME"=>"Rob", "AGE"=>"28"}
+ # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
+ #
+ # If you do not provide a +block+, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
def transform_keys
return enum_for(:transform_keys) unless block_given?
result = self.class.new
@@ -14,8 +18,8 @@ class Hash
result
end
- # Destructively converts all keys using the block operations.
- # Same as transform_keys but modifies +self+.
+ # Destructively converts all keys using the +block+ operations.
+ # Same as +transform_keys+ but modifies +self+.
def transform_keys!
return enum_for(:transform_keys!) unless block_given?
keys.each do |key|
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index e9bcce761f..9ddb838774 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -2,10 +2,15 @@ class Hash
# Returns a new hash with the results of running +block+ once for every value.
# The keys are unchanged.
#
- # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 }
- # # => { a: 2, b: 4, c: 6 }
+ # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 }
+ #
+ # If you do not provide a +block+, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 }
def transform_values
return enum_for(:transform_values) unless block_given?
+ return {} if empty?
result = self.class.new
each do |key, value|
result[key] = yield(value)
@@ -13,7 +18,8 @@ class Hash
result
end
- # Destructive +transform_values+
+ # Destructively converts all values using the +block+ operations.
+ # Same as +transform_values+ but modifies +self+.
def transform_values!
return enum_for(:transform_values!) unless block_given?
each do |key, value|
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index a77da573fe..a084177b9f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -19,9 +19,9 @@ class Module
# The attribute name must be a valid method name in Ruby.
#
# module Foo
- # mattr_reader :"1_Badname "
+ # mattr_reader :"1_Badname"
# end
- # # => NameError: invalid attribute name
+ # # => NameError: invalid attribute name: 1_Badname
#
# If you want to opt out the creation on the instance reader method, pass
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 0c8ff79237..d5bb11deed 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -41,7 +41,7 @@ class Numeric
# 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000%
# 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399%
# 1000.to_s(:percentage, locale: :fr) # => 1 000,000%
- # 100.to_s(:percentage, format: '%n %') # => 100 %
+ # 100.to_s(:percentage, format: '%n %') # => 100.000 %
#
# Delimited:
# 12345678.to_s(:delimited) # => 12,345,678
@@ -78,7 +78,7 @@ class Numeric
# 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
- # 1234567890123.to_s(:human_size, precision: 5) # => "1.1229 TB"
+ # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
#
# Human-friendly format:
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 548c91638c..039c50a4a2 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, +false+, '', ' ', +nil+, [], and {} are all blank.
@@ -129,3 +127,14 @@ class Numeric #:nodoc:
false
end
end
+
+class Time #:nodoc:
+ # No Time is blank:
+ #
+ # Time.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index 55f281b213..d4c17dfb07 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -5,7 +5,7 @@ class Object
# characters = ["Konata", "Kagami", "Tsukasa"]
# "Konata".in?(characters) # => true
#
- # This will throw an ArgumentError if the argument doesn't respond
+ # This will throw an +ArgumentError+ if the argument doesn't respond
# to +#include?+.
def in?(another_object)
another_object.include?(self)
@@ -18,7 +18,7 @@ class Object
#
# params[:bucket_type].presence_in %w( project calendar )
#
- # This will throw an ArgumentError if the argument doesn't respond to +#include?+.
+ # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+.
#
# @return [Object]
def presence_in(another_object)
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index c67eb25b68..8c16d95b62 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -94,7 +94,7 @@ class Object
# :call-seq:
# try!(*a, &b)
#
- # Same as #try, but raises a NoMethodError exception if the receiver is
+ # Same as #try, but raises a +NoMethodError+ exception if the receiver is
# not +nil+ and does not implement the tried method.
#
# "a".try!(:upcase) # => "A"
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
index 6cdbea1f37..98cf7430f7 100644
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -10,8 +10,8 @@ module SecureRandom
#
# The result may contain alphanumeric characters except 0, O, I and l
#
- # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE"
- # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7"
+ # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
+ # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
#
def self.base58(n = 16)
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 7055f7f699..cc6f2158e7 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -9,12 +9,10 @@ class String
# encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
#
- # name = 'Claus Müller'
- # name.reverse # => "rell??M sualC"
- # name.length # => 13
- #
- # name.mb_chars.reverse.to_s # => "rellüM sualC"
- # name.mb_chars.length # => 12
+ # >> "lj".upcase
+ # => "lj"
+ # >> "lj".mb_chars.upcase.to_s
+ # => "LJ"
#
# == Method chaining
#
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 c676b26b06..8b27ec4413 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -86,7 +86,7 @@ class ERB
# use inside HTML attributes.
#
# If your JSON is being used downstream for insertion into the DOM, be aware of
- # whether or not it is being inserted via +html()+. Most JQuery plugins do this.
+ # whether or not it is being inserted via +html()+. Most jQuery plugins do this.
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
# content returned by your JSON.
#
diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb
index a523e76b69..55b9b87352 100644
--- a/activesupport/lib/active_support/core_ext/string/strip.rb
+++ b/activesupport/lib/active_support/core_ext/string/strip.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/try'
-
class String
# Strips indentation in heredocs.
#
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index dbf1f2f373..24d13937f6 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -55,7 +55,8 @@ class Time
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
- # Returns the UTC offset as an +HH:MM formatted string.
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
#
# Time.local(2000).formatted_offset # => "-06:00"
# Time.local(2000).formatted_offset(false) # => "-0600"
diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb
index 0b2ff817c3..c6c183edd9 100644
--- a/activesupport/lib/active_support/core_ext/uri.rb
+++ b/activesupport/lib/active_support/core_ext/uri.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
require 'uri'
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
parser = URI::Parser.new
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 8215a3085e..16b726bcba 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,6 +1,6 @@
require 'set'
require 'thread'
-require 'thread_safe'
+require 'concurrent'
require 'pathname'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
@@ -585,7 +585,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = ThreadSafe::Cache.new
+ @store = Concurrent::Map.new
end
def empty?
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 42560f3515..c3907e9c22 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,4 +1,4 @@
-require 'thread_safe'
+require 'concurrent'
require 'active_support/core_ext/array/prepend_and_append'
require 'active_support/i18n'
@@ -25,7 +25,7 @@ module ActiveSupport
# singularization rules that is runs. This guarantees that your rules run
# before any of the rules that may already have been loaded.
class Inflections
- @__instance__ = ThreadSafe::Cache.new
+ @__instance__ = Concurrent::Map.new
class Uncountables < Array
def initialize
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index ab5372ac43..595b0339cc 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
require 'active_support/inflections'
module ActiveSupport
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 7b28eeb6e2..7472d4386a 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/core_ext/string/multibyte'
require 'active_support/i18n'
@@ -76,15 +75,15 @@ module ActiveSupport
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
unless sep.nil? || sep.empty?
if sep == "-".freeze
- re_duplicate_seperator = /-{2,}/
+ re_duplicate_separator = /-{2,}/
re_leading_trailing_separator = /^-|-$/i
else
re_sep = Regexp.escape(sep)
- re_duplicate_seperator = /#{re_sep}{2,}/
+ re_duplicate_separator = /#{re_sep}{2,}/
re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
end
# No more than one of the separator in a row.
- parameterized_string.gsub!(re_duplicate_seperator, sep)
+ parameterized_string.gsub!(re_duplicate_separator, sep)
# Remove leading/trailing separator.
parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze)
end
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 51d2da3a79..6bc3db6ec6 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -1,4 +1,4 @@
-require 'thread_safe'
+require 'concurrent'
require 'openssl'
module ActiveSupport
@@ -28,7 +28,7 @@ module ActiveSupport
class CachingKeyGenerator
def initialize(key_generator)
@key_generator = key_generator
- @cache_keys = ThreadSafe::Cache.new
+ @cache_keys = Concurrent::Map.new
end
# Returns a derived key suitable for use. The default key_size is chosen
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 45cf6fc1ef..f6a2e7e949 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/json'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/behavior'
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 3f6a4c8457..5221a6dfb8 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module ActiveSupport
module Multibyte
module Unicode
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 0131fe2572..71354dd15f 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,5 +1,5 @@
require 'mutex_m'
-require 'thread_safe'
+require 'concurrent'
module ActiveSupport
module Notifications
@@ -12,7 +12,7 @@ module ActiveSupport
def initialize
@subscribers = []
- @listeners_for = ThreadSafe::Cache.new
+ @listeners_for = Concurrent::Map.new
super
end
@@ -51,7 +51,7 @@ module ActiveSupport
end
def listeners_for(name)
- # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics)
+ # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
@listeners_for[name] || synchronize do
# use synchronisation when accessing @subscribers
@listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 9762d95145..504f96961a 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -118,7 +118,7 @@ module ActiveSupport
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
# number_to_percentage:(1000, precision: nil) # => 1000%
# number_to_percentage('98a') # => 98a%
- # number_to_percentage(100, format: '%n %') # => 100 %
+ # number_to_percentage(100, format: '%n %') # => 100.000 %
def number_to_percentage(number, options = {})
NumberToPercentageConverter.convert(number, options)
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index d87ce3474d..ae8c15d8bf 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -68,13 +68,15 @@ module ActiveSupport
}
before = exps.map(&:call)
- yield
+ retval = yield
expressions.zip(exps).each_with_index do |(code, e), i|
error = "#{code.inspect} didn't change by #{difference}"
error = "#{message}.\n#{error}" if message
assert_equal(before[i] + difference, e.call, error)
end
+
+ retval
end
# Assertion that the numeric result of evaluating an expression is not
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 07fc787550..a82a7dfea0 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -14,7 +14,7 @@ module ActiveSupport
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
# Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00
- # Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
+ # Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
# Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00
# Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00
#
@@ -388,6 +388,11 @@ module ActiveSupport
end
alias_method :kind_of?, :is_a?
+ # An instance of ActiveSupport::TimeWithZone is never blank
+ def blank?
+ false
+ end
+
def freeze
period; utc; time # preload instance variables before freezing
super
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index c8dbe92171..312cce2821 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,5 +1,5 @@
require 'tzinfo'
-require 'thread_safe'
+require 'concurrent'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
@@ -189,7 +189,7 @@ module ActiveSupport
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '')
- @lazy_zones_map = ThreadSafe::Cache.new
+ @lazy_zones_map = Concurrent::Map.new
class << self
# Assumes self represents an offset from UTC in seconds (as returned from
@@ -285,8 +285,12 @@ module ActiveSupport
end
end
- # Returns the offset of this time zone as a formatted string, of the
- # format "+HH:MM".
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
+ #
+ # zone = ActiveSupport::TimeZone['Central Time (US & Canada)']
+ # zone.formatted_offset # => "-06:00"
+ # zone.formatted_offset(false) # => "-0600"
def formatted_offset(colon=true, alternate_utc_string = nil)
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
end
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index cda9732cae..4f47e0519f 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -766,13 +766,11 @@ module CallbacksTest
end
class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase
- def test_returning_false_halts_callback_if_config_variable_is_not_set
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_not_set
obj = CallbackFalseTerminator.new
- assert_deprecated do
- obj.save
- assert_equal :second, obj.halted
- assert !obj.saved
- end
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
end
end
@@ -781,13 +779,11 @@ module CallbacksTest
ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true
end
- def test_returning_false_halts_callback_if_config_variable_is_true
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_true
obj = CallbackFalseTerminator.new
- assert_deprecated do
- obj.save
- assert_equal :second, obj.halted
- assert !obj.saved
- end
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
end
end
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
index a7e12117f3..5a0b99e22c 100644
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -24,9 +24,21 @@ class TransformKeysTest < ActiveSupport::TestCase
assert_equal Enumerator, enumerator.class
end
+ test "transform_keys! returns an Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_keys!
+ assert_equal Enumerator, enumerator.class
+ end
+
test "transform_keys is chainable with Enumerable methods" do
original = { a: 'a', b: 'b' }
mapped = original.transform_keys.with_index { |k, i| [k, i].join.to_sym }
assert_equal({ a0: 'a', b1: 'b' }, mapped)
end
+
+ test "transform_keys! is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ original.transform_keys!.with_index { |k, i| [k, i].join.to_sym }
+ assert_equal({ a0: 'a', b1: 'b' }, original)
+ end
end
diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb
index 45ed11fef7..7c33227dc0 100644
--- a/activesupport/test/core_ext/hash/transform_values_test.rb
+++ b/activesupport/test/core_ext/hash/transform_values_test.rb
@@ -53,9 +53,21 @@ class TransformValuesTest < ActiveSupport::TestCase
assert_equal Enumerator, enumerator.class
end
+ test "transform_values! returns an Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_values!
+ assert_equal Enumerator, enumerator.class
+ end
+
test "transform_values is chainable with Enumerable methods" do
original = { a: 'a', b: 'b' }
mapped = original.transform_values.with_index { |v, i| [v, i].join }
assert_equal({ a: 'a0', b: 'b1' }, mapped)
end
+
+ test "transform_values! is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ original.transform_values!.with_index { |v, i| [v, i].join }
+ assert_equal({ a: 'a0', b: 'b1' }, original)
+ end
end
diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 8a5e385dd7..a142096993 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -1,4 +1,3 @@
-
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index 5ea0f0eca6..25bf0207b8 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -121,7 +121,7 @@ class ObjectTryTest < ActiveSupport::TestCase
assert_equal 5, Decorator.new(@string).size
end
- def test_try_with_overriden_method_on_delegator
+ def test_try_with_overridden_method_on_delegator
assert_equal 'overridden reverse', Decorator.new(@string).reverse
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 18a8b92eb9..e6898658b5 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -1,4 +1,3 @@
-
module InflectorTestCases
SingularToPlural = {
"search" => "searches",
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index d8704716e7..2a885e32bf 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -1,4 +1,3 @@
-
require 'abstract_unit'
require 'multibyte_test_helpers'
diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb
index 11f5374017..360cf57302 100644
--- a/activesupport/test/multibyte_proxy_test.rb
+++ b/activesupport/test/multibyte_proxy_test.rb
@@ -1,4 +1,3 @@
-
require 'abstract_unit'
class MultibyteProxyText < ActiveSupport::TestCase
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 2e4b5cc873..58cf5488cd 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -1,4 +1,3 @@
-
module MultibyteTestHelpers
UNICODE_STRING = 'こにちわ'.freeze
ASCII_STRING = 'ohayo'.freeze
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 9e6d1a91d0..18228a2ac5 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -56,6 +56,14 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
+ def test_assert_difference_retval
+ incremented = assert_difference '@object.num', +1 do
+ @object.increment
+ end
+
+ assert_equal incremented, 1
+ end
+
def test_assert_difference_with_implicit_difference
assert_difference '@object.num' do
@object.increment
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index 17035069d0..69c7cd5136 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
require 'redcarpet'
require 'nokogiri'
require 'rails_guides/markdown/renderer'
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 67f5f1cdd5..f50bcddbe7 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -16,9 +16,9 @@
<% end %>
<p>
The guides for earlier releases:
-<a href="http://guides.rubyonrails.org/v4.2.0/">Rails 4.2.0</a>,
-<a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>,
-<a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>,
-<a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and
-<a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
+<a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>,
+<a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>,
+<a href="http://guides.rubyonrails.org/v4.0/">Rails 4.0</a>,
+<a href="http://guides.rubyonrails.org/v3.2/">Rails 3.2</a>, and
+<a href="http://guides.rubyonrails.org/v2.3/">Rails 2.3</a>.
</p>
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index c39cd34e9a..4800cece82 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -760,8 +760,8 @@ config.action_mailer.smtp_settings = {
enable_starttls_auto: true }
```
Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure.
-You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or
-use another ESP to send email by replacing 'smpt.gmail.com' above with the address of your provider.
+You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or
+use another ESP to send email by replacing 'smtp.gmail.com' above with the address of your provider.
Mailer Testing
--------------
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 00c41a480e..76454e77c7 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -147,6 +147,39 @@ xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
end
```
+#### Jbuilder
+[Jbuilder](https://github.com/rails/jbuilder) is a gem that's
+maintained by the Rails team and included in the default Rails Gemfile.
+It's similar to Builder, but is used to generate JSON, instead of XML.
+
+If you don't have it, you can add the following to your Gemfile:
+
+```ruby
+gem 'jbuilder'
+```
+
+A Jbuilder object named `json` is automatically made available to templates with
+a `.jbuilder` extension.
+
+Here is a basic example:
+
+```ruby
+json.name("Alex")
+json.email("alex@example.com")
+```
+
+would produce:
+
+```json
+{
+ "name": "Alex",
+ "email: "alex@example.com"
+}
+```
+
+See the [Jbuilder documention](https://github.com/rails/jbuilder#jbuilder) for
+more examples and information.
+
#### Template Caching
By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will check the file's modification time and recompile it in development mode.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index dadac7fb54..7f88c13dc0 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -242,7 +242,7 @@ end
>> person = Person.new
>> person.valid?
->> person.errors.details[:name] #=> [{error: :blank}]
+>> person.errors.details[:name] # => [{error: :blank}]
```
Using `details` with custom validators is covered in the [Working with
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 01bf928407..367a1bf7c0 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1865,15 +1865,15 @@ The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wr
```ruby
"2010-07-27".to_date # => Tue, 27 Jul 2010
-"2010-07-27 23:37:00".to_time # => Tue Jul 27 23:37:00 UTC 2010
+"2010-07-27 23:37:00".to_time # => 2010-07-27 23:37:00 +0200
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
```
`to_time` receives an optional argument `:utc` or `:local`, to indicate which time zone you want the time in:
```ruby
-"2010-07-27 23:42:00".to_time(:utc) # => Tue Jul 27 23:42:00 UTC 2010
-"2010-07-27 23:42:00".to_time(:local) # => Tue Jul 27 23:42:00 +0200 2010
+"2010-07-27 23:42:00".to_time(:utc) # => 2010-07-27 23:42:00 UTC
+"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200
```
Default is `:utc`.
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index 28727a51bd..feaaff166a 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -194,7 +194,6 @@ An API application comes with the following middlewares by default:
- `ActionDispatch::RemoteIp`
- `ActionDispatch::Reloader`
- `ActionDispatch::Callbacks`
-- `ActionDispatch::ParamsParser`
- `Rack::Head`
- `Rack::ConditionalGet`
- `Rack::ETag`
@@ -292,9 +291,9 @@ instructions in the `Rack::Sendfile` documentation.
NOTE: The `Rack::Sendfile` middleware is always outside of the `Rack::Lock`
mutex, even in single-threaded applications.
-### Using ActionDispatch::ParamsParser
+### Using ActionDispatch::Request
-`ActionDispatch::ParamsParser` will take parameters from the client in the JSON
+`ActionDispatch::Request#params` will take parameters from the client in the JSON
format and make them available in your controller inside `params`.
To use this, your client will need to make a request with JSON-encoded parameters
@@ -313,7 +312,7 @@ jQuery.ajax({
});
```
-`ActionDispatch::ParamsParser` will see the `Content-Type` and your parameters
+`ActionDispatch::Request` will see the `Content-Type` and your parameters
will be:
```ruby
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index a4feff798d..526bf768cc 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -239,7 +239,7 @@ You can quickly test the RDoc output with the following command:
```
$ echo "+:to_param+" | rdoc --pipe
-#=> <p><code>:to_param</code></p>
+# => <p><code>:to_param</code></p>
```
### Regular Font
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 1191f5edfe..60790b33a4 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -768,7 +768,7 @@ The `belongs_to` association creates a one-to-one match with another model. In d
When you declare a `belongs_to` association, the declaring class automatically gains five methods related to the association:
-* `association(force_reload = false)`
+* `association`
* `association=(associate)`
* `build_association(attributes = {})`
* `create_association(attributes = {})`
@@ -794,7 +794,7 @@ create_customer!
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.
-##### `association(force_reload = false)`
+##### `association`
The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`.
@@ -802,7 +802,11 @@ The `association` method returns the associated object, if any. If no associated
@customer = @order.customer
```
-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), pass `true` as the `force_reload` argument.
+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
+```
##### `association=(associate)`
@@ -1113,7 +1117,7 @@ The `has_one` association creates a one-to-one match with another model. In data
When you declare a `has_one` association, the declaring class automatically gains five methods related to the association:
-* `association(force_reload = false)`
+* `association`
* `association=(associate)`
* `build_association(attributes = {})`
* `create_association(attributes = {})`
@@ -1139,7 +1143,7 @@ create_account!
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.
-##### `association(force_reload = false)`
+##### `association`
The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`.
@@ -1147,7 +1151,11 @@ The `association` method returns the associated object, if any. If no associated
@account = @supplier.account
```
-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), pass `true` as the `force_reload` argument.
+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
+@account = @supplier.reload.account
+```
##### `association=(associate)`
@@ -1380,7 +1388,7 @@ The `has_many` association creates a one-to-many relationship with another model
When you declare a `has_many` association, the declaring class automatically gains 16 methods related to the association:
-* `collection(force_reload = false)`
+* `collection`
* `collection<<(object, ...)`
* `collection.delete(object, ...)`
* `collection.destroy(object, ...)`
@@ -1408,7 +1416,7 @@ end
Each instance of the `Customer` model will have these methods:
```ruby
-orders(force_reload = false)
+orders
orders<<(object, ...)
orders.delete(object, ...)
orders.destroy(object, ...)
@@ -1426,7 +1434,7 @@ orders.create(attributes = {})
orders.create!(attributes = {})
```
-##### `collection(force_reload = false)`
+##### `collection`
The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.
@@ -1892,7 +1900,7 @@ The `has_and_belongs_to_many` association creates a many-to-many relationship wi
When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 16 methods related to the association:
-* `collection(force_reload = false)`
+* `collection`
* `collection<<(object, ...)`
* `collection.delete(object, ...)`
* `collection.destroy(object, ...)`
@@ -1920,7 +1928,7 @@ end
Each instance of the `Part` model will have these methods:
```ruby
-assemblies(force_reload = false)
+assemblies
assemblies<<(object, ...)
assemblies.delete(object, ...)
assemblies.destroy(object, ...)
@@ -1945,7 +1953,7 @@ If the join table for a `has_and_belongs_to_many` association has additional col
WARNING: The use of extra attributes on the join table in a `has_and_belongs_to_many` association is deprecated. If you require this sort of complex behavior on the table that joins two models in a many-to-many relationship, you should use a `has_many :through` association instead of `has_and_belongs_to_many`.
-##### `collection(force_reload = false)`
+##### `collection`
The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index cd265331d6..e85f9fc9c6 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -412,7 +412,7 @@ Ruby version 2.2.2 (x86_64-linux)
RubyGems version 2.4.6
Rack version 1.6
JavaScript Runtime Node.js (V8)
-Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
+Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag
Application root /home/foobar/commandsapp
Environment development
Database adapter sqlite3
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 5e72b96787..f0d87e4dc8 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -214,7 +214,6 @@ Every Rails application comes with a standard set of middleware which it uses in
* `ActionDispatch::Cookies` sets cookies for the request.
* `ActionDispatch::Session::CookieStore` is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the `config.action_controller.session_store` to an alternate value. Additionally, options passed to this can be configured by using `config.action_controller.session_options`.
* `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value.
-* `ActionDispatch::ParamsParser` parses out parameters from the request into `params`.
* `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types.
* `Rack::Head` converts HEAD requests to GET requests and serves them as so.
@@ -642,7 +641,7 @@ TIP: You don't have to update the database configurations manually. If you look
### Connection Preference
-Since there are two ways to set your connection, via environment variable it is important to understand how the two can interact.
+Since there are two ways to configure your connection (using `config/database.yml` or using an environment variable) it is important to understand how they can interact.
If you have an empty `config/database.yml` file but your `ENV['DATABASE_URL']` is present, then Rails will connect to the database via your environment variable:
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 3b1588b75a..71844b7990 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -150,7 +150,7 @@ When you include the engine into an application later on, you will do so with
this line in the Rails application's `Gemfile`:
```ruby
-gem 'blorgh', path: "vendor/engines/blorgh"
+gem 'blorgh', path: 'engines/blorgh'
```
Don't forget to run `bundle install` as usual. By specifying it as a gem within
@@ -639,7 +639,7 @@ However, because you are developing the `blorgh` engine on your local machine,
you will need to specify the `:path` option in your `Gemfile`:
```ruby
-gem 'blorgh', path: "/path/to/blorgh"
+gem 'blorgh', path: 'engines/blorgh'
```
Then run `bundle` to install the gem.
@@ -843,28 +843,10 @@ above the "Title" output inside `app/views/blorgh/articles/show.html.erb`:
```html+erb
<p>
<b>Author:</b>
- <%= @article.author %>
+ <%= @article.author.name %>
</p>
```
-By outputting `@article.author` using the `<%=` tag, the `to_s` method will be
-called on the object. By default, this will look quite ugly:
-
-```
-#<User:0x00000100ccb3b0>
-```
-
-This is undesirable. It would be much better to have the user's name there. To
-do this, add a `to_s` method to the `User` class within the application:
-
-```ruby
-def to_s
- name
-end
-```
-
-Now instead of the ugly Ruby object output, the author's name will be displayed.
-
#### Using a Controller Provided by the Application
Because Rails controllers generally share code for things like authentication
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 987320a0f8..ea79855919 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -109,7 +109,7 @@ The **translations load path** (`I18n.load_path`) is an array of paths to files
NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced.
-The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
+The default `config/application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
```ruby
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
@@ -117,7 +117,7 @@ The default `application.rb` file has instructions on how to add locales from an
# config.i18n.default_locale = :de
```
-The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `application.rb`:
+The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `config/application.rb`:
```ruby
# config/initializers/locale.rb
diff --git a/guides/source/kindle/layout.html.erb b/guides/source/kindle/layout.html.erb
index f0a286210b..fd8746776b 100644
--- a/guides/source/kindle/layout.html.erb
+++ b/guides/source/kindle/layout.html.erb
@@ -14,12 +14,12 @@
<% if content_for? :header_section %>
<%= yield :header_section %>
- <div class="pagebreak">
+ <div class="pagebreak"></div>
<% end %>
<% if content_for? :index_section %>
<%= yield :index_section %>
- <div class="pagebreak">
+ <div class="pagebreak"></div>
<% end %>
<%= yield.html_safe %>
diff --git a/guides/source/kindle/toc.ncx.erb b/guides/source/kindle/toc.ncx.erb
index 2c6d8e3bdf..5094fea4ca 100644
--- a/guides/source/kindle/toc.ncx.erb
+++ b/guides/source/kindle/toc.ncx.erb
@@ -32,12 +32,12 @@
</navPoint>
<navPoint class="article" id="credits" playOrder="3">
<navLabel><text>Credits</text></navLabel>
- <content src="credits.html">
+ <content src="credits.html"/>
</navPoint>
<navPoint class="article" id="copyright" playOrder="4">
<navLabel><text>Copyright &amp; License</text></navLabel>
- <content src="copyright.html">
- </navPoint>
+ <content src="copyright.html"/>
+ </navPoint>
</navPoint>
<% play_order = 4 %>
@@ -47,7 +47,7 @@
<text><%= section['name'] %></text>
</navLabel>
<content src="<%=section['documents'].first['url'] %>"/>
-
+
<% section['documents'].each_with_index do |document, document_no| %>
<navPoint class="article" id="_<%=section_no+1%>.<%=document_no+1%>" playOrder="<%=play_order +=1 %>">
<navLabel>
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 1e2fe94010..0db90fedb3 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -121,7 +121,6 @@ use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
-use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
@@ -284,10 +283,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Sets up the flash keys. Only available if `config.action_controller.session_store` is set to a value.
-**`ActionDispatch::ParamsParser`**
-
-* Parses out parameters from the request into `params`.
-
**`Rack::Head`**
* Converts HEAD requests to `GET` requests and serves them as so.
diff --git a/guides/source/routing.md b/guides/source/routing.md
index e4799d93fa..1fd38c0940 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -7,7 +7,7 @@ This guide covers the user-facing features of Rails routing.
After reading this guide, you will know:
-* How to interpret the code in `routes.rb`.
+* How to interpret the code in `config/routes.rb`.
* How to construct your own routes, using either the preferred resourceful style or the `match` method.
* What parameters to expect an action to receive.
* How to automatically create paths and URLs using route helpers.
@@ -1118,7 +1118,7 @@ Rails offers facilities for inspecting and testing your 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.
-Both methods will list all of your routes, in the same order that they appear in `routes.rb`. For each route, you'll see:
+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:
* The route name (if any)
* The HTTP verb used (if the route doesn't respond to all verbs)
diff --git a/guides/source/testing.md b/guides/source/testing.md
index aa3497fa13..435de30acc 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -302,7 +302,7 @@ specify to make your test failure messages clearer. It's not required.
The above are a subset of assertions that minitest supports. For an exhaustive &
more up-to-date list, please check
[Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically
-[`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
+[`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html).
Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier.
@@ -478,7 +478,7 @@ default. Loading involves three steps:
2. Load the fixture data into the table
3. Dump the fixture data into a method in case you want to access it directly
-TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html))
+TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html)).
#### Fixtures are Active Record objects
@@ -750,9 +750,9 @@ end
After a request has been made and processed, you will have 3 Hash objects ready for use:
-* `cookies` - Any cookies that are set.
-* `flash` - Any objects living in the flash.
-* `session` - Any object living in session variables.
+* `cookies` - Any cookies that are set
+* `flash` - Any objects living in the flash
+* `session` - Any object living in session variables
As is the case with normal Hash objects, you can access the values by referencing the keys by string. You can also reference them by symbol name. For example:
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 88eade5c5a..21062f3a53 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -72,7 +72,6 @@ module Rails
middleware.use ::ActionDispatch::Flash
end
- middleware.use ::ActionDispatch::ParamsParser
middleware.use ::Rack::Head
middleware.use ::Rack::ConditionalGet
middleware.use ::Rack::ETag, "no-cache"
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 2c3b04043f..f73e9a96ba 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -1,5 +1,5 @@
<% if namespaced? -%>
-require_dependency "<%= namespaced_file_path %>/application_controller"
+require_dependency "<%= namespaced_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index ed0ea9dd4d..38004fe99a 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -31,7 +31,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{@email_action}"
end
else
- @part = find_preferred_part(request.format, Mime::HTML, Mime::TEXT)
+ @part = find_preferred_part(request.format, Mime::Type[:HTML], Mime::Type[:TEXT])
render action: 'email', layout: false, formats: %w[html]
end
else
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index bb6e49966d..cf95cd8ad0 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -95,8 +95,8 @@
<% if @email.multipart? %>
<dd>
<select onchange="document.getElementsByName('messageBody')[0].src=this.options[this.selectedIndex].value;">
- <option <%= request.format == Mime::HTML ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option>
- <option <%= request.format == Mime::TEXT ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option>
+ <option <%= request.format == Mime::Type[:HTML] ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option>
+ <option <%= request.format == Mime::Type[:TEXT] ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option>
</select>
</dd>
<% end %>
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index dacab08ec3..1923433c4a 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -42,7 +42,8 @@ module Minitest
ENV["RAILS_ENV"] = options[:environment] || "test"
unless run_with_autorun
- ::Rails::TestRequirer.require_files @rake_patterns || options[:patterns]
+ patterns = defined?(@rake_patterns) ? @rake_patterns : options[:patterns]
+ ::Rails::TestRequirer.require_files(patterns)
end
unless options[:full_backtrace] || ENV["BACKTRACE"]
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index f677a7c42a..464fc1688c 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1304,7 +1304,7 @@ module ApplicationTests
assert_equal 'custom key', Rails.application.config.my_custom_config['key']
end
- test "config_for with syntax error show a more descritive exception" do
+ test "config_for with syntax error show a more descriptive exception" do
app_file 'config/custom.yml', <<-RUBY
development:
key: foo:
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index d298e8d632..138c63266e 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -43,7 +43,6 @@ module ApplicationTests
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
- "ActionDispatch::ParamsParser",
"Rack::Head",
"Rack::ConditionalGet",
"Rack::ETag"
@@ -70,7 +69,6 @@ module ApplicationTests
"ActionDispatch::Callbacks",
"ActiveRecord::ConnectionAdapters::ConnectionManagement",
"ActiveRecord::QueryCache",
- "ActionDispatch::ParamsParser",
"Rack::Head",
"Rack::ConditionalGet",
"Rack::ETag"
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index a040dd4cf6..0da0928b48 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -1,4 +1,3 @@
-# coding:utf-8
require "isolation/abstract_unit"
require "active_support/core_ext/string/strip"
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index cbada6be97..0777714d35 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -21,6 +21,12 @@ module ApplicationTests
assert_equal 200, last_response.status
end
+ test "rails/info in development" do
+ app("development")
+ get "/rails/info"
+ assert_equal 302, last_response.status
+ end
+
test "rails/info/routes in development" do
app("development")
get "/rails/info/routes"
@@ -63,6 +69,12 @@ module ApplicationTests
assert_equal 404, last_response.status
end
+ test "rails/info in production" do
+ app("production")
+ get "/rails/info"
+ assert_equal 404, last_response.status
+ end
+
test "rails/info/routes in production" do
app("production")
get "/rails/info/routes"
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index daf362357c..e5f10a89d3 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
require 'generators/shared_generator_tests'
-require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_APP_FILES = %w(
.gitignore
@@ -117,35 +116,33 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [app_root]
- Rails.application.config.root = app_moved_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
+ stub_rails_application(app_moved_root) do
+ Rails.application.stub(:is_a?, -> *args { Rails::Application }) do
+ FileUtils.mv(app_root, app_moved_root)
- FileUtils.mv(app_root, app_moved_root)
+ # make sure we are in correct dir
+ FileUtils.cd(app_moved_root)
- # make sure we are in correct dir
- FileUtils.cd(app_moved_root)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
- destination_root: app_moved_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/
- assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
+ destination_root: app_moved_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/
+ assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/
+ end
+ end
end
def test_rails_update_generates_correct_session_key
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/
+ end
end
def test_new_application_use_json_serialzier
@@ -158,14 +155,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
+ end
end
def test_rails_update_does_not_create_callback_terminator_initializer
@@ -174,14 +169,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_no_file "#{app_root}/config/initializers/callback_terminator.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
end
def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present
@@ -190,14 +183,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/initializers/callback_terminator.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
end
def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured
@@ -206,14 +197,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
+ end
end
def test_rails_update_does_not_create_active_record_belongs_to_required_by_default
@@ -222,14 +211,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.rm("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ end
end
def test_rails_update_does_not_remove_active_record_belongs_to_required_by_default_if_already_present
@@ -238,14 +225,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.touch("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ end
end
def test_application_names_are_not_singularized
@@ -456,13 +441,15 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_usage_read_from_file
- File.expects(:read).returns("USAGE FROM FILE")
- assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc
+ assert_called(File, :read, returns: "USAGE FROM FILE") do
+ assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc
+ end
end
def test_default_usage
- Rails::Generators::AppGenerator.expects(:usage_path).returns(nil)
- assert_match(/Create rails files for app generator/, Rails::Generators::AppGenerator.desc)
+ assert_called(Rails::Generators::AppGenerator, :usage_path, returns: nil) do
+ assert_match(/Create rails files for app generator/, Rails::Generators::AppGenerator.desc)
+ end
end
def test_default_namespace
@@ -538,18 +525,31 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_spring_binstubs
jruby_skip "spring doesn't run on JRuby"
- generator.stubs(:bundle_command).with('install')
- generator.expects(:bundle_command).with('exec spring binstub --all').once
- quietly { generator.invoke_all }
+ command_check = -> command do
+ @binstub_called ||= 0
+
+ case command
+ when 'install'
+ # Called when running bundle, we just want to stub it so nothing to do here.
+ when 'exec spring binstub --all'
+ @binstub_called += 1
+ assert_equal 1, @binstub_called, "exec spring binstub --all expected to be called once, but was called #{@install_called} times."
+ end
+ end
+
+ generator.stub :bundle_command, command_check do
+ quietly { generator.invoke_all }
+ end
end
def test_spring_no_fork
jruby_skip "spring doesn't run on JRuby"
- Process.stubs(:respond_to?).with(:fork).returns(false)
- run_generator
+ assert_called_with(Process, :respond_to?, [:fork], returns: false) do
+ run_generator
- assert_file "Gemfile" do |content|
- assert_no_match(/spring/, content)
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
end
end
@@ -651,18 +651,37 @@ class AppGeneratorTest < Rails::Generators::TestCase
template = %{ after_bundle { run 'echo ran after_bundle' } }
template.instance_eval "def read; self; end" # Make the string respond to read
- generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
+ check_open = -> *args do
+ assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args
+ template
+ end
- bundler_first = sequence('bundle, binstubs, after_bundle')
- generator.expects(:bundle_command).with('install').once.in_sequence(bundler_first)
- generator.expects(:bundle_command).with('exec spring binstub --all').in_sequence(bundler_first)
- generator.expects(:run).with('echo ran after_bundle').in_sequence(bundler_first)
+ sequence = ['install', 'exec spring binstub --all', 'echo ran after_bundle']
+ ensure_bundler_first = -> command do
+ @sequence_step ||= 0
- quietly { generator.invoke_all }
+ assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
+ @sequence_step += 1
+ end
+
+ generator([destination_root], template: path).stub(:open, check_open, template) do
+ generator.stub(:bundle_command, ensure_bundler_first) do
+ generator.stub(:run, ensure_bundler_first) do
+ quietly { generator.invoke_all }
+ end
+ end
+ end
end
protected
+ def stub_rails_application(root)
+ Rails.application.config.root = root
+ Rails.application.class.stub(:name, "Myapp") do
+ yield
+ end
+ end
+
def action(*args, &block)
capture(:stdout) { generator.send(*args, &block) }
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index e839b67960..c4ee6602c5 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -281,6 +281,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Controller
assert_file "app/controllers/test_app/admin/roles_controller.rb" do |content|
assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content)
+ assert_match(%r(require_dependency "test_app/application_controller"), content)
end
assert_file "test/controllers/test_app/admin/roles_controller_test.rb",
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index afccf9d885..0625e5fbd7 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/plugin/plugin_generator'
require 'generators/shared_generator_tests'
-require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_PLUGIN_FILES = %w(
.gitignore
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 77372fb514..acb78ec888 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -28,9 +28,22 @@ module SharedGeneratorTests
def assert_generates_with_bundler(options = {})
generator([destination_root], options)
- generator.expects(:bundle_command).with('install').once
- generator.stubs(:bundle_command).with('exec spring binstub --all')
- quietly { generator.invoke_all }
+
+ command_check = -> command do
+ @install_called ||= 0
+
+ case command
+ when 'install'
+ @install_called += 1
+ assert_equal 1, @install_called, "install expected to be called once, but was called #{@install_called} times"
+ when 'exec spring binstub --all'
+ # Called when running tests with spring, let through unscathed.
+ end
+ end
+
+ generator.stub :bundle_command, command_check do
+ quietly { generator.invoke_all }
+ end
end
def test_generation_runs_bundle_install
@@ -91,8 +104,14 @@ module SharedGeneratorTests
template = %{ say "It works!" }
template.instance_eval "def read; self; end" # Make the string respond to read
- generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
+ check_open = -> *args do
+ assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args
+ template
+ end
+
+ generator([destination_root], template: path).stub(:open, check_open, template) do
+ quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
+ end
end
def test_dev_option
@@ -107,18 +126,19 @@ module SharedGeneratorTests
end
def test_skip_gemfile
- generator([destination_root], skip_gemfile: true).expects(:bundle_command).never
- quietly { generator.invoke_all }
- assert_no_file 'Gemfile'
+ assert_not_called(generator([destination_root], skip_gemfile: true), :bundle_command) do
+ quietly { generator.invoke_all }
+ assert_no_file 'Gemfile'
+ end
end
def test_skip_bundle
- generator([destination_root], skip_bundle: true).expects(:bundle_command).never
- quietly { generator.invoke_all }
-
- # skip_bundle is only about running bundle install, ensure the Gemfile is still
- # generated.
- assert_file 'Gemfile'
+ assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do
+ quietly { generator.invoke_all }
+ # skip_bundle is only about running bundle install, ensure the Gemfile is still
+ # generated.
+ assert_file 'Gemfile'
+ end
end
def test_skip_git
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 65d8a55421..41d36680e0 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -159,6 +159,7 @@ module TestHelpers
require "rails"
require "action_controller/railtie"
require "action_view/railtie"
+ require 'action_dispatch/middleware/flash'
app = Class.new(Rails::Application)
app.config.eager_load = false