aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml7
-rw-r--r--Gemfile8
-rw-r--r--Gemfile.lock281
-rw-r--r--actionmailer/CHANGELOG.md2
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb2
-rw-r--r--actionmailer/lib/rails/generators/mailer/USAGE4
-rw-r--r--actionmailer/test/abstract_unit.rb2
-rw-r--r--actionmailer/test/base_test.rb1
-rw-r--r--actionmailer/test/message_delivery_test.rb1
-rw-r--r--actionmailer/test/test_helper_test.rb22
-rw-r--r--actionpack/CHANGELOG.md105
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb10
-rw-r--r--actionpack/lib/abstract_controller/railties/routes_helpers.rb4
-rw-r--r--actionpack/lib/abstract_controller/translation.rb15
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/metal.rb8
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb78
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb10
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb11
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb4
-rw-r--r--actionpack/lib/action_controller/renderer.rb100
-rw-r--r--actionpack/lib/action_controller/test_case.rb133
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb40
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb24
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb210
-rw-r--r--actionpack/test/abstract/translation_test.rb33
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb4
-rw-r--r--actionpack/test/controller/base_test.rb27
-rw-r--r--actionpack/test/controller/caching_test.rb8
-rw-r--r--actionpack/test/controller/default_url_options_with_before_action_test.rb3
-rw-r--r--actionpack/test/controller/flash_hash_test.rb25
-rw-r--r--actionpack/test/controller/flash_test.rb16
-rw-r--r--actionpack/test/controller/force_ssl_test.rb6
-rw-r--r--actionpack/test/controller/helper_test.rb6
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb7
-rw-r--r--actionpack/test/controller/integration_test.rb329
-rw-r--r--actionpack/test/controller/localized_templates_test.rb2
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb12
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb4
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb48
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb9
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb4
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_action_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_layout_test.rb6
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_test.rb6
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb48
-rw-r--r--actionpack/test/controller/permitted_params_test.rb4
-rw-r--r--actionpack/test/controller/render_js_test.rb4
-rw-r--r--actionpack/test/controller/render_json_test.rb4
-rw-r--r--actionpack/test/controller/render_test.rb116
-rw-r--r--actionpack/test/controller/render_xml_test.rb4
-rw-r--r--actionpack/test/controller/renderer_test.rb103
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb39
-rw-r--r--actionpack/test/controller/required_params_test.rb8
-rw-r--r--actionpack/test/controller/resources_test.rb30
-rw-r--r--actionpack/test/controller/routing_test.rb1
-rw-r--r--actionpack/test/controller/send_file_test.rb1
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb12
-rw-r--r--actionpack/test/controller/test_case_test.rb397
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb1
-rw-r--r--actionpack/test/controller/webservice_test.rb23
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb64
-rw-r--r--actionpack/test/dispatch/exception_wrapper_test.rb17
-rw-r--r--actionpack/test/dispatch/mount_test.rb2
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb8
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb15
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb5
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request_id_test.rb2
-rw-r--r--actionpack/test/dispatch/response_test.rb4
-rw-r--r--actionpack/test/dispatch/routing_test.rb34
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb10
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb24
-rw-r--r--actionpack/test/dispatch/ssl_test.rb2
-rw-r--r--actionpack/test/dispatch/static_test.rb1
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb4
-rw-r--r--actionpack/test/journey/router/utils_test.rb1
-rw-r--r--actionview/CHANGELOG.md21
-rw-r--r--actionview/lib/action_view/base.rb8
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/record_tag_helper.rb111
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb127
-rw-r--r--actionview/lib/action_view/helpers/tags.rb1
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb49
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb9
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb16
-rw-r--r--actionview/lib/action_view/helpers/tags/placeholderable.rb18
-rw-r--r--actionview/lib/action_view/helpers/tags/translator.rb40
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb6
-rw-r--r--actionview/lib/action_view/railtie.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb5
-rw-r--r--actionview/lib/action_view/rendering.rb7
-rw-r--r--actionview/lib/action_view/template.rb15
-rw-r--r--actionview/lib/action_view/test_case.rb2
-rw-r--r--actionview/test/abstract_unit.rb2
-rw-r--r--actionview/test/actionpack/controller/render_test.rb19
-rw-r--r--actionview/test/activerecord/controller_runtime_test.rb2
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb2
-rw-r--r--actionview/test/fixtures/test/_FooBar.html.erb1
-rw-r--r--actionview/test/fixtures/test/_a-in.html.erb0
-rw-r--r--actionview/test/lib/controller/fake_models.rb16
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb2
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb34
-rw-r--r--actionview/test/template/dependency_tracker_test.rb1
-rw-r--r--actionview/test/template/form_helper_test.rb54
-rw-r--r--actionview/test/template/record_tag_helper_test.rb90
-rw-r--r--actionview/test/template/render_test.rb22
-rw-r--r--actionview/test/template/streaming_render_test.rb1
-rw-r--r--actionview/test/template/text_helper_test.rb1
-rw-r--r--actionview/test/template/url_helper_test.rb24
-rw-r--r--activejob/CHANGELOG.md40
-rw-r--r--activejob/lib/active_job/arguments.rb49
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb37
-rw-r--r--activejob/lib/active_job/test_helper.rb69
-rw-r--r--activejob/test/cases/argument_serialization_test.rb39
-rw-r--r--activejob/test/cases/test_helper_test.rb117
-rw-r--r--activejob/test/jobs/kwargs_job.rb7
-rw-r--r--activejob/test/support/integration/helper.rb2
-rw-r--r--activemodel/CHANGELOG.md63
-rw-r--r--activemodel/README.rdoc2
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb63
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/dirty.rb43
-rw-r--r--activemodel/lib/active_model/errors.rb109
-rw-r--r--activemodel/lib/active_model/lint.rb60
-rw-r--r--activemodel/lib/active_model/locale/en.yml3
-rw-r--r--activemodel/lib/active_model/model.rb7
-rw-r--r--activemodel/lib/active_model/naming.rb1
-rw-r--r--activemodel/lib/active_model/validations.rb33
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb24
-rw-r--r--activemodel/lib/active_model/validator.rb6
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb107
-rw-r--r--activemodel/test/cases/errors_test.rb106
-rw-r--r--activemodel/test/cases/model_test.rb4
-rw-r--r--activemodel/test/cases/validations/absence_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb1
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb14
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/validates_test.rb1
-rw-r--r--activemodel/test/cases/validations/validations_context_test.rb1
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations_test.rb20
-rw-r--r--activemodel/test/models/topic.rb4
-rw-r--r--activerecord/CHANGELOG.md266
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/aggregations.rb29
-rw-r--r--activerecord/lib/active_record/associations.rb52
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb87
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb26
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb17
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb19
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute.rb27
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb157
-rw-r--r--activerecord/lib/active_record/attribute_decorators.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb37
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb18
-rw-r--r--activerecord/lib/active_record/attribute_set.rb24
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb16
-rw-r--r--activerecord/lib/active_record/attributes.rb256
-rw-r--r--activerecord/lib/active_record/autosave_association.rb16
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb58
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb61
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb113
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb98
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb122
-rw-r--r--activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb43
-rw-r--r--activerecord/lib/active_record/core.rb69
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb98
-rw-r--r--activerecord/lib/active_record/errors.rb16
-rw-r--r--activerecord/lib/active_record/fixtures.rb15
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/locale/en.yml1
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb6
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb20
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb93
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb4
-rw-r--r--activerecord/lib/active_record/null_relation.rb12
-rw-r--r--activerecord/lib/active_record/persistence.rb25
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake8
-rw-r--r--activerecord/lib/active_record/relation.rb70
-rw-r--r--activerecord/lib/active_record/relation/batches.rb58
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb24
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/from_clause.rb32
-rw-r--r--activerecord/lib/active_record/relation/merger.rb51
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb50
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb22
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb19
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb240
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb5
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb173
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb34
-rw-r--r--activerecord/lib/active_record/result.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/scoping.rb4
-rw-r--r--activerecord/lib/active_record/scoping/named.rb25
-rw-r--r--activerecord/lib/active_record/secure_token.rb3
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb4
-rw-r--r--activerecord/lib/active_record/statement_cache.rb18
-rw-r--r--activerecord/lib/active_record/suppressor.rb55
-rw-r--r--activerecord/lib/active_record/table_metadata.rb9
-rw-r--r--activerecord/lib/active_record/transactions.rb12
-rw-r--r--activerecord/lib/active_record/type.rb51
-rw-r--r--activerecord/lib/active_record/type/adapter_specific_registry.rb142
-rw-r--r--activerecord/lib/active_record/type/binary.rb6
-rw-r--r--activerecord/lib/active_record/type/date.rb11
-rw-r--r--activerecord/lib/active_record/type/date_time.rb27
-rw-r--r--activerecord/lib/active_record/type/decimal.rb2
-rw-r--r--activerecord/lib/active_record/type/decorator.rb14
-rw-r--r--activerecord/lib/active_record/type/float.rb12
-rw-r--r--activerecord/lib/active_record/type/helpers.rb4
-rw-r--r--activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb30
-rw-r--r--activerecord/lib/active_record/type/helpers/mutable.rb18
-rw-r--r--activerecord/lib/active_record/type/helpers/numeric.rb34
-rw-r--r--activerecord/lib/active_record/type/helpers/time_value.rb58
-rw-r--r--activerecord/lib/active_record/type/integer.rb18
-rw-r--r--activerecord/lib/active_record/type/mutable.rb16
-rw-r--r--activerecord/lib/active_record/type/numeric.rb36
-rw-r--r--activerecord/lib/active_record/type/serialized.rb19
-rw-r--r--activerecord/lib/active_record/type/string.rb6
-rw-r--r--activerecord/lib/active_record/type/time.rb5
-rw-r--r--activerecord/lib/active_record/type/time_value.rb42
-rw-r--r--activerecord/lib/active_record/type/value.rb92
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb21
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb16
-rw-r--r--activerecord/lib/active_record/validations/length.rb12
-rw-r--r--activerecord/lib/active_record/validations/presence.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb6
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb9
-rw-r--r--activerecord/test/cases/adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/datetime_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb11
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb20
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb15
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/change_schema_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/cidr_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb38
-rw-r--r--activerecord/test/cases/adapters/postgresql/infinity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/integer_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb39
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/numbers_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb64
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb3
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb13
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb1
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb7
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb115
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb10
-rw-r--r--activerecord/test/cases/associations/required_test.rb28
-rw-r--r--activerecord/test/cases/associations_test.rb26
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb16
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb10
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb16
-rw-r--r--activerecord/test/cases/attribute_set_test.rb25
-rw-r--r--activerecord/test/cases/attribute_test.rb51
-rw-r--r--activerecord/test/cases/attributes_test.rb87
-rw-r--r--activerecord/test/cases/autosave_association_test.rb5
-rw-r--r--activerecord/test/cases/base_test.rb8
-rw-r--r--activerecord/test/cases/batches_test.rb44
-rw-r--r--activerecord/test/cases/binary_test.rb1
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb24
-rw-r--r--activerecord/test/cases/calculations_test.rb11
-rw-r--r--activerecord/test/cases/callbacks_test.rb21
-rw-r--r--activerecord/test/cases/column_definition_test.rb79
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb7
-rw-r--r--activerecord/test/cases/core_test.rb11
-rw-r--r--activerecord/test/cases/counter_cache_test.rb18
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb111
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb8
-rw-r--r--activerecord/test/cases/enum_test.rb60
-rw-r--r--activerecord/test/cases/explain_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb21
-rw-r--r--activerecord/test/cases/helper.rb4
-rw-r--r--activerecord/test/cases/inheritance_test.rb10
-rw-r--r--activerecord/test/cases/integration_test.rb1
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb8
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb17
-rw-r--r--activerecord/test/cases/migration/columns_test.rb8
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb5
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb41
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb14
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb4
-rw-r--r--activerecord/test/cases/migration/table_and_index_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb40
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb8
-rw-r--r--activerecord/test/cases/multiple_db_test.rb14
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb19
-rw-r--r--activerecord/test/cases/primary_keys_test.rb4
-rw-r--r--activerecord/test/cases/quoting_test.rb10
-rw-r--r--activerecord/test/cases/reflection_test.rb13
-rw-r--r--activerecord/test/cases/relation/merging_test.rb15
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb10
-rw-r--r--activerecord/test/cases/relation/or_test.rb84
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb122
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb182
-rw-r--r--activerecord/test/cases/relation/where_test.rb38
-rw-r--r--activerecord/test/cases/relation_test.rb38
-rw-r--r--activerecord/test/cases/relations_test.rb80
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb13
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb12
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb4
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb8
-rw-r--r--activerecord/test/cases/secure_token_test.rb7
-rw-r--r--activerecord/test/cases/suppressor_test.rb21
-rw-r--r--activerecord/test/cases/test_case.rb20
-rw-r--r--activerecord/test/cases/time_precision_test.rb108
-rw-r--r--activerecord/test/cases/timestamp_test.rb21
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb25
-rw-r--r--activerecord/test/cases/transactions_test.rb2
-rw-r--r--activerecord/test/cases/type/adapter_specific_registry_test.rb133
-rw-r--r--activerecord/test/cases/type/decimal_test.rb16
-rw-r--r--activerecord/test/cases/type/integer_test.rb75
-rw-r--r--activerecord/test/cases/type/string_test.rb10
-rw-r--r--activerecord/test/cases/type/unsigned_integer_test.rb4
-rw-r--r--activerecord/test/cases/type_test.rb39
-rw-r--r--activerecord/test/cases/types_test.rb113
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb3
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb20
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb17
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb18
-rw-r--r--activerecord/test/cases/validations_test.rb3
-rw-r--r--activerecord/test/fixtures/dead_parrots.yml5
-rw-r--r--activerecord/test/fixtures/live_parrots.yml4
-rw-r--r--activerecord/test/migrations/missing/1000_people_have_middle_names.rb2
-rw-r--r--activerecord/test/migrations/missing/1_people_have_last_names.rb2
-rw-r--r--activerecord/test/migrations/missing/3_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/missing/4_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/rename/1_we_need_things.rb2
-rw-r--r--activerecord/test/migrations/rename/2_rename_things.rb2
-rw-r--r--activerecord/test/migrations/valid/2_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/valid/3_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb2
-rw-r--r--activerecord/test/models/admin.rb2
-rw-r--r--activerecord/test/models/admin/account.rb2
-rw-r--r--activerecord/test/models/admin/randomly_named_c1.rb8
-rw-r--r--activerecord/test/models/binary.rb2
-rw-r--r--activerecord/test/models/car.rb2
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/event.rb2
-rw-r--r--activerecord/test/models/guid.rb2
-rw-r--r--activerecord/test/models/notification.rb2
-rw-r--r--activerecord/test/models/pirate.rb2
-rw-r--r--activerecord/test/models/post.rb3
-rw-r--r--activerecord/test/models/randomly_named_c1.rb2
-rw-r--r--activerecord/test/models/ship.rb1
-rw-r--r--activerecord/test/models/ship_part.rb3
-rw-r--r--activerecord/test/models/treasure.rb1
-rw-r--r--activerecord/test/models/user.rb4
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb2
-rw-r--r--activerecord/test/schema/schema.rb18
-rw-r--r--activesupport/CHANGELOG.md46
-rw-r--r--activesupport/lib/active_support/callbacks.rb31
-rw-r--r--activesupport/lib/active_support/concern.rb2
-rw-r--r--activesupport/lib/active_support/configurable.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb36
-rw-r--r--activesupport/lib/active_support/core_ext/name_error.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb4
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb6
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb53
-rw-r--r--activesupport/lib/active_support/subscriber.rb2
-rw-r--r--activesupport/lib/active_support/test_case.rb2
-rw-r--r--activesupport/lib/active_support/testing/file_fixtures.rb34
-rw-r--r--activesupport/lib/active_support/testing/stream.rb42
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb1
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb15
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb4
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb4
-rw-r--r--activesupport/test/concern_test.rb10
-rw-r--r--activesupport/test/configurable_test.rb8
-rw-r--r--activesupport/test/core_ext/file_test.rb10
-rw-r--r--activesupport/test/core_ext/module_test.rb2
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb1
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb1
-rw-r--r--activesupport/test/core_ext/uri_ext_test.rb1
-rw-r--r--activesupport/test/deprecation_test.rb19
-rw-r--r--activesupport/test/file_fixtures/sample.txt1
-rw-r--r--activesupport/test/inflector_test_cases.rb1
-rw-r--r--activesupport/test/json/decoding_test.rb1
-rw-r--r--activesupport/test/json/encoding_test.rb1
-rw-r--r--activesupport/test/multibyte_chars_test.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/multibyte_unicode_database_test.rb1
-rw-r--r--activesupport/test/number_helper_test.rb4
-rw-r--r--activesupport/test/safe_buffer_test.rb7
-rw-r--r--activesupport/test/testing/file_fixtures_test.rb28
-rw-r--r--activesupport/test/time_travel_test.rb11
-rw-r--r--activesupport/test/time_zone_test.rb3
-rw-r--r--activesupport/test/transliterate_test.rb1
-rw-r--r--activesupport/test/xml_mini_test.rb2
-rw-r--r--guides/CHANGELOG.md8
-rw-r--r--guides/assets/images/favicon.icobin1150 -> 5430 bytes
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md33
-rw-r--r--guides/source/action_view_overview.md95
-rw-r--r--guides/source/active_job_basics.md10
-rw-r--r--guides/source/active_record_migrations.md15
-rw-r--r--guides/source/active_record_querying.md25
-rw-r--r--guides/source/active_record_validations.md68
-rw-r--r--guides/source/association_basics.md81
-rw-r--r--guides/source/autoloading_and_reloading_constants.md24
-rw-r--r--guides/source/command_line.md8
-rw-r--r--guides/source/configuring.md18
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md12
-rw-r--r--guides/source/debugging_rails_applications.md56
-rw-r--r--guides/source/documents.yaml2
-rw-r--r--guides/source/form_helpers.md9
-rw-r--r--guides/source/getting_started.md17
-rw-r--r--guides/source/i18n.md27
-rw-r--r--guides/source/layouts_and_rendering.md46
-rw-r--r--guides/source/testing.md72
-rw-r--r--guides/source/upgrading_ruby_on_rails.md18
-rw-r--r--guides/source/working_with_javascript_in_rails.md30
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md30
-rw-r--r--railties/lib/rails/api/task.rb14
-rw-r--r--railties/lib/rails/application.rb4
-rw-r--r--railties/lib/rails/application/configuration.rb17
-rw-r--r--railties/lib/rails/application/finisher.rb7
-rw-r--r--railties/lib/rails/commands/console.rb2
-rw-r--r--railties/lib/rails/commands/server.rb4
-rw-r--r--railties/lib/rails/configuration.rb6
-rw-r--r--railties/lib/rails/engine.rb16
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/actions.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb14
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb6
-rw-r--r--railties/lib/rails/generators/migration.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README.md (renamed from railties/lib/rails/generators/rails/app/templates/README.rdoc)6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb61
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec10
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/README.rdoc2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt)5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt14
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt14
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/config/routes.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/gitignore4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb1
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake)2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css11
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb5
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb10
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb18
-rw-r--r--railties/lib/rails/tasks.rb1
-rw-r--r--railties/lib/rails/tasks/documentation.rake70
-rw-r--r--railties/lib/rails/tasks/tmp.rake2
-rw-r--r--railties/lib/rails/test_help.rb1
-rw-r--r--railties/test/abstract_unit.rb21
-rw-r--r--railties/test/application/assets_test.rb1
-rw-r--r--railties/test/application/middleware/exceptions_test.rb1
-rw-r--r--railties/test/application/middleware/session_test.rb1
-rw-r--r--railties/test/application/middleware/static_test.rb1
-rw-r--r--railties/test/application/middleware_test.rb16
-rw-r--r--railties/test/application/rake_test.rb2
-rw-r--r--railties/test/application/test_runner_test.rb14
-rw-r--r--railties/test/generators/actions_test.rb4
-rw-r--r--railties/test/generators/app_generator_test.rb16
-rw-r--r--railties/test/generators/generators_test_helper.rb20
-rw-r--r--railties/test/generators/mailer_generator_test.rb31
-rw-r--r--railties/test/generators/named_base_test.rb14
-rw-r--r--railties/test/generators/namespaced_generators_test.rb10
-rw-r--r--railties/test/generators/plugin_generator_test.rb122
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb8
-rw-r--r--railties/test/generators/scaffold_generator_test.rb31
-rw-r--r--railties/test/generators/shared_generator_tests.rb4
-rw-r--r--railties/test/isolation/abstract_unit.rb44
-rw-r--r--railties/test/path_generation_test.rb1
-rw-r--r--railties/test/rails_info_controller_test.rb2
614 files changed, 9116 insertions, 4507 deletions
diff --git a/.gitignore b/.gitignore
index bc96284375..c3cb009140 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,6 @@ debug.log
.Gemfile
/.bundle
/.ruby-version
-/Gemfile.lock
pkg
/dist
/doc/rdoc
diff --git a/.travis.yml b/.travis.yml
index 8bbc7c8f7e..3130ee1b32 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,8 +2,8 @@ language: ruby
sudo: false
script: 'ci/travis.rb'
before_install:
- - "rvm current | grep 'jruby' && export AR_JDBC=true || echo"
- gem install bundler
+ - "rm ${BUNDLE_GEMFILE}.lock"
before_script:
- bundle update
cache: bundler
@@ -24,12 +24,13 @@ rvm:
- 2.2
- ruby-head
- rbx-2
- - jruby
+ - jruby-head
matrix:
allow_failures:
- env: "GEM=ar:mysql"
+ - rvm: ruby-head
- rvm: rbx-2
- - rvm: jruby
+ - rvm: jruby-head
- env: "GEM=aj"
- env: "GEM=aj:integration"
fast_finish: true
diff --git a/Gemfile b/Gemfile
index 0bcfab7b79..074db1b4eb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -29,7 +29,7 @@ group :doc do
gem 'sdoc', '~> 0.4.0'
gem 'redcarpet', '~> 3.2.2', platforms: :ruby
gem 'w3c_validators'
- gem 'kindlerb'
+ gem 'kindlerb', '0.1.1'
end
# AS
@@ -42,7 +42,7 @@ group :job do
gem 'sidekiq', require: false
gem 'sucker_punch', require: false
gem 'delayed_job', require: false
- gem 'queue_classic', "< 3.0.0", require: false, platforms: :ruby
+ gem 'queue_classic', require: false, platforms: :ruby
gem 'sneakers', '0.1.1.pre', require: false
gem 'que', require: false
gem 'backburner', require: false
@@ -85,9 +85,9 @@ platforms :ruby do
gem 'sqlite3', '~> 1.3.6'
group :db do
- gem 'pg', '>= 0.15.0'
+ gem 'pg', '>= 0.18.0'
gem 'mysql', '>= 2.9.0'
- gem 'mysql2', '>= 0.3.13'
+ gem 'mysql2', '>= 0.3.18'
end
end
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000..d2ed998160
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,281 @@
+GIT
+ remote: git://github.com/bkeepers/qu.git
+ revision: d098e2657c92e89a6413bebd9c033930759c061f
+ branch: master
+ specs:
+ qu (0.2.0)
+ qu-rails (0.2.0)
+ qu (= 0.2.0)
+ railties (>= 3.2, < 5)
+ qu-redis (0.2.0)
+ qu (= 0.2.0)
+ redis-namespace
+
+GIT
+ remote: git://github.com/rails/arel.git
+ revision: aac9da257f291ad8d2d4f914528881c240848bb2
+ branch: master
+ specs:
+ arel (7.0.0.alpha)
+
+GIT
+ remote: git://github.com/rails/jquery-rails.git
+ revision: 272abdd319bb3381b23182b928b25320590096b0
+ branch: master
+ specs:
+ jquery-rails (4.0.3)
+ rails-dom-testing (~> 1.0)
+ railties (>= 4.2.0)
+ thor (>= 0.14, < 2.0)
+
+PATH
+ remote: .
+ specs:
+ actionmailer (5.0.0.alpha)
+ actionpack (= 5.0.0.alpha)
+ actionview (= 5.0.0.alpha)
+ activejob (= 5.0.0.alpha)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ actionpack (5.0.0.alpha)
+ actionview (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ rack (~> 1.6)
+ rack-test (~> 0.6.2)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.1)
+ actionview (5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ builder (~> 3.1)
+ erubis (~> 2.7.0)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.1)
+ activejob (5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ globalid (>= 0.3.0)
+ activemodel (5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ builder (~> 3.1)
+ activerecord (5.0.0.alpha)
+ activemodel (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ arel (= 7.0.0.alpha)
+ activesupport (5.0.0.alpha)
+ i18n (~> 0.7)
+ json (~> 1.7, >= 1.7.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.3, >= 0.3.4)
+ tzinfo (~> 1.1)
+ rails (5.0.0.alpha)
+ actionmailer (= 5.0.0.alpha)
+ actionpack (= 5.0.0.alpha)
+ actionview (= 5.0.0.alpha)
+ activejob (= 5.0.0.alpha)
+ activemodel (= 5.0.0.alpha)
+ activerecord (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ bundler (>= 1.3.0, < 2.0)
+ railties (= 5.0.0.alpha)
+ sprockets-rails
+ railties (5.0.0.alpha)
+ actionpack (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ rake (>= 0.8.7)
+ thor (>= 0.18.1, < 2.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ amq-protocol (1.9.2)
+ backburner (0.4.6)
+ beaneater (~> 0.3.1)
+ dante (~> 0.1.5)
+ bcrypt (3.1.10)
+ beaneater (0.3.3)
+ benchmark-ips (2.1.1)
+ builder (3.2.2)
+ bunny (1.1.9)
+ amq-protocol (>= 1.9.2)
+ celluloid (0.16.0)
+ timers (~> 4.0.0)
+ coffee-rails (4.1.0)
+ coffee-script (>= 2.2.0)
+ railties (>= 4.0.0, < 5.0)
+ coffee-script (2.3.0)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.9.0)
+ connection_pool (2.1.1)
+ dalli (2.7.2)
+ dante (0.1.5)
+ delayed_job (4.0.6)
+ activesupport (>= 3.0, < 5.0)
+ erubis (2.7.0)
+ execjs (2.3.0)
+ globalid (0.3.3)
+ activesupport (>= 4.1.0)
+ hike (1.2.3)
+ hitimes (1.2.2)
+ i18n (0.7.0)
+ json (1.8.2)
+ kindlerb (0.1.1)
+ mustache
+ nokogiri
+ loofah (2.0.1)
+ nokogiri (>= 1.5.9)
+ mail (2.6.3)
+ mime-types (>= 1.16, < 3)
+ metaclass (0.0.4)
+ mime-types (2.4.3)
+ mini_portile (0.6.2)
+ minitest (5.3.3)
+ mocha (0.14.0)
+ metaclass (~> 0.0.1)
+ mono_logger (1.1.0)
+ multi_json (1.10.1)
+ mustache (1.0.0)
+ mysql (2.9.1)
+ mysql2 (0.3.18)
+ nokogiri (1.6.6.2)
+ mini_portile (~> 0.6.0)
+ pg (0.18.1)
+ psych (2.0.13)
+ que (0.9.2)
+ queue_classic (3.1.0)
+ pg (>= 0.17, < 0.19)
+ racc (1.4.12)
+ rack (1.6.0)
+ rack-cache (1.2)
+ rack (>= 0.4)
+ rack-protection (1.5.3)
+ rack
+ rack-test (0.6.3)
+ rack (>= 1.0)
+ rails-deprecated_sanitizer (1.0.3)
+ activesupport (>= 4.2.0.alpha)
+ rails-dom-testing (1.0.5)
+ activesupport (>= 4.2.0.beta, < 5.0)
+ nokogiri (~> 1.6.0)
+ rails-deprecated_sanitizer (>= 1.0.1)
+ rails-html-sanitizer (1.0.1)
+ loofah (~> 2.0)
+ rake (10.4.2)
+ rdoc (4.2.0)
+ redcarpet (3.2.2)
+ redis (3.2.1)
+ redis-namespace (1.5.1)
+ redis (~> 3.0, >= 3.0.4)
+ resque (1.25.2)
+ mono_logger (~> 1.0)
+ multi_json (~> 1.0)
+ redis-namespace (~> 1.3)
+ sinatra (>= 0.9.2)
+ vegas (~> 0.1.2)
+ resque-scheduler (4.0.0)
+ mono_logger (~> 1.0)
+ redis (~> 3.0)
+ resque (~> 1.25)
+ rufus-scheduler (~> 3.0)
+ ruby-prof (0.11.3)
+ rufus-scheduler (3.0.9)
+ tzinfo
+ sdoc (0.4.1)
+ json (~> 1.7, >= 1.7.7)
+ rdoc (~> 4.0)
+ sequel (4.19.0)
+ serverengine (1.5.10)
+ sigdump (~> 0.2.2)
+ sidekiq (3.3.2)
+ celluloid (>= 0.16.0)
+ connection_pool (>= 2.1.1)
+ json
+ redis (>= 3.0.6)
+ redis-namespace (>= 1.3.1)
+ sigdump (0.2.2)
+ sinatra (1.4.5)
+ rack (~> 1.4)
+ rack-protection (~> 1.4)
+ tilt (~> 1.3, >= 1.3.4)
+ sneakers (0.1.1.pre)
+ bunny (~> 1.1.3)
+ serverengine
+ thor
+ thread
+ sprockets (2.12.3)
+ hike (~> 1.2)
+ multi_json (~> 1.0)
+ rack (~> 1.0)
+ tilt (~> 1.1, != 1.3.0)
+ sprockets-rails (2.2.4)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ sprockets (>= 2.8, < 4.0)
+ sqlite3 (1.3.10)
+ stackprof (0.2.7)
+ sucker_punch (1.3.2)
+ celluloid (~> 0.16.0)
+ thor (0.19.1)
+ thread (0.1.5)
+ thread_safe (0.3.4)
+ tilt (1.4.1)
+ timers (4.0.1)
+ hitimes
+ turbolinks (2.5.3)
+ coffee-rails
+ tzinfo (1.2.2)
+ thread_safe (~> 0.1)
+ uglifier (2.7.0)
+ execjs (>= 0.3.0)
+ json (>= 1.8.0)
+ vegas (0.1.11)
+ rack (>= 1.0.0)
+ w3c_validators (1.2)
+ json
+ nokogiri
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ activerecord-jdbcmysql-adapter (>= 1.3.0)
+ activerecord-jdbcpostgresql-adapter (>= 1.3.0)
+ activerecord-jdbcsqlite3-adapter (>= 1.3.0)
+ arel!
+ backburner
+ bcrypt (~> 3.1.7)
+ benchmark-ips
+ coffee-rails (~> 4.1.0)
+ dalli (>= 2.2.1)
+ delayed_job
+ jquery-rails!
+ json
+ kindlerb (= 0.1.1)
+ minitest (< 5.3.4)
+ mocha (~> 0.14)
+ mysql (>= 2.9.0)
+ mysql2 (>= 0.3.18)
+ nokogiri (>= 1.4.5)
+ pg (>= 0.18.0)
+ psych (~> 2.0)
+ qu-rails!
+ qu-redis
+ que
+ queue_classic
+ racc (>= 1.4.6)
+ rack-cache (~> 1.2)
+ rails!
+ rake (>= 10.3)
+ redcarpet (~> 3.2.2)
+ resque
+ resque-scheduler
+ ruby-prof (~> 0.11.2)
+ sdoc (~> 0.4.0)
+ sequel
+ sidekiq
+ sneakers (= 0.1.1.pre)
+ sqlite3 (~> 1.3.6)
+ stackprof
+ sucker_punch
+ turbolinks
+ uglifier (>= 1.3.0)
+ w3c_validators
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 008760f2fc..86ecb3ee88 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,5 +1,7 @@
* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`.
+ Example:
+
def test_emails
assert_enqueued_emails 2 do
ContactMailer.welcome.deliver_later
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index cc7935a7e0..227192f48d 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -33,6 +33,8 @@ module ActionMailer
end
# Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
+ # By default column length +len+ equals 72 characters and indent
+ # +indent+ equal two spaces.
#
# my_text = 'Here is a sample text with more than 40 characters'
#
diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE
index d9d9d064d8..2b0a078109 100644
--- a/actionmailer/lib/rails/generators/mailer/USAGE
+++ b/actionmailer/lib/rails/generators/mailer/USAGE
@@ -12,6 +12,6 @@ Example:
creates a Notifications mailer class, views, and test:
Mailer: app/mailers/notifications_mailer.rb
- Views: app/views/notifications/signup.text.erb [...]
- Test: test/mailers/notifications_test.rb
+ Views: app/views/notifications_mailer/signup.text.erb [...]
+ Test: test/mailers/notifications_mailer_test.rb
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 7681679dc7..c4d5cb8c33 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -15,7 +15,7 @@ require 'mail'
# Emulate AV railtie
require 'action_view'
-ActionMailer::Base.send(:include, ActionView::Layouts)
+ActionMailer::Base.include(ActionView::Layouts)
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index b218d923e5..59c5638f96 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'set'
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index 55ee00602a..e4dd269494 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_job'
require 'minitest/mock'
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 21e7154c43..089933e245 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -1,5 +1,5 @@
-# encoding: utf-8
require 'abstract_unit'
+require 'active_support/testing/stream'
class TestHelperMailer < ActionMailer::Base
def test
@@ -11,6 +11,8 @@ class TestHelperMailer < ActionMailer::Base
end
class TestHelperMailerTest < ActionMailer::TestCase
+ include ActiveSupport::Testing::Stream
+
def test_setup_sets_right_action_mailer_options
assert_equal :test, ActionMailer::Base.delivery_method
assert ActionMailer::Base.perform_deliveries
@@ -123,7 +125,9 @@ class TestHelperMailerTest < ActionMailer::TestCase
def test_assert_enqueued_emails
assert_nothing_raised do
assert_enqueued_emails 1 do
- TestHelperMailer.test.deliver_later
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
end
end
end
@@ -131,7 +135,9 @@ class TestHelperMailerTest < ActionMailer::TestCase
def test_assert_enqueued_emails_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_emails 2 do
- TestHelperMailer.test.deliver_later
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
end
end
@@ -141,8 +147,10 @@ class TestHelperMailerTest < ActionMailer::TestCase
def test_assert_enqueued_emails_too_many_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_emails 1 do
- TestHelperMailer.test.deliver_later
- TestHelperMailer.test.deliver_later
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ TestHelperMailer.test.deliver_later
+ end
end
end
@@ -160,7 +168,9 @@ class TestHelperMailerTest < ActionMailer::TestCase
def test_assert_no_enqueued_emails_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_enqueued_emails do
- TestHelperMailer.test.deliver_later
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
end
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 6cd0b2d15d..055d4b4f5b 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,105 @@
+* Non-string authenticity tokens do not raise NoMethodError when decoding
+ the masked token.
+
+ *Ville Lautanala*
+
+* Add http_cache_forever to Action Controller, so we can cache a response that never gets expired.
+
+ *arthurnn*
+
+* ActionController#translate supports symbols as shortcuts.
+ When shortcut is given it also lookups without action name.
+
+ *Max Melentiev*
+
+* Expand `ActionController::ConditionalGet#fresh_when` and `stale?` to also
+ accept a collection of records as the first argument, so that the
+ following code can be written in a shorter form.
+
+ # Before
+ def index
+ @articles = Article.all
+ fresh_when(etag: @articles, last_modified: @articles.maximum(:updated_at))
+ end
+
+ # After
+ def index
+ @articles = Article.all
+ fresh_when(@articles)
+ end
+
+ *claudiob*
+
+* Explicitly ignored wildcard verbs when searching for HEAD routes before fallback
+
+ Fixes an issue where a mounted rack app at root would intercept the HEAD
+ request causing an incorrect behavior during the fall back to GET requests.
+
+ Example:
+ ```ruby
+ draw do
+ get '/home' => 'test#index'
+ mount rack_app, at: '/'
+ end
+ head '/home'
+ assert_response :success
+ ```
+ In this case, a HEAD request runs through the routes the first time and fails
+ to match anything. Then, it runs through the list with the fallback and matches
+ `get '/home'`. The original behavior would match the rack app in the first pass.
+
+ *Terence Sun*
+
+* Migrating xhr methods to keyword arguments syntax
+ in `ActionController::TestCase` and `ActionDispatch::Integration`
+
+ Old syntax:
+
+ xhr :get, :create, params: { id: 1 }
+
+ New syntax example:
+
+ get :create, params: { id: 1 }, xhr: true
+
+ *Kir Shatrov*
+
+* Migrating to keyword arguments syntax in `ActionController::TestCase` and
+ `ActionDispatch::Integration` HTTP request methods.
+
+ Example:
+
+ post :create, params: { y: x }, session: { a: 'b' }
+ get :view, params: { id: 1 }
+ get :view, params: { id: 1 }, format: :json
+
+ *Kir Shatrov*
+
+* Preserve default url options when generating URLs.
+
+ Fixes an issue that would cause default_url_options to be lost when
+ generating URLs with fewer positional arguments than parameters in the
+ route definition.
+
+ *Tekin Suleyman*
+
+* Deprecate *_via_redirect integration test methods.
+
+ Use `follow_redirect!` manually after the request call for the same behavior.
+
+ *Aditya Kapoor*
+
+* Add `ActionController::Renderer` to render arbitrary templates
+ outside controller actions.
+
+ Its functionality is accessible through class methods `render` and
+ `renderer` of `ActionController::Base`.
+
+ *Ravil Bayramgalin*
+
+* Support `:assigns` option when rendering with controllers/mailers.
+
+ *Ravil Bayramgalin*
+
* Default headers, removed in controller actions, are no longer reapplied on
the test response.
@@ -10,6 +112,7 @@
* Allow you to pass `prepend: false` to protect_from_forgery to have the
verification callback appended instead of prepended to the chain.
This allows you to let the verification step depend on prior callbacks.
+
Example:
class ApplicationController < ActionController::Base
@@ -106,7 +209,7 @@
*Travis Grathwell*
-* Stop converting empty arrays in `params` to `nil`
+* Stop converting empty arrays in `params` to `nil`.
This behaviour was introduced in response to CVE-2012-2660, CVE-2012-2694
and CVE-2013-0155
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index db999c2765..69f490b327 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -69,7 +69,7 @@ module AbstractController
end
def skip_filter(*names)
- ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will removed in Rails 5.1. Use #{callback}_action instead.")
+ ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.")
skip_action_callback(*names)
end
@@ -188,7 +188,7 @@ module AbstractController
end
define_method "#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will removed in Rails 5.1. Use #{callback}_action instead.")
+ ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.")
send("#{callback}_action", *names, &blk)
end
@@ -199,7 +199,7 @@ module AbstractController
end
define_method "prepend_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will removed in Rails 5.1. Use prepend_#{callback}_action instead.")
+ ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use prepend_#{callback}_action instead.")
send("prepend_#{callback}_action", *names, &blk)
end
@@ -212,7 +212,7 @@ module AbstractController
end
define_method "skip_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will removed in Rails 5.1. Use skip_#{callback}_action instead.")
+ ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use skip_#{callback}_action instead.")
send("skip_#{callback}_action", *names, &blk)
end
@@ -220,7 +220,7 @@ module AbstractController
alias_method :"append_#{callback}_action", :"#{callback}_action"
define_method "append_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will removed in Rails 5.1. Use append_#{callback}_action instead.")
+ ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.")
send("append_#{callback}_action", *names, &blk)
end
end
diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
index 568c47e43a..14b574e322 100644
--- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb
+++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
@@ -6,9 +6,9 @@ module AbstractController
define_method(:inherited) do |klass|
super(klass)
if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
- klass.send(:include, namespace.railtie_routes_url_helpers(include_path_helpers))
+ klass.include(namespace.railtie_routes_url_helpers(include_path_helpers))
else
- klass.send(:include, routes.url_helpers(include_path_helpers))
+ klass.include(routes.url_helpers(include_path_helpers))
end
end
end
diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index 02028d8e05..56b8ce895e 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -8,14 +8,15 @@ module AbstractController
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
# to translate many keys within the same controller / action and gives you a
# simple framework for scoping them consistently.
- def translate(*args)
- key = args.first
- if key.is_a?(String) && (key[0] == '.')
- key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }"
- args[0] = key
+ def translate(key, options = {})
+ if key.to_s.first == '.'
+ path = controller_path.tr('/', '.')
+ defaults = [:"#{path}#{key}"]
+ defaults << options[:default] if options[:default]
+ options[:default] = defaults
+ key = "#{path}.#{action_name}#{key}"
end
-
- I18n.translate(*args)
+ I18n.translate(key, options)
end
alias :t :translate
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index e977bbce99..7667e469d3 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -11,6 +11,7 @@ module ActionController
autoload :Caching
autoload :Metal
autoload :Middleware
+ autoload :Renderer
autoload_under "metal" do
autoload :Compatibility
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 993f8e150d..ae111e4951 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -190,11 +190,15 @@ module ActionController
end
def dispatch(name, request) #:nodoc:
+ set_request!(request)
+ process(name)
+ to_a
+ end
+
+ def set_request!(request) #:nodoc:
@_request = request
@_env = request.env
@_env['action_controller.instance'] = self
- process(name)
- to_a
end
def to_a #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index febbc72861..858870d8b8 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -57,15 +57,25 @@ module ActionController
# This will render the show template if the request isn't sending a matching ETag or
# If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
#
- # You can also just pass a record where +last_modified+ will be set by calling
- # +updated_at+ and the +etag+ by passing the object itself.
+ # You can also just pass a record. In this case +last_modified+ will be set
+ # by calling +updated_at+ and +etag+ by passing the object itself.
#
# def show
# @article = Article.find(params[:id])
# fresh_when(@article)
# end
#
- # When passing a record, you can still set whether the public header:
+ # You can also pass an object that responds to +maximum+, such as a
+ # collection of active records. In this case +last_modified+ will be set by
+ # calling +maximum(:updated_at)+ on the collection (the timestamp of the
+ # most recently updated record) and the +etag+ by passing the object itself.
+ #
+ # def index
+ # @articles = Article.all
+ # fresh_when(@articles)
+ # end
+ #
+ # When passing a record or a collection, you can still set the public header:
#
# def show
# @article = Article.find(params[:id])
@@ -77,18 +87,16 @@ module ActionController
#
# before_action { fresh_when @article, template: 'widgets/show' }
#
- def fresh_when(record_or_options, additional_options = {})
- if record_or_options.is_a? Hash
- options = record_or_options
- options.assert_valid_keys(:etag, :last_modified, :public, :template)
- else
- record = record_or_options
- options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
+ def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil)
+ last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
+
+ if etag || template
+ response.etag = combine_etags(etag: etag, last_modified: last_modified,
+ public: public, template: template)
end
- response.etag = combine_etags(options) if options[:etag] || options[:template]
- response.last_modified = options[:last_modified] if options[:last_modified]
- response.cache_control[:public] = true if options[:public]
+ response.last_modified = last_modified if last_modified
+ response.cache_control[:public] = true if public
head :not_modified if request.fresh?(response)
end
@@ -123,8 +131,8 @@ module ActionController
# end
# end
#
- # You can also just pass a record where +last_modified+ will be set by calling
- # +updated_at+ and the +etag+ by passing the object itself.
+ # You can also just pass a record. In this case +last_modified+ will be set
+ # by calling +updated_at+ and +etag+ by passing the object itself.
#
# def show
# @article = Article.find(params[:id])
@@ -137,7 +145,23 @@ module ActionController
# end
# end
#
- # When passing a record, you can still set whether the public header:
+ # You can also pass an object that responds to +maximum+, such as a
+ # collection of active records. In this case +last_modified+ will be set by
+ # calling +maximum(:updated_at)+ on the collection (the timestamp of the
+ # most recently updated record) and the +etag+ by passing the object itself.
+ #
+ # def index
+ # @articles = Article.all
+ #
+ # if stale?(@articles)
+ # @statistics = @articles.really_expensive_call
+ # respond_to do |format|
+ # # all the supported formats
+ # end
+ # end
+ # end
+ #
+ # When passing a record or a collection, you can still set the public header:
#
# def show
# @article = Article.find(params[:id])
@@ -157,8 +181,8 @@ module ActionController
# super if stale? @article, template: 'widgets/show'
# end
#
- def stale?(record_or_options, additional_options = {})
- fresh_when(record_or_options, additional_options)
+ def stale?(object = nil, etag: object, last_modified: nil, public: nil, template: nil)
+ fresh_when(object, etag: etag, last_modified: last_modified, public: public, template: template)
!request.fresh?(response)
end
@@ -191,6 +215,24 @@ module ActionController
response.cache_control.replace(:no_cache => true)
end
+ # Cache or yield the block. The cache is supposed to never expire.
+ #
+ # You can use this method when you have a HTTP response that never changes,
+ # and the browser and proxies should cache it indefinitely.
+ #
+ # * +public+: By default, HTTP responses are private, cached only on the
+ # user's web browser. To allow proxies to cache the response, set +true+ to
+ # indicate that they can serve the cached response to all users.
+ #
+ # * +version+: the version passed as a key for the cache.
+ def http_cache_forever(public: false, version: 'v1')
+ expires_in 100.years, public: public
+
+ yield if stale?(etag: "#{version}-#{request.fullpath}",
+ last_modified: Time.parse('2011-01-01').utc,
+ public: public)
+ end
+
private
def combine_etags(options)
etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index a219d35b25..20afcee537 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -106,11 +106,11 @@ module ActionController
end
def auth_scheme(request)
- request.authorization.split(' ', 2).first
+ request.authorization.to_s.split(' ', 2).first
end
def auth_param(request)
- request.authorization.split(' ', 2).second
+ request.authorization.to_s.split(' ', 2).second
end
def encode_credentials(user_name, password)
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index 545d4a7e6e..ae9d89cc8c 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -8,9 +8,15 @@ module ActionController
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, :response_code, :to => "@_response"
- def dispatch(action, request)
+ module ClassMethods
+ def build_with_env(env = {}) #:nodoc:
+ new.tap { |c| c.set_request! ActionDispatch::Request.new(env) }
+ end
+ end
+
+ def set_request!(request) #:nodoc:
+ super
set_response!(request)
- super(action, request)
end
def response_body=(body)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 7bbff0450a..2d15c39d88 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -4,6 +4,17 @@ module ActionController
RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+ module ClassMethods
+ # Documentation at ActionController::Renderer#render
+ delegate :render, to: :renderer
+
+ # Returns a renderer class (inherited from ActionController::Renderer)
+ # for the controller.
+ def renderer
+ @renderer ||= Renderer.for(self)
+ end
+ end
+
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 7facbe79aa..367b736035 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -275,7 +275,9 @@ module ActionController #:nodoc:
# session token. Essentially the inverse of
# +masked_authenticity_token+.
def valid_authenticity_token?(session, encoded_masked_token)
- return false if encoded_masked_token.nil? || encoded_masked_token.empty?
+ if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
+ return false
+ end
begin
masked_token = Base64.strict_decode64(encoded_masked_token)
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
new file mode 100644
index 0000000000..e8b29c5b5e
--- /dev/null
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -0,0 +1,100 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActionController
+ # ActionController::Renderer allows to render arbitrary templates
+ # without requirement of being in controller actions.
+ #
+ # You get a concrete renderer class by invoking ActionController::Base#renderer.
+ # For example,
+ #
+ # ApplicationController.renderer
+ #
+ # It allows you to call method #render directly.
+ #
+ # ApplicationController.renderer.render template: '...'
+ #
+ # You can use a shortcut on controller to replace previous example with:
+ #
+ # ApplicationController.render template: '...'
+ #
+ # #render method allows you to use any options as when rendering in controller.
+ # For example,
+ #
+ # FooController.render :action, locals: { ... }, assigns: { ... }
+ #
+ # The template will be rendered in a Rack environment which is accessible through
+ # ActionController::Renderer#env. You can set it up in two ways:
+ #
+ # * by changing renderer defaults, like
+ #
+ # ApplicationController.renderer.defaults # => hash with default Rack environment
+ #
+ # * by initializing an instance of renderer by passing it a custom environment.
+ #
+ # ApplicationController.renderer.new(method: 'post', https: true)
+ #
+ class Renderer
+ class_attribute :controller, :defaults
+ # Rack environment to render templates in.
+ attr_reader :env
+
+ class << self
+ delegate :render, to: :new
+
+ # Create a new renderer class for a specific controller class.
+ def for(controller)
+ Class.new self do
+ self.controller = controller
+ self.defaults = {
+ http_host: 'example.org',
+ https: false,
+ method: 'get',
+ script_name: '',
+ 'rack.input' => ''
+ }
+ end
+ end
+ end
+
+ # Accepts a custom Rack environment to render templates in.
+ # It will be merged with ActionController::Renderer.defaults
+ def initialize(env = {})
+ @env = normalize_keys(defaults).merge normalize_keys(env)
+ @env['action_dispatch.routes'] = controller._routes
+ end
+
+ # Render templates with any options from ActionController::Base#render_to_string.
+ def render(*args)
+ raise 'missing controller' unless controller?
+
+ instance = controller.build_with_env(env)
+ instance.render_to_string(*args)
+ end
+
+ private
+ def normalize_keys(env)
+ http_header_format(env).tap do |new_env|
+ handle_method_key! new_env
+ handle_https_key! new_env
+ end
+ end
+
+ def http_header_format(env)
+ env.transform_keys do |key|
+ key.is_a?(Symbol) ? key.to_s.upcase : key
+ end
+ end
+
+ def handle_method_key!(env)
+ if method = env.delete('METHOD')
+ env['REQUEST_METHOD'] = method.upcase
+ end
+ end
+
+ def handle_https_key!(env)
+ if env.has_key? 'HTTPS'
+ env['HTTPS'] = env['HTTPS'] ? 'on' : 'off'
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index d30615fade..4782991463 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -494,55 +494,66 @@ module ActionController
# Simulate a GET request with the given parameters.
#
# - +action+: The controller action to call.
- # - +parameters+: The HTTP parameters that you want to pass. This may
- # be +nil+, a hash, or a string that is appropriately encoded
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
+ # - +body+: The request body with a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
# +post+, +patch+, +put+, +delete+, and +head+.
+ # Example sending parameters, session and setting a flash message:
+ #
+ # get :show,
+ # params: { id: 7 },
+ # session: { user_id: 1 },
+ # flash: { notice: 'This is flash message' }
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
- process(action, "GET", *args)
+ process_with_kwargs("GET", action, *args)
end
# Simulate a POST request with the given parameters and set/volley the response.
# See +get+ for more details.
def post(action, *args)
- process(action, "POST", *args)
+ process_with_kwargs("POST", action, *args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
# See +get+ for more details.
def patch(action, *args)
- process(action, "PATCH", *args)
+ process_with_kwargs("PATCH", action, *args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
# See +get+ for more details.
def put(action, *args)
- process(action, "PUT", *args)
+ process_with_kwargs("PUT", action, *args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
# See +get+ for more details.
def delete(action, *args)
- process(action, "DELETE", *args)
+ process_with_kwargs("DELETE", action, *args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
# See +get+ for more details.
def head(action, *args)
- process(action, "HEAD", *args)
+ process_with_kwargs("HEAD", action, *args)
end
- def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
+ def xml_http_request(*args)
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ xhr and xml_http_request methods are deprecated in favor of
+ `get :index, xhr: true` and `post :create, xhr: true`
+ MSG
+
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- __send__(request_method, action, parameters, session, flash).tap do
+ @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ __send__(*args).tap do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
@@ -566,41 +577,69 @@ module ActionController
# parameters and set/volley the response.
#
# - +action+: The controller action to call.
- # - +http_method+: Request method used to send the http request. Possible values
- # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
- # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
- # string that is appropriately encoded (+application/x-www-form-urlencoded+
- # or +multipart/form-data+).
+ # - +method+: Request method used to send the HTTP request. Possible values
+ # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
+ # - +body+: The request body with a string that is appropriately encoded
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
+ # - +format+: Request format. Defaults to +nil+. Can be string or symbol.
#
# Example calling +create+ action and sending two params:
#
- # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' }
- #
- # Example sending parameters, +nil+ session and setting a flash message:
- #
- # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
+ # process :create,
+ # method: 'POST',
+ # params: {
+ # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
+ # },
+ # session: { user_id: 1 },
+ # flash: { notice: 'This is flash message' }
#
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
# prefer using #get, #post, #patch, #put, #delete and #head methods
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
- def process(action, http_method = 'GET', *args)
+ def process(action, *args)
check_required_ivars
- if args.first.is_a?(String) && http_method != 'HEAD'
- @request.env['RAW_POST_DATA'] = args.shift
+ if kwarg_request?(*args)
+ parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
+ else
+ http_method, parameters, session, flash = args
+ format = nil
+
+ if parameters.is_a?(String) && http_method != 'HEAD'
+ body = parameters
+ parameters = nil
+ end
+
+ if parameters.present? || session.present? || flash.present?
+ non_kwarg_request_warning
+ end
+ end
+
+ if body.present?
+ @request.env['RAW_POST_DATA'] = body
+ end
+
+ if http_method.present?
+ http_method = http_method.to_s.upcase
+ else
+ http_method = "GET"
end
- parameters, session, flash = args
parameters ||= {}
# Ensure that numbers and symbols passed as params are converted to
# proper params, as is the case when engaging rack.
parameters = paramify_values(parameters) if html_format?(parameters)
+ if format.present?
+ parameters[:format] = format
+ end
+
@html_document = nil
unless @controller.respond_to?(:recycle!)
@@ -622,6 +661,11 @@ module ActionController
@request.session.update(session) if session
@request.flash.update(flash || {})
+ if xhr
+ @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ end
+
@controller.request = @request
@controller.response = @response
@@ -643,6 +687,13 @@ module ActionController
if flash_value = @request.flash.to_session_value
@request.session['flash'] = flash_value
+ else
+ @request.session.delete('flash')
+ end
+
+ if xhr
+ @request.env.delete 'HTTP_X_REQUESTED_WITH'
+ @request.env.delete 'HTTP_ACCEPT'
end
@response
@@ -693,6 +744,38 @@ module ActionController
private
+ def process_with_kwargs(http_method, action, *args)
+ if kwarg_request?(*args)
+ args.first.merge!(method: http_method)
+ process(action, *args)
+ else
+ non_kwarg_request_warning if args.present?
+
+ args = args.unshift(http_method)
+ process(action, *args)
+ end
+ end
+
+ REQUEST_KWARGS = %i(params session flash method body xhr)
+ def kwarg_request?(*args)
+ args[0].respond_to?(:keys) && (
+ (args[0].key?(:format) && args[0].keys.size == 1) ||
+ args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
+ )
+ end
+
+ def non_kwarg_request_warning
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ ActionController::TestCase HTTP request methods will accept only
+ keyword arguments in future Rails versions.
+
+ Examples:
+
+ get :show, params: { id: 1 }, session: { user_id: 1 }
+ process :update, method: :post, params: { id: 1 }
+ MSG
+ end
+
def document_root_element
html_document.root
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 2b036796ab..e9df984c86 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -121,6 +121,7 @@ module ActionDispatch
end
def match_head_routes(routes, req)
+ routes.delete_if { |route| route.verb == // }
head_routes = match_routes(routes, req)
if head_routes.empty?
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index a4862e33aa..d176a73633 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,6 @@
require 'action_controller/metal/exceptions'
require 'active_support/core_ext/module/attribute_accessors'
+require 'rack/utils'
module ActionDispatch
class ExceptionWrapper
@@ -87,8 +88,7 @@ module ActionDispatch
def source_extracts
backtrace.map do |trace|
- file, line = trace.split(":")
- line_number = line.to_i
+ file, line_number = extract_file_and_line_number(trace)
{
code: source_fragment(file, line_number),
@@ -139,6 +139,13 @@ module ActionDispatch
end
end
+ def extract_file_and_line_number(trace)
+ # Split by the first colon followed by some digits, which works for both
+ # Windows and Unix path styles.
+ file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
+ [file, line.to_i]
+ end
+
def expand_backtrace
@exception.backtrace.unshift(
@exception.to_s.split("\n")
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index a7f95150a4..59639a010e 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -80,24 +80,30 @@ module ActionDispatch
include Enumerable
def self.from_session_value(value) #:nodoc:
- flash = case value
- when FlashHash # Rails 3.1, 3.2
- new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
- when Hash # Rails 4.0
- new(value['flashes'], value['discard'])
- else
- new
- end
-
- flash.tap(&:sweep)
- end
-
- # Builds a hash containing the discarded values and the hashes
- # representing the flashes.
- # If there are no values in @flashes, returns nil.
+ case value
+ when FlashHash # Rails 3.1, 3.2
+ flashes = value.instance_variable_get(:@flashes)
+ if discard = value.instance_variable_get(:@used)
+ flashes.except!(*discard)
+ end
+ new(flashes, flashes.keys)
+ when Hash # Rails 4.0
+ flashes = value['flashes']
+ if discard = value['discard']
+ flashes.except!(*discard)
+ end
+ new(flashes, flashes.keys)
+ else
+ new
+ end
+ end
+
+ # Builds a hash containing the flashes to keep for the next request.
+ # If there are none to keep, returns nil.
def to_session_value #:nodoc:
- return nil if empty?
- {'discard' => @discard.to_a, 'flashes' => @flashes}
+ flashes_to_keep = @flashes.except(*@discard)
+ return nil if flashes_to_keep.empty?
+ {'flashes' => flashes_to_keep}
end
def initialize(flashes = {}, discard = []) #:nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 25658bac3d..cf31815c23 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -12,19 +12,23 @@ module ActionDispatch
# The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
# from multiple pieces of the stack.
class RequestId
+ X_REQUEST_ID = "X-Request-Id".freeze # :nodoc:
+ ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
+ HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # :nodoc:
+
def initialize(app)
@app = app
end
def call(env)
- env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
- @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
+ env[ACTION_DISPATCH_REQUEST_ID] = external_request_id(env) || internal_request_id
+ @app.call(env).tap { |_status, headers, _body| headers[X_REQUEST_ID] = env[ACTION_DISPATCH_REQUEST_ID] }
end
private
def external_request_id(env)
- if request_id = env["HTTP_X_REQUEST_ID"].presence
- request_id.gsub(/[^\w\-]/, "").first(255)
+ if request_id = env[HTTP_X_REQUEST_ID].presence
+ request_id.gsub(/[^\w\-]/, "".freeze).first(255)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 002bf8b11a..2e1bd45c3d 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -23,10 +23,9 @@ module ActionDispatch
def match?(path)
path = URI.parser.unescape(path)
return false unless path.valid_encoding?
+ path = Rack::Utils.clean_path_info path
- paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"].map { |v|
- Rack::Utils.clean_path_info v
- }
+ paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"]
if match = paths.detect { |p|
path = File.join(@root, p)
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index b4c861d306..ce04f0b08a 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -12,14 +12,15 @@ require 'action_dispatch/routing/endpoint'
module ActionDispatch
module Routing
- class RouteSet #:nodoc:
+ # :stopdoc:
+ class RouteSet
# Since the router holds references to many parts of the system
# like engines, controllers and the application itself, inspecting
# the route set can actually be really slow, therefore we default
# alias inspect to to_s.
alias inspect to_s
- class Dispatcher < Routing::Endpoint #:nodoc:
+ class Dispatcher < Routing::Endpoint
def initialize(defaults)
@defaults = defaults
@controller_class_names = ThreadSafe::Cache.new
@@ -84,7 +85,7 @@ module ActionDispatch
# A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the
# named routes.
- class NamedRouteCollection #:nodoc:
+ class NamedRouteCollection
include Enumerable
attr_reader :routes, :url_helpers_module, :path_helpers_module
@@ -161,7 +162,7 @@ module ActionDispatch
routes.length
end
- class UrlHelper # :nodoc:
+ class UrlHelper
def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
OptimizedUrlHelper.new(route, options, route_name, url_strategy)
@@ -176,7 +177,7 @@ module ActionDispatch
attr_reader :url_strategy, :route_name
- class OptimizedUrlHelper < UrlHelper # :nodoc:
+ class OptimizedUrlHelper < UrlHelper
attr_reader :arg_size
def initialize(route, options, route_name, url_strategy)
@@ -264,9 +265,10 @@ module ActionDispatch
path_params -= controller_options.keys
path_params -= result.keys
end
- path_params.each { |param|
- result[param] = inner_options.fetch(param) { args.shift }
- }
+ path_params -= inner_options.keys
+ path_params.take(args.size).each do |param|
+ result[param] = args.shift
+ end
end
result.merge!(inner_options)
@@ -299,11 +301,9 @@ module ActionDispatch
end
end
- # :stopdoc:
# strategy for building urls to send to the client
PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
- # :startdoc:
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
@@ -378,7 +378,7 @@ module ActionDispatch
Routing::RouteSet::Dispatcher.new(defaults)
end
- module MountedHelpers #:nodoc:
+ module MountedHelpers
extend ActiveSupport::Concern
include UrlFor
end
@@ -395,9 +395,11 @@ module ActionDispatch
return if MountedHelpers.method_defined?(name)
routes = self
+ helpers = routes.url_helpers
+
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, _routes_context)
+ RoutesProxy.new(routes, _routes_context, helpers)
end
end
@@ -419,7 +421,14 @@ module ActionDispatch
# Rails.application.routes.url_helpers.url_for(args)
@_routes = routes
class << self
- delegate :url_for, :optimize_routes_generation?, to: '@_routes'
+ def url_for(options)
+ @_routes.url_for(options)
+ end
+
+ def optimize_routes_generation?
+ @_routes.optimize_routes_generation?
+ end
+
attr_reader :_routes
def url_options; {}; end
end
@@ -535,7 +544,7 @@ module ActionDispatch
end
private :build_conditions
- class Generator #:nodoc:
+ class Generator
PARAMETERIZE = lambda do |name, value|
if name == :controller
value
@@ -687,7 +696,7 @@ module ActionDispatch
options.delete(:script_name) || ''
end
- def path_for(options, route_name = nil) # :nodoc:
+ def path_for(options, route_name = nil)
url_for(options, route_name, PATH)
end
@@ -773,5 +782,6 @@ module ActionDispatch
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
end
+ # :startdoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index e2393d3799..040ea04046 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -8,8 +8,9 @@ module ActionDispatch
attr_accessor :scope, :routes
alias :_routes :routes
- def initialize(routes, scope)
+ def initialize(routes, scope, helpers)
@routes, @scope = routes, scope
+ @helpers = helpers
end
def url_options
@@ -19,16 +20,16 @@ module ActionDispatch
end
def respond_to?(method, include_private = false)
- super || routes.url_helpers.respond_to?(method)
+ super || @helpers.respond_to?(method)
end
def method_missing(method, *args)
- if routes.url_helpers.respond_to?(method)
+ if @helpers.respond_to?(method)
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args)
options = args.extract_options!
args << url_options.merge((options || {}).symbolize_keys)
- routes.url_helpers.#{method}(*args)
+ @helpers.#{method}(*args)
end
RUBY
send(method, *args)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 28dc88d317..c94eea9134 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -38,18 +38,24 @@ module ActionDispatch
# # Test a custom route
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
def assert_recognizes(expected_options, path, extras={}, msg=nil)
- request = recognized_request_for(path, extras, msg)
+ if path.is_a?(Hash) && path[:method].to_s == "all"
+ [:get, :post, :put, :delete].each do |method|
+ assert_recognizes(expected_options, path.merge(method: method), extras, msg)
+ end
+ else
+ request = recognized_request_for(path, extras, msg)
- expected_options = expected_options.clone
+ expected_options = expected_options.clone
- expected_options.stringify_keys!
+ expected_options.stringify_keys!
- msg = message(msg, "") {
- sprintf("The recognized options <%s> did not match <%s>, difference:",
- request.path_parameters, expected_options)
- }
+ msg = message(msg, "") {
+ sprintf("The recognized options <%s> did not match <%s>, difference:",
+ request.path_parameters, expected_options)
+ }
- assert_equal(expected_options, request.path_parameters, msg)
+ assert_equal(expected_options, request.path_parameters, msg)
+ end
end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
@@ -144,7 +150,7 @@ module ActionDispatch
old_controller, @controller = @controller, @controller.clone
_routes = @routes
- @controller.singleton_class.send(:include, _routes.url_helpers)
+ @controller.singleton_class.include(_routes.url_helpers)
@controller.view_context_class = Class.new(@controller.view_context_class) do
include _routes.url_helpers
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index f0e2c5becc..9b00616968 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -12,12 +12,14 @@ module ActionDispatch
#
# - +path+: The URI (as a String) on which you want to perform a GET
# request.
- # - +parameters+: The HTTP parameters that you want to pass. This may
+ # - +params+: The HTTP parameters that you want to pass. This may
# be +nil+,
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
+ # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # merged into the Rack env hash.
+ # - +env+: Additional env to pass, as a Hash. The headers will be
# merged into the Rack env hash.
#
# This method returns a Response object, which one can use to
@@ -28,38 +30,43 @@ module ActionDispatch
#
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
- def get(path, parameters = nil, headers_or_env = nil)
- process :get, path, parameters, headers_or_env
+ #
+ # Example:
+ #
+ # get '/feed', params: { since: 201501011400 }
+ # post '/profile', headers: { "X-Test-Header" => "testvalue" }
+ def get(path, *args)
+ process_with_kwargs(:get, path, *args)
end
# Performs a POST request with the given parameters. See +#get+ for more
# details.
- def post(path, parameters = nil, headers_or_env = nil)
- process :post, path, parameters, headers_or_env
+ def post(path, *args)
+ process_with_kwargs(:post, path, *args)
end
# Performs a PATCH request with the given parameters. See +#get+ for more
# details.
- def patch(path, parameters = nil, headers_or_env = nil)
- process :patch, path, parameters, headers_or_env
+ def patch(path, *args)
+ process_with_kwargs(:patch, path, *args)
end
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
- def put(path, parameters = nil, headers_or_env = nil)
- process :put, path, parameters, headers_or_env
+ def put(path, *args)
+ process_with_kwargs(:put, path, *args)
end
# Performs a DELETE request with the given parameters. See +#get+ for
# more details.
- def delete(path, parameters = nil, headers_or_env = nil)
- process :delete, path, parameters, headers_or_env
+ def delete(path, *args)
+ process_with_kwargs(:delete, path, *args)
end
# Performs a HEAD request with the given parameters. See +#get+ for more
# details.
- def head(path, parameters = nil, headers_or_env = nil)
- process :head, path, parameters, headers_or_env
+ def head(path, *args)
+ process_with_kwargs(:head, path, *args)
end
# Performs an XMLHttpRequest request with the given parameters, mirroring
@@ -68,11 +75,29 @@ module ActionDispatch
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
# string; the headers are a hash.
- def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
- headers_or_env ||= {}
- headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- process(request_method, path, parameters, headers_or_env)
+ #
+ # Example:
+ #
+ # xhr :get, '/feed', params: { since: 201501011400 }
+ def xml_http_request(request_method, path, *args)
+ if kwarg_request?(*args)
+ params, headers, env = args.first.values_at(:params, :headers, :env)
+ else
+ params = args[0]
+ headers = args[1]
+ env = {}
+
+ if params.present? || headers.present?
+ non_kwarg_request_warning
+ end
+ end
+
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ xhr and xml_http_request methods are deprecated in favor of
+ `get "/posts", xhr: true` and `post "/posts/1", xhr: true`
+ MSG
+
+ process(request_method, path, params: params, headers: headers, xhr: true)
end
alias xhr :xml_http_request
@@ -89,40 +114,52 @@ module ActionDispatch
# redirect. Note that the redirects are followed until the response is
# not a redirect--this means you may run into an infinite loop if your
# redirect loops back to itself.
- def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
- process(http_method, path, parameters, headers_or_env)
+ #
+ # Example:
+ #
+ # request_via_redirect :post, '/welcome',
+ # params: { ref_id: 14 },
+ # headers: { "X-Test-Header" => "testvalue" }
+ def request_via_redirect(http_method, path, *args)
+ process_with_kwargs(http_method, path, *args)
+
follow_redirect! while redirect?
status
end
# Performs a GET request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def get_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:get, path, parameters, headers_or_env)
+ def get_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:get, path, *args)
end
# Performs a POST request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def post_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:post, path, parameters, headers_or_env)
+ def post_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:post, path, *args)
end
# Performs a PATCH request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:patch, path, parameters, headers_or_env)
+ def patch_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:patch, path, *args)
end
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def put_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:put, path, parameters, headers_or_env)
+ def put_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:put, path, *args)
end
# Performs a DELETE request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:delete, path, parameters, headers_or_env)
+ def delete_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:delete, path, *args)
end
end
@@ -185,15 +222,6 @@ module ActionDispatch
super()
@app = app
- # If the app is a Rails app, make url_helpers available on the session
- # This makes app.url_for and app.foo_path available in the console
- if app.respond_to?(:routes)
- singleton_class.class_eval do
- include app.routes.url_helpers
- include app.routes.mounted_helpers
- end
- end
-
reset!
end
@@ -261,8 +289,38 @@ module ActionDispatch
@_mock_session ||= Rack::MockSession.new(@app, host)
end
+ def process_with_kwargs(http_method, path, *args)
+ if kwarg_request?(*args)
+ process(http_method, path, *args)
+ else
+ non_kwarg_request_warning if args.present?
+ process(http_method, path, { params: args[0], headers: args[1] })
+ end
+ end
+
+ REQUEST_KWARGS = %i(params headers env xhr)
+ def kwarg_request?(*args)
+ args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
+ end
+
+ def non_kwarg_request_warning
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ ActionDispatch::IntegrationTest HTTP request methods will accept only
+ the following keyword arguments in future Rails versions:
+ #{REQUEST_KWARGS.join(', ')}
+
+ Examples:
+
+ get '/profile',
+ params: { id: 1 },
+ headers: { 'X-Extra-Header' => '123' },
+ env: { 'action_dispatch.custom' => 'custom' },
+ xhr: true
+ MSG
+ end
+
# Performs the actual request.
- def process(method, path, parameters = nil, headers_or_env = nil)
+ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -272,9 +330,9 @@ module ActionDispatch
hostname, port = host.split(':')
- env = {
+ request_env = {
:method => method,
- :params => parameters,
+ :params => params,
"SERVER_NAME" => hostname,
"SERVER_PORT" => port || (https? ? "443" : "80"),
@@ -287,14 +345,26 @@ module ActionDispatch
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"HTTP_ACCEPT" => accept
}
- # this modifies the passed env directly
- Http::Headers.new(env).merge!(headers_or_env || {})
+
+ if xhr
+ headers ||= {}
+ headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ end
+
+ # this modifies the passed request_env directly
+ if headers.present?
+ Http::Headers.new(request_env).merge!(headers)
+ end
+ if env.present?
+ Http::Headers.new(request_env).merge!(env)
+ end
session = Rack::Test::Session.new(_mock_session)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
- session.request(build_full_uri(path, env), env)
+ session.request(build_full_uri(path, request_env), request_env)
@request_count += 1
@request = ActionDispatch::Request.new(session.last_request.env)
@@ -305,7 +375,7 @@ module ActionDispatch
@controller = session.last_request.env['action_controller.instance']
- return response.status
+ response.status
end
def build_full_uri(path, env)
@@ -316,14 +386,36 @@ module ActionDispatch
module Runner
include ActionDispatch::Assertions
- def app
- @app ||= nil
+ APP_SESSIONS = {}
+
+ attr_reader :app
+
+ def before_setup
+ super
+ @app = nil
+ @integration_session = nil
+ end
+
+ def integration_session
+ @integration_session ||= create_session(app)
end
# Reset the current session. This is useful for testing multiple sessions
# in a single test case.
def reset!
- @integration_session = Integration::Session.new(app)
+ @integration_session = create_session(app)
+ end
+
+ def create_session(app)
+ klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
+ # If the app is a Rails app, make url_helpers available on the session
+ # This makes app.url_for and app.foo_path available in the console
+ if app.respond_to?(:routes)
+ include app.routes.url_helpers
+ include app.routes.mounted_helpers
+ end
+ }
+ klass.new(app)
end
def remove! # :nodoc:
@@ -333,8 +425,6 @@ module ActionDispatch
%w(get post patch put head delete cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
- reset! unless integration_session
-
# reset the html_document variable, except for cookies/assigns calls
unless method == 'cookies' || method == 'assigns'
@html_document = nil
@@ -366,19 +456,16 @@ module ActionDispatch
# Copy the instance variables from the current session instance into the
# test instance.
def copy_session_variables! #:nodoc:
- return unless integration_session
- %w(controller response request).each do |var|
- instance_variable_set("@#{var}", @integration_session.__send__(var))
- end
+ @controller = @integration_session.controller
+ @response = @integration_session.response
+ @request = @integration_session.request
end
def default_url_options
- reset! unless integration_session
integration_session.default_url_options
end
def default_url_options=(options)
- reset! unless integration_session
integration_session.default_url_options = options
end
@@ -388,7 +475,6 @@ module ActionDispatch
# Delegate unhandled messages to the current session instance.
def method_missing(sym, *args, &block)
- reset! unless integration_session
if integration_session.respond_to?(sym)
integration_session.__send__(sym, *args, &block).tap do
copy_session_variables!
@@ -397,11 +483,6 @@ module ActionDispatch
super
end
end
-
- private
- def integration_session
- @integration_session ||= nil
- end
end
end
@@ -578,7 +659,6 @@ module ActionDispatch
end
def url_options
- reset! unless integration_session
integration_session.url_options
end
diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb
index 4fdc480b43..8289252dfc 100644
--- a/actionpack/test/abstract/translation_test.rb
+++ b/actionpack/test/abstract/translation_test.rb
@@ -9,6 +9,22 @@ module AbstractController
class TranslationControllerTest < ActiveSupport::TestCase
def setup
@controller = TranslationController.new
+ I18n.backend.store_translations(:en, {
+ one: {
+ two: 'bar',
+ },
+ abstract_controller: {
+ testing: {
+ translation: {
+ index: {
+ foo: 'bar',
+ },
+ no_action: 'no_action_tr',
+ },
+ },
+ },
+ })
+ @controller.stubs(action_name: :index)
end
def test_action_controller_base_responds_to_translate
@@ -28,16 +44,19 @@ module AbstractController
end
def test_lazy_lookup
- expected = 'bar'
- @controller.stubs(action_name: :index)
- I18n.stubs(:translate).with('abstract_controller.testing.translation.index.foo').returns(expected)
- assert_equal expected, @controller.t('.foo')
+ assert_equal 'bar', @controller.t('.foo')
+ end
+
+ def test_lazy_lookup_with_symbol
+ assert_equal 'bar', @controller.t(:'.foo')
+ end
+
+ def test_lazy_lookup_fallback
+ assert_equal 'no_action_tr', @controller.t(:'.no_action')
end
def test_default_translation
- key, expected = 'one.two', 'bar'
- I18n.stubs(:translate).with(key).returns(expected)
- assert_equal expected, @controller.t(key)
+ assert_equal 'bar', @controller.t('one.two')
end
def test_localize
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index d0dfbfbc74..21586e2193 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -378,7 +378,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
end
def test_render_based_on_parameters
- process :render_based_on_parameters, "GET", "name" => "David"
+ process :render_based_on_parameters,
+ method: "GET",
+ params: { name: "David" }
assert_equal "Mr. David", @response.body
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 001493afc0..f7ad8e5158 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -27,6 +27,16 @@ class DefaultUrlOptionsController < ActionController::Base
end
end
+class OptionalDefaultUrlOptionsController < ActionController::Base
+ def show
+ render nothing: true
+ end
+
+ def default_url_options
+ { format: 'atom', id: 'default-id' }
+ end
+end
+
class UrlOptionsController < ActionController::Base
def from_view
render :inline => "<%= #{params[:route]} %>"
@@ -168,7 +178,7 @@ class UrlOptionsTest < ActionController::TestCase
get ':controller/:action'
end
- get :from_view, :route => "from_view_url"
+ get :from_view, params: { route: "from_view_url" }
assert_equal 'http://www.override.com/from_view', @response.body
assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url)
@@ -202,7 +212,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase
get ':controller/:action'
end
- get :from_view, :route => "from_view_url"
+ get :from_view, params: { route: "from_view_url" }
assert_equal 'http://www.override.com/from_view?locale=en', @response.body
assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
@@ -219,7 +229,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase
get ':controller/:action'
end
- get :from_view, :route => "description_path(1)"
+ get :from_view, params: { route: "description_path(1)" }
assert_equal '/en/descriptions/1', @response.body
assert_equal '/en/descriptions', @controller.send(:descriptions_path)
@@ -234,7 +244,18 @@ class DefaultUrlOptionsTest < ActionController::TestCase
assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml")
end
end
+end
+class OptionalDefaultUrlOptionsControllerTest < ActionController::TestCase
+ def test_default_url_options_override_missing_positional_arguments
+ with_routing do |set|
+ set.draw do
+ get "/things/:id(.:format)" => 'things#show', :as => :thing
+ end
+ assert_equal '/things/1.atom', thing_path('1')
+ assert_equal '/things/default-id.atom', thing_path
+ end
+ end
end
class EmptyUrlOptionsTest < ActionController::TestCase
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index c0e6a2ebd1..4760ec1698 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -210,7 +210,7 @@ CACHED
end
def test_skipping_fragment_cache_digesting
- get :fragment_cached_without_digest, :format => "html"
+ get :fragment_cached_without_digest, format: "html"
assert_response :success
expected_body = "<body>\n<p>ERB</p>\n</body>\n"
@@ -244,7 +244,7 @@ CACHED
end
def test_html_formatted_fragment_caching
- get :formatted_fragment_cached, :format => "html"
+ get :formatted_fragment_cached, format: "html"
assert_response :success
expected_body = "<body>\n<p>ERB</p>\n</body>\n"
@@ -255,7 +255,7 @@ CACHED
end
def test_xml_formatted_fragment_caching
- get :formatted_fragment_cached, :format => "xml"
+ get :formatted_fragment_cached, format: "xml"
assert_response :success
expected_body = "<body>\n <p>Builder</p>\n</body>\n"
@@ -269,7 +269,7 @@ CACHED
def test_fragment_caching_with_variant
@request.variant = :phone
- get :formatted_fragment_cached_with_variant, :format => "html"
+ get :formatted_fragment_cached_with_variant, format: "html"
assert_response :success
expected_body = "<body>\n<p>PHONE</p>\n</body>\n"
diff --git a/actionpack/test/controller/default_url_options_with_before_action_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb
index 656fd0431e..230f40d7ad 100644
--- a/actionpack/test/controller/default_url_options_with_before_action_test.rb
+++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb
@@ -1,6 +1,5 @@
require 'abstract_unit'
-
class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
before_action { I18n.locale = params[:locale] }
@@ -23,7 +22,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::Tes
# This test has its roots in issue #1872
test "should redirect with correct locale :de" do
- get :redirect, :locale => "de"
+ get :redirect, params: { locale: "de" }
assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de"
end
end
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index d979b561f2..081288ef21 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -57,33 +57,36 @@ module ActionDispatch
def test_to_session_value
@hash['foo'] = 'bar'
- assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => []}, @hash.to_session_value)
-
- @hash.discard('foo')
- assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => %w[foo]}, @hash.to_session_value)
+ assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value)
@hash.now['qux'] = 1
- assert_equal({'flashes' => {'foo' => 'bar', 'qux' => 1}, 'discard' => %w[foo qux]}, @hash.to_session_value)
+ assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value)
+
+ @hash.discard('foo')
+ assert_equal(nil, @hash.to_session_value)
@hash.sweep
assert_equal(nil, @hash.to_session_value)
end
def test_from_session_value
- rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoNQGZsYXNoZXN7BkkiDG1lc3NhZ2UGOwBGSSIKSGVsbG8GOwBGOglAbm93MA=='
+ # {"session_id"=>"f8e1b8152ba7609c28bbb17ec9263ba7", "flash"=>#<ActionDispatch::Flash::FlashHash:0x00000000000000 @used=#<Set: {"farewell"}>, @closed=false, @flashes={"greeting"=>"Hello", "farewell"=>"Goodbye"}, @now=nil>}
+ rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsGSSINZmFyZXdlbGwGOwBUVDoMQGNsb3NlZEY6DUBmbGFzaGVzewdJIg1ncmVldGluZwY7AFRJIgpIZWxsbwY7AFRJIg1mYXJld2VsbAY7AFRJIgxHb29kYnllBjsAVDoJQG5vdzA='
session = Marshal.load(Base64.decode64(rails_3_2_cookie))
hash = Flash::FlashHash.from_session_value(session['flash'])
- assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
+ assert_equal({'greeting' => 'Hello'}, hash.to_hash)
+ assert_equal(nil, hash.to_session_value)
end
def test_from_session_value_on_json_serializer
- decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }"
+ decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[\"farewell\"], \"flashes\":{\"greeting\":\"Hello\",\"farewell\":\"Goodbye\"}} }"
session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data)
hash = Flash::FlashHash.from_session_value(session['flash'])
- assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value)
- assert_equal "hey you", hash[:message]
- assert_equal "hey you", hash["message"]
+ assert_equal({'greeting' => 'Hello'}, hash.to_hash)
+ assert_equal(nil, hash.to_session_value)
+ assert_equal "Hello", hash[:greeting]
+ assert_equal "Hello", hash["greeting"]
end
def test_empty?
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 3720a920d0..0ff0a1ef61 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -288,16 +288,16 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
def test_setting_flash_does_not_raise_in_following_requests
with_test_route_set do
env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
- get '/set_flash', nil, env
- get '/set_flash', nil, env
+ get '/set_flash', env: env
+ get '/set_flash', env: env
end
end
def test_setting_flash_now_does_not_raise_in_following_requests
with_test_route_set do
env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
- get '/set_flash_now', nil, env
- get '/set_flash_now', nil, env
+ get '/set_flash_now', env: env
+ get '/set_flash_now', env: env
end
end
@@ -312,9 +312,11 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
private
# Overwrite get to send SessionSecret in env hash
- def get(path, parameters = nil, env = {})
- env["action_dispatch.key_generator"] ||= Generator
- super
+ def get(path, *args)
+ args[0] ||= {}
+ args[0][:env] ||= {}
+ args[0][:env]["action_dispatch.key_generator"] ||= Generator
+ super(path, *args)
end
def with_test_route_set
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 00d4612ac9..04222745d9 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -100,7 +100,7 @@ class ForceSSLControllerLevelTest < ActionController::TestCase
end
def test_banana_redirects_to_https_with_extra_params
- get :banana, :token => "secret"
+ get :banana, params: { token: "secret" }
assert_response 301
assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url
end
@@ -273,7 +273,7 @@ class ForceSSLFormatTest < ActionController::TestCase
get '/foo', :to => 'force_ssl_controller_level#banana'
end
- get :banana, :format => :json
+ get :banana, format: :json
assert_response 301
assert_equal 'https://test.host/foo.json', redirect_to_url
end
@@ -294,7 +294,7 @@ class ForceSSLOptionalSegmentsTest < ActionController::TestCase
end
@request.env['PATH_INFO'] = '/en/foo'
- get :banana, :locale => 'en'
+ get :banana, params: { locale: 'en' }
assert_equal 'en', @controller.params[:locale]
assert_response 301
assert_equal 'https://test.host/en/foo', redirect_to_url
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 936b8c2450..e263ed341f 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -29,7 +29,7 @@ module ImpressiveLibrary
def useful_function() end
end
-ActionController::Base.send :include, ImpressiveLibrary
+ActionController::Base.include(ImpressiveLibrary)
class JustMeController < ActionController::Base
clear_helpers
@@ -223,10 +223,10 @@ class HelperTest < ActiveSupport::TestCase
# fun/pdf_helper.rb
assert methods.include?(:foobar)
end
-
+
def test_helper_proxy_config
AllHelpersController.config.my_var = 'smth'
-
+
assert_equal 'smth', AllHelpersController.helpers.config.my_var
end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 9052fc6962..20962a90cb 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -83,6 +83,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_response :unauthorized
assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials"
end
+
+ test "unsuccessful authentication with #{header.downcase} and no credentials" do
+ get :show
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and no credentials"
+ end
end
def test_encode_credentials_has_no_newline
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 5c0651bd73..9ab1549e8e 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -32,158 +32,275 @@ class SessionTest < ActiveSupport::TestCase
def test_request_via_redirect_uses_given_method
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
- @session.expects(:process).with(:put, path, args, headers)
+ @session.expects(:process).with(:put, path, params: args, headers: headers)
@session.stubs(:redirect?).returns(false)
- @session.request_via_redirect(:put, path, args, headers)
+ @session.request_via_redirect(:put, path, params: args, headers: headers)
+ end
+
+ def test_deprecated_request_via_redirect_uses_given_method
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
+ @session.expects(:process).with(:put, path, params: args, headers: headers)
+ @session.stubs(:redirect?).returns(false)
+ assert_deprecated { @session.request_via_redirect(:put, path, args, headers) }
end
def test_request_via_redirect_follows_redirects
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
@session.stubs(:redirect?).returns(true, true, false)
@session.expects(:follow_redirect!).times(2)
- @session.request_via_redirect(:get, path, args, headers)
+ @session.request_via_redirect(:get, path, params: args, headers: headers)
end
def test_request_via_redirect_returns_status
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
@session.stubs(:redirect?).returns(false)
@session.stubs(:status).returns(200)
- assert_equal 200, @session.request_via_redirect(:get, path, args, headers)
+ assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers)
end
- def test_get_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_get_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:get, path, args, headers)
- @session.get_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.get_via_redirect(path, args, headers)
+ end
end
- def test_post_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_post_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:post, path, args, headers)
- @session.post_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.post_via_redirect(path, args, headers)
+ end
end
- def test_patch_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_patch_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:patch, path, args, headers)
- @session.patch_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.patch_via_redirect(path, args, headers)
+ end
end
- def test_put_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_put_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers)
- @session.put_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.put_via_redirect(path, args, headers)
+ end
end
- def test_delete_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_delete_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:delete, path, args, headers)
- @session.delete_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.delete_via_redirect(path, args, headers)
+ end
end
def test_get
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:get,path,params,headers)
- @session.get(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers)
+ @session.get(path, params: params, headers: headers)
+ end
+
+ def test_get_with_env_and_headers
+ path = "/index"; params = "blah"; headers = { location: 'blah' }; env = { 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, env: env)
+ @session.get(path, params: params, headers: headers, env: env)
+ end
+
+ def test_deprecated_get
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.get(path, params, headers)
+ }
end
def test_post
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:post,path,params,headers)
- @session.post(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.post(path, params, headers)
+ }
+ end
+
+ def test_deprecated_post
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers)
+ @session.post(path, params: params, headers: headers)
end
def test_patch
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:patch,path,params,headers)
- @session.patch(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers)
+ @session.patch(path, params: params, headers: headers)
+ end
+
+ def test_deprecated_patch
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.patch(path, params, headers)
+ }
end
def test_put
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:put,path,params,headers)
- @session.put(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers)
+ @session.put(path, params: params, headers: headers)
+ end
+
+ def test_deprecated_put
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.put(path, params, headers)
+ }
end
def test_delete
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:delete,path,params,headers)
- @session.delete(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.delete(path,params,headers)
+ }
+ end
+
+ def test_deprecated_delete
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers)
+ @session.delete(path, params: params, headers: headers)
end
def test_head
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:head,path,params,headers)
- @session.head(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers)
+ @session.head(path, params: params, headers: headers)
+ end
+
+ def deprecated_test_head
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.head(path, params, headers)
+ }
end
def test_xml_http_request_get
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:get,path,params,headers_after_xhr)
- @session.xml_http_request(:get,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true)
+ @session.get(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_get
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true)
+ @session.get(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_get
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) {
+ @session.xml_http_request(:get, path, params, headers)
+ }
end
def test_xml_http_request_post
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:post,path,params,headers_after_xhr)
- @session.xml_http_request(:post,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true)
+ @session.post(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_post
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true)
+ @session.post(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_post
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:post,path,params,headers) }
end
def test_xml_http_request_patch
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:patch,path,params,headers_after_xhr)
- @session.xml_http_request(:patch,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true)
+ @session.patch(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true)
+ @session.patch(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:patch,path,params,headers) }
end
def test_xml_http_request_put
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:put,path,params,headers_after_xhr)
- @session.xml_http_request(:put,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true)
+ @session.put(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_put
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true)
+ @session.put(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_put
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:put, path, params, headers) }
end
def test_xml_http_request_delete
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:delete,path,params,headers_after_xhr)
- @session.xml_http_request(:delete,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true)
+ @session.delete(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_delete
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true)
+ assert_deprecated { @session.xml_http_request(:delete, path, params: params, headers: headers) }
+ end
+
+ def test_deprecated_args_xml_http_request_delete
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:delete, path, params, headers) }
end
def test_xml_http_request_head
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:head,path,params,headers_after_xhr)
- @session.xml_http_request(:head,path,params,headers)
- end
-
- def test_xml_http_request_override_accept
- path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
- )
- @session.expects(:process).with(:post,path,params,headers_after_xhr)
- @session.xml_http_request(:post,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true)
+ @session.head(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_head
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:head, path, params: params, headers: headers) }
+ end
+
+ def test_deprecated_args_xml_http_request_head
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true)
+ assert_deprecated { @session.xml_http_request(:head, path, params, headers) }
end
end
@@ -388,7 +505,19 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def test_xml_http_request_get
with_test_route_set do
- xhr :get, '/get'
+ get '/get', xhr: true
+ assert_equal 200, status
+ assert_equal "OK", status_message
+ assert_response 200
+ assert_response :success
+ assert_response :ok
+ assert_equal "JS OK", response.body
+ end
+ end
+
+ def test_deprecated_xml_http_request_get
+ with_test_route_set do
+ assert_deprecated { xhr :get, '/get' }
assert_equal 200, status
assert_equal "OK", status_message
assert_response 200
@@ -400,7 +529,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def test_request_with_bad_format
with_test_route_set do
- xhr :get, '/get.php'
+ get '/get.php', xhr: true
assert_equal 406, status
assert_response 406
assert_response :not_acceptable
@@ -423,7 +552,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def test_get_with_parameters
with_test_route_set do
- get '/get_with_params', :foo => "bar"
+ get '/get_with_params', params: { foo: "bar" }
assert_equal '/get_with_params', request.env["PATH_INFO"]
assert_equal '/get_with_params', request.path_info
assert_equal 'foo=bar', request.env["QUERY_STRING"]
@@ -544,7 +673,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
get 'get/:action', :to => controller, :as => :get_action
end
- self.singleton_class.send(:include, set.url_helpers)
+ self.singleton_class.include(set.url_helpers)
yield
end
@@ -588,14 +717,22 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
end
def test_pass_headers
- get "/success", {}, "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"
+ get "/success", headers: {"Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"}
assert_equal "http://nohost.com", @request.env["HTTP_HOST"]
assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"]
end
+ def test_pass_headers_and_env
+ get "/success", headers: { "X-Test-Header" => "value" }, env: { "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" }
+
+ assert_equal "http://test.com", @request.env["HTTP_HOST"]
+ assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
+ assert_equal "value", @request.env["HTTP_X_TEST_HEADER"]
+ end
+
def test_pass_env
- get "/success", {}, "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com"
+ get "/success", env: { "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" }
assert_equal "http://test.com", @request.env["HTTP_HOST"]
assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
@@ -685,7 +822,7 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
test "process do not modify the env passed as argument" do
env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' }
old_env = env.dup
- get '/foo', nil, env
+ get '/foo', env: env
assert_equal old_env, env
end
end
@@ -715,7 +852,7 @@ class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
end
test "filters rack request form vars" do
- post "/post", :username => 'cjolly', :password => 'secret'
+ post "/post", params: { username: 'cjolly', password: 'secret' }
assert_equal 'cjolly', request.filtered_parameters['username']
assert_equal '[FILTERED]', request.filtered_parameters['password']
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index 2be947c648..3576015513 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -30,7 +30,7 @@ class LocalizedTemplatesTest < ActionController::TestCase
def test_use_fallback_locales
I18n.locale = :"de-AT"
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.backend.class.include(I18n::Backend::Fallbacks)
I18n.fallbacks[:"de-AT"] = [:de]
get :hello_world
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 864c6ee130..03a4ad7823 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -140,7 +140,7 @@ class ACLogSubscriberTest < ActionController::TestCase
end
def test_process_action_with_parameters
- get :show, :id => '10'
+ get :show, params: { id: '10' }
wait
assert_equal 3, logs.size
@@ -148,8 +148,8 @@ class ACLogSubscriberTest < ActionController::TestCase
end
def test_multiple_process_with_parameters
- get :show, :id => '10'
- get :show, :id => '20'
+ get :show, params: { id: '10' }
+ get :show, params: { id: '20' }
wait
@@ -160,7 +160,7 @@ class ACLogSubscriberTest < ActionController::TestCase
def test_process_action_with_wrapped_parameters
@request.env['CONTENT_TYPE'] = 'application/json'
- post :show, :id => '10', :name => 'jose'
+ post :show, params: { id: '10', name: 'jose' }
wait
assert_equal 3, logs.size
@@ -186,7 +186,9 @@ class ACLogSubscriberTest < ActionController::TestCase
def test_process_action_with_filter_parameters
@request.env["action_dispatch.parameter_filter"] = [:lifo, :amount]
- get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
+ get :show, params: {
+ lifo: 'Pratik', amount: '420', step: '1'
+ }
wait
params = logs[1]
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
index 811c507af2..e20c08da4e 100644
--- a/actionpack/test/controller/mime/accept_format_test.rb
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -11,7 +11,7 @@ end
class StarStarMimeControllerTest < ActionController::TestCase
def test_javascript_with_format
@request.accept = "text/javascript"
- get :index, :format => 'js'
+ get :index, format: 'js'
assert_match "function addition(a,b){ return a+b; }", @response.body
end
@@ -86,7 +86,7 @@ class MimeControllerLayoutsTest < ActionController::TestCase
end
def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout
- get :index, :format => :js
+ get :index, format: :js
assert_equal "Hello Firefox", @response.body
end
end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 66d2fd7716..1f5f66dc80 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -310,17 +310,17 @@ class RespondToControllerTest < ActionController::TestCase
def test_js_or_html
@request.accept = "text/javascript, text/html"
- xhr :get, :js_or_html
+ get :js_or_html, xhr: true
assert_equal 'JS', @response.body
@request.accept = "text/javascript, text/html"
- xhr :get, :html_or_xml
+ get :html_or_xml, xhr: true
assert_equal 'HTML', @response.body
@request.accept = "text/javascript, text/html"
assert_raises(ActionController::UnknownFormat) do
- xhr :get, :just_xml
+ get :just_xml, xhr: true
end
end
@@ -335,13 +335,13 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_json_or_yaml
- xhr :get, :json_or_yaml
+ get :json_or_yaml, xhr: true
assert_equal 'JSON', @response.body
- get :json_or_yaml, :format => 'json'
+ get :json_or_yaml, format: 'json'
assert_equal 'JSON', @response.body
- get :json_or_yaml, :format => 'yaml'
+ get :json_or_yaml, format: 'yaml'
assert_equal 'YAML', @response.body
{ 'YAML' => %w(text/yaml),
@@ -357,13 +357,13 @@ class RespondToControllerTest < ActionController::TestCase
def test_js_or_anything
@request.accept = "text/javascript, */*"
- xhr :get, :js_or_html
+ get :js_or_html, xhr: true
assert_equal 'JS', @response.body
- xhr :get, :html_or_xml
+ get :html_or_xml, xhr: true
assert_equal 'HTML', @response.body
- xhr :get, :just_xml
+ get :just_xml, xhr: true
assert_equal 'XML', @response.body
end
@@ -408,14 +408,14 @@ class RespondToControllerTest < ActionController::TestCase
def test_with_atom_content_type
@request.accept = ""
@request.env["CONTENT_TYPE"] = "application/atom+xml"
- xhr :get, :made_for_content_type
+ get :made_for_content_type, xhr: true
assert_equal "ATOM", @response.body
end
def test_with_rss_content_type
@request.accept = ""
@request.env["CONTENT_TYPE"] = "application/rss+xml"
- xhr :get, :made_for_content_type
+ get :made_for_content_type, xhr: true
assert_equal "RSS", @response.body
end
@@ -474,7 +474,7 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_handle_any_any_parameter_format
- get :handle_any_any, {:format=>'html'}
+ get :handle_any_any, format: 'html'
assert_equal 'HTML', @response.body
end
@@ -497,7 +497,7 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_handle_any_any_unkown_format
- get :handle_any_any, { format: 'php' }
+ get :handle_any_any, format: 'php'
assert_equal 'Whatever you ask for, I got it', @response.body
end
@@ -525,18 +525,18 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_xhr
- xhr :get, :js_or_html
+ get :js_or_html, xhr: true
assert_equal 'JS', @response.body
end
def test_custom_constant
- get :custom_constant_handling, :format => "mobile"
+ get :custom_constant_handling, format: "mobile"
assert_equal "text/x-mobile", @response.content_type
assert_equal "Mobile", @response.body
end
def test_custom_constant_handling_without_block
- get :custom_constant_handling_without_block, :format => "mobile"
+ get :custom_constant_handling_without_block, format: "mobile"
assert_equal "text/x-mobile", @response.content_type
assert_equal "Mobile", @response.body
end
@@ -545,13 +545,13 @@ class RespondToControllerTest < ActionController::TestCase
get :html_xml_or_rss
assert_equal "HTML", @response.body
- get :html_xml_or_rss, :format => "html"
+ get :html_xml_or_rss, format: "html"
assert_equal "HTML", @response.body
- get :html_xml_or_rss, :format => "xml"
+ get :html_xml_or_rss, format: "xml"
assert_equal "XML", @response.body
- get :html_xml_or_rss, :format => "rss"
+ get :html_xml_or_rss, format: "rss"
assert_equal "RSS", @response.body
end
@@ -559,12 +559,12 @@ class RespondToControllerTest < ActionController::TestCase
get :forced_xml
assert_equal "XML", @response.body
- get :forced_xml, :format => "html"
+ get :forced_xml, format: "html"
assert_equal "XML", @response.body
end
def test_extension_synonyms
- get :html_xml_or_rss, :format => "xhtml"
+ get :html_xml_or_rss, format: "xhtml"
assert_equal "HTML", @response.body
end
@@ -581,7 +581,7 @@ class RespondToControllerTest < ActionController::TestCase
get :using_defaults
assert_equal "using_defaults - #{[:html]}", @response.body
- get :using_defaults, :format => "xml"
+ get :using_defaults, format: "xml"
assert_equal "using_defaults - #{[:xml]}", @response.body
end
@@ -589,7 +589,7 @@ class RespondToControllerTest < ActionController::TestCase
get :iphone_with_html_response_type
assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
- get :iphone_with_html_response_type, :format => "iphone"
+ get :iphone_with_html_response_type, format: "iphone"
assert_equal "text/html", @response.content_type
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
end
@@ -603,7 +603,7 @@ class RespondToControllerTest < ActionController::TestCase
def test_invalid_format
assert_raises(ActionController::UnknownFormat) do
- get :using_defaults, :format => "invalidformat"
+ get :using_defaults, format: "invalidformat"
end
end
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index 246ba099af..710c428dcc 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -31,6 +31,15 @@ module BareMetalTest
controller.index
assert_equal ["Hello world"], controller.response_body
end
+
+ test "connect a request to controller instance without dispatch" do
+ env = {}
+ controller = BareController.new
+ controller.set_request! ActionDispatch::Request.new(env)
+ assert controller.request
+ assert controller.response
+ assert env['action_controller.instance']
+ end
end
class HeadController < ActionController::Metal
diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
index 5fd5946619..c8166280fc 100644
--- a/actionpack/test/controller/new_base/content_negotiation_test.rb
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -15,12 +15,12 @@ module ContentNegotiation
class TestContentNegotiation < Rack::TestCase
test "A */* Accept header will return HTML" do
- get "/content_negotiation/basic/hello", {}, "HTTP_ACCEPT" => "*/*"
+ get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "*/*" }
assert_body "Hello world */*!"
end
test "Not all mimes are converted to symbol" do
- get "/content_negotiation/basic/all", {}, "HTTP_ACCEPT" => "text/plain, mime/another"
+ get "/content_negotiation/basic/all", headers: { "HTTP_ACCEPT" => "text/plain, mime/another" }
assert_body '[:text, "mime/another"]'
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 9b57641e75..88453988dd 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -76,7 +76,7 @@ module ContentType
end
test "sets Content-Type as application/xml when rendering *.xml.erb" do
- get "/content_type/implied/i_am_xml_erb", "format" => "xml"
+ get "/content_type/implied/i_am_xml_erb", params: { "format" => "xml" }
assert_header "Content-Type", "application/xml; charset=utf-8"
end
@@ -88,7 +88,7 @@ module ContentType
end
test "sets Content-Type as application/xml when rendering *.xml.builder" do
- get "/content_type/implied/i_am_xml_builder", "format" => "xml"
+ get "/content_type/implied/i_am_xml_builder", params: { "format" => "xml" }
assert_header "Content-Type", "application/xml; charset=utf-8"
end
diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb
index 475bf9d3c9..3bf1dd0ede 100644
--- a/actionpack/test/controller/new_base/render_action_test.rb
+++ b/actionpack/test/controller/new_base/render_action_test.rb
@@ -88,7 +88,7 @@ module RenderAction
test "rendering with layout => true" do
assert_raise(ArgumentError) do
- get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false
+ get "/render_action/basic/hello_world_with_layout", headers: { "action_dispatch.show_exceptions" => false }
end
end
@@ -108,7 +108,7 @@ module RenderAction
test "rendering with layout => 'greetings'" do
assert_raise(ActionView::MissingTemplate) do
- get "/render_action/basic/hello_world_with_custom_layout", {}, "action_dispatch.show_exceptions" => false
+ get "/render_action/basic/hello_world_with_custom_layout", headers: { "action_dispatch.show_exceptions" => false }
end
end
end
diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb
index 4ac40ca405..7ab3777026 100644
--- a/actionpack/test/controller/new_base/render_layout_test.rb
+++ b/actionpack/test/controller/new_base/render_layout_test.rb
@@ -86,7 +86,7 @@ module ControllerLayouts
XML_INSTRUCT = %Q(<?xml version="1.0" encoding="UTF-8"?>\n)
test "if XML is selected, an HTML template is not also selected" do
- get :index, :format => "xml"
+ get :index, params: { format: "xml" }
assert_response XML_INSTRUCT
end
@@ -96,7 +96,7 @@ module ControllerLayouts
end
test "a layout for JS is ignored even if explicitly provided for HTML" do
- get :explicit, { :format => "js" }
+ get :explicit, params: { format: "js" }
assert_response "alert('foo');"
end
end
@@ -120,7 +120,7 @@ module ControllerLayouts
testing ControllerLayouts::FalseLayoutMethodController
test "access false layout returned by a method/proc" do
- get :index, :format => "js"
+ get :index, params: { format: "js" }
assert_response "alert('foo');"
end
end
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index 4c9126ca8c..9ea056194a 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -97,7 +97,7 @@ module RenderStreaming
end
test "do not stream on HTTP/1.0" do
- get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0"
+ get "/render_streaming/basic/hello_world", headers: { "HTTP_VERSION" => "HTTP/1.0" }
assert_body "Hello world, I'm here!"
assert_status 200
assert_equal "22", headers["Content-Length"]
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index 42a86b1d0d..19fef718e7 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -111,7 +111,7 @@ module RenderTemplate
end
test "rendering a builder template" do
- get :builder_template, "format" => "xml"
+ get :builder_template, params: { "format" => "xml" }
assert_response "<html>\n <p>Hello</p>\n</html>\n"
end
@@ -126,7 +126,7 @@ module RenderTemplate
assert_body "Hello <strong>this is also raw</strong> in an html template"
assert_status 200
- get :with_implicit_raw, format: 'text'
+ get :with_implicit_raw, params: { format: 'text' }
assert_body "Hello <strong>this is also raw</strong> in a text template"
assert_status 200
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 5635e16234..11a19ab783 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -74,7 +74,7 @@ module Render
end
assert_raises(AbstractController::DoubleRenderError) do
- get "/render/double_render", {}, "action_dispatch.show_exceptions" => false
+ get "/render/double_render", headers: { "action_dispatch.show_exceptions" => false }
end
end
end
@@ -84,13 +84,13 @@ module Render
# Only public methods on actual controllers are callable actions
test "raises an exception when a method of Object is called" do
assert_raises(AbstractController::ActionNotFound) do
- get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false
+ get "/render/blank_render/clone", headers: { "action_dispatch.show_exceptions" => false }
end
end
test "raises an exception when a private method is called" do
assert_raises(AbstractController::ActionNotFound) do
- get "/render/blank_render/secretz", {}, "action_dispatch.show_exceptions" => false
+ get "/render/blank_render/secretz", headers: { "action_dispatch.show_exceptions" => false }
end
end
end
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 645ecae220..8bf016d060 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -40,7 +40,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_filtered_parameters
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_equal @request.filtered_parameters, { 'controller' => 'params_wrapper_test/users', 'action' => 'parse', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' } }
end
end
@@ -48,7 +48,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_derived_name_from_controller
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -58,7 +58,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :person
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -68,7 +68,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters Person
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -78,7 +78,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :include => :username
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -88,7 +88,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :exclude => :title
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -98,7 +98,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :person, :include => :username
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -106,7 +106,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_not_enabled_format
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/xml'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
end
end
@@ -115,7 +115,7 @@ class ParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
UsersController.wrap_parameters false
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
end
end
@@ -125,7 +125,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :format => :xml
@request.env['CONTENT_TYPE'] = 'application/xml'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
end
end
@@ -133,7 +133,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_not_wrap_reserved_parameters
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu' }
+ post :parse, params: { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu' }
assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -141,7 +141,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_no_double_wrap_if_key_exists
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'user' => { 'username' => 'sikachu' }}
+ post :parse, params: { 'user' => { 'username' => 'sikachu' }}
assert_parameters({ 'user' => { 'username' => 'sikachu' }})
end
end
@@ -149,7 +149,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_nested_params
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'person' => { 'username' => 'sikachu' }}
+ post :parse, params: { 'person' => { 'username' => 'sikachu' }}
assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}})
end
end
@@ -160,7 +160,7 @@ class ParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -173,7 +173,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters Person
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -184,7 +184,7 @@ class ParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
end
end
@@ -192,7 +192,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_preserves_query_string_params
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- get :parse, { 'user' => { 'username' => 'nixon' } }
+ get :parse, params: { 'user' => { 'username' => 'nixon' } }
assert_parameters(
{'user' => { 'username' => 'nixon' } }
)
@@ -202,7 +202,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_empty_parameter_set
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, {}
+ post :parse, params: {}
assert_parameters(
{'user' => { } }
)
@@ -249,7 +249,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
def test_derived_name_from_controller
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -259,7 +259,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
begin
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
ensure
@@ -272,7 +272,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
begin
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }})
end
ensure
@@ -299,7 +299,7 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase
def test_does_not_implicitly_wrap_params
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu' })
end
end
@@ -308,7 +308,7 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@controller.class.wrap_parameters(:name => "guest")
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'guest' => { 'username' => 'sikachu' }})
end
end
@@ -344,7 +344,7 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' }
+ post :parse, params: { 'username' => 'sikachu', 'test_attr' => 'test_value' }
assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }})
end
end
diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb
index f46249d712..7136fafae5 100644
--- a/actionpack/test/controller/permitted_params_test.rb
+++ b/actionpack/test/controller/permitted_params_test.rb
@@ -14,12 +14,12 @@ class ActionControllerPermittedParamsTest < ActionController::TestCase
tests PeopleController
test "parameters are forbidden" do
- post :create, { person: { name: "Mjallo!" } }
+ post :create, params: { person: { name: "Mjallo!" } }
assert_equal "forbidden", response.body
end
test "parameters can be permitted and are then not forbidden" do
- post :create_with_permit, { person: { name: "Mjallo!" } }
+ post :create_with_permit, params: { person: { name: "Mjallo!" } }
assert_equal "permitted", response.body
end
end
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
index d550422a2f..6b661de064 100644
--- a/actionpack/test/controller/render_js_test.rb
+++ b/actionpack/test/controller/render_js_test.rb
@@ -22,13 +22,13 @@ class RenderJSTest < ActionController::TestCase
tests TestController
def test_render_vanilla_js
- xhr :get, :render_vanilla_js_hello
+ get :render_vanilla_js_hello, xhr: true
assert_equal "alert('hello')", @response.body
assert_equal "text/javascript", @response.content_type
end
def test_should_render_js_partial
- xhr :get, :show_partial, :format => 'js'
+ get :show_partial, format: 'js', xhr: true
assert_equal 'partial js', @response.body
end
end
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index ada978aa11..b1ad16bc55 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -100,13 +100,13 @@ class RenderJsonTest < ActionController::TestCase
end
def test_render_json_with_callback
- xhr :get, :render_json_hello_world_with_callback
+ get :render_json_hello_world_with_callback, xhr: true
assert_equal '/**/alert({"hello":"world"})', @response.body
assert_equal 'text/javascript', @response.content_type
end
def test_render_json_with_custom_content_type
- xhr :get, :render_json_with_custom_content_type
+ get :render_json_with_custom_content_type, xhr: true
assert_equal '{"hello":"world"}', @response.body
assert_equal 'text/javascript', @response.content_type
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 108a35f59f..488585c7a4 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -58,6 +58,27 @@ class TestController < ActionController::Base
end
end
+ class Collection
+ def initialize(records)
+ @records = records
+ end
+
+ def maximum(attribute)
+ @records.max_by(&attribute).public_send(attribute)
+ end
+ end
+
+ def conditional_hello_with_collection_of_records
+ ts = Time.now.utc.beginning_of_day
+
+ record = Struct.new(:updated_at, :cache_key).new(ts, "foo/123")
+ old_record = Struct.new(:updated_at, :cache_key).new(ts - 1.day, "bar/123")
+
+ if stale?(Collection.new([record, old_record]))
+ render action: 'hello_world'
+ end
+ end
+
def conditional_hello_with_expires_in
expires_in 60.1.seconds
render :action => 'hello_world'
@@ -288,7 +309,6 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_equal @last_modified, @response.headers['Last-Modified']
end
-
def test_responds_with_last_modified_with_record
get :conditional_hello_with_record
assert_equal @last_modified, @response.headers['Last-Modified']
@@ -299,6 +319,7 @@ class LastModifiedRenderTest < ActionController::TestCase
get :conditional_hello_with_record
assert_equal 304, @response.status.to_i
assert @response.body.blank?
+ assert_not_nil @response.etag
assert_equal @last_modified, @response.headers['Last-Modified']
end
@@ -317,6 +338,34 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_equal @last_modified, @response.headers['Last-Modified']
end
+ def test_responds_with_last_modified_with_collection_of_records
+ get :conditional_hello_with_collection_of_records
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_with_collection_of_records
+ @request.if_modified_since = @last_modified
+ get :conditional_hello_with_collection_of_records
+ assert_equal 304, @response.status.to_i
+ assert @response.body.blank?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_but_etag_differs_with_collection_of_records
+ @request.if_modified_since = @last_modified
+ @request.if_none_match = "234"
+ get :conditional_hello_with_collection_of_records
+ assert_response :success
+ end
+
+ def test_request_modified_with_collection_of_records
+ @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
+ get :conditional_hello_with_collection_of_records
+ assert_equal 200, @response.status.to_i
+ assert @response.body.present?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
def test_request_with_bang_gets_last_modified
get :conditional_hello_with_bangs
assert_equal @last_modified, @response.headers['Last-Modified']
@@ -462,21 +511,21 @@ class HeadRenderTest < ActionController::TestCase
end
def test_head_with_symbolic_status
- get :head_with_symbolic_status, :status => "ok"
+ get :head_with_symbolic_status, params: { status: "ok" }
assert_equal 200, @response.status
assert_response :ok
- get :head_with_symbolic_status, :status => "not_found"
+ get :head_with_symbolic_status, params: { status: "not_found" }
assert_equal 404, @response.status
assert_response :not_found
- get :head_with_symbolic_status, :status => "no_content"
+ get :head_with_symbolic_status, params: { status: "no_content" }
assert_equal 204, @response.status
assert !@response.headers.include?('Content-Length')
assert_response :no_content
Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code|
- get :head_with_symbolic_status, :status => status.to_s
+ get :head_with_symbolic_status, params: { status: status.to_s }
assert_equal code, @response.response_code
assert_response status
end
@@ -484,7 +533,7 @@ class HeadRenderTest < ActionController::TestCase
def test_head_with_integer_status
Rack::Utils::HTTP_STATUS_CODES.each do |code, message|
- get :head_with_integer_status, :status => code.to_s
+ get :head_with_integer_status, params: { status: code.to_s }
assert_equal message, @response.message
end
end
@@ -498,7 +547,7 @@ class HeadRenderTest < ActionController::TestCase
end
def test_head_with_string_status
- get :head_with_string_status, :status => "404 Eat Dirt"
+ get :head_with_string_status, params: { status: "404 Eat Dirt" }
assert_equal 404, @response.response_code
assert_equal "Not Found", @response.message
assert_response :not_found
@@ -512,3 +561,56 @@ class HeadRenderTest < ActionController::TestCase
assert_response :forbidden
end
end
+
+class HttpCacheForeverTest < ActionController::TestCase
+ class HttpCacheForeverController < ActionController::Base
+ def cache_me_forever
+ http_cache_forever(public: params[:public], version: params[:version] || 'v1') do
+ render text: 'hello'
+ end
+ end
+ end
+
+ tests HttpCacheForeverController
+
+ def test_cache_with_public
+ get :cache_me_forever, params: {public: true}
+ assert_equal "max-age=#{100.years.to_i}, public", @response.headers["Cache-Control"]
+ assert_not_nil @response.etag
+ end
+
+ def test_cache_with_private
+ get :cache_me_forever
+ assert_equal "max-age=#{100.years.to_i}, private", @response.headers["Cache-Control"]
+ assert_not_nil @response.etag
+ assert_response :success
+ end
+
+ def test_cache_response_code_with_if_modified_since
+ get :cache_me_forever
+ assert_response :success
+ @request.if_modified_since = @response.headers['Last-Modified']
+ get :cache_me_forever
+ assert_response :not_modified
+ end
+
+ def test_cache_response_code_with_etag
+ get :cache_me_forever
+ assert_response :success
+ @request.if_modified_since = @response.headers['Last-Modified']
+ @request.if_none_match = @response.etag
+
+ get :cache_me_forever
+ assert_response :not_modified
+ @request.if_modified_since = @response.headers['Last-Modified']
+ @request.if_none_match = @response.etag
+
+ get :cache_me_forever, params: {version: 'v2'}
+ assert_response :success
+ @request.if_modified_since = @response.headers['Last-Modified']
+ @request.if_none_match = @response.etag
+
+ get :cache_me_forever, params: {version: 'v2'}
+ assert_response :not_modified
+ end
+end
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index 4f280c4bec..7a91577b17 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -81,7 +81,7 @@ class RenderXmlTest < ActionController::TestCase
end
def test_should_render_formatted_xml_erb_template
- get :formatted_xml_erb, :format => :xml
+ get :formatted_xml_erb, format: :xml
assert_equal '<test>passed formatted xml erb</test>', @response.body
end
@@ -91,7 +91,7 @@ class RenderXmlTest < ActionController::TestCase
end
def test_should_use_implicit_content_type
- get :implicit_content_type, :format => 'atom'
+ get :implicit_content_type, format: 'atom'
assert_equal Mime::ATOM, @response.content_type
end
end
diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
new file mode 100644
index 0000000000..b55a25430b
--- /dev/null
+++ b/actionpack/test/controller/renderer_test.rb
@@ -0,0 +1,103 @@
+require 'abstract_unit'
+
+class RendererTest < ActiveSupport::TestCase
+ test 'creating with a controller' do
+ controller = CommentsController
+ renderer = ActionController::Renderer.for controller
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'creating from a controller' do
+ controller = AccountsController
+ renderer = controller.renderer
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'rendering with a class renderer' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'ruby_template'
+
+ assert_equal 'Hello from Ruby code', content
+ end
+
+ test 'rendering with an instance renderer' do
+ renderer = ApplicationController.renderer.new
+ content = renderer.render file: 'test/hello_world'
+
+ assert_equal 'Hello world!', content
+ end
+
+ test 'rendering with a controller class' do
+ assert_equal 'Hello world!', ApplicationController.render('test/hello_world')
+ end
+
+ test 'rendering with locals' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_locals',
+ locals: { secret: 'bar' }
+
+ assert_equal "The secret is bar\n", content
+ end
+
+ test 'rendering with assigns' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_ivar',
+ assigns: { secret: 'foo' }
+
+ assert_equal "The secret is foo\n", content
+ end
+
+ test 'rendering with custom env' do
+ renderer = ApplicationController.renderer.new method: 'post'
+ content = renderer.render inline: '<%= request.post? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'rendering with defaults' do
+ renderer = ApplicationController.renderer
+ renderer.defaults[:https] = true
+ content = renderer.render inline: '<%= request.ssl? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'same defaults from the same controller' do
+ renderer_defaults = ->(controller) { controller.renderer.defaults }
+
+ assert renderer_defaults[AccountsController].equal? renderer_defaults[AccountsController]
+ assert_not renderer_defaults[AccountsController].equal? renderer_defaults[CommentsController]
+ end
+
+ test 'rendering with different formats' do
+ html = 'Hello world!'
+ xml = "<p>Hello world!</p>\n"
+
+ assert_equal html, render['respond_to/using_defaults']
+ assert_equal xml, render['respond_to/using_defaults.xml.builder']
+ assert_equal xml, render['respond_to/using_defaults', formats: :xml]
+ end
+
+ test 'rendering with helpers' do
+ assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>']
+ end
+
+ test 'rendering from inherited renderer' do
+ inherited = Class.new ApplicationController.renderer do
+ defaults[:script_name] = 'script'
+ def render(options)
+ super options.merge(locals: { param: :value })
+ end
+ end
+
+ template = '<%= url_for controller: :foo, action: :bar, param: param %>'
+ assert_equal 'script/foo/bar?param=value', inherited.render(inline: template)
+ end
+
+ private
+ def render
+ @render ||= ApplicationController.renderer.method(:render)
+ end
+end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index ea2d35c3f8..8887f291cf 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -262,23 +262,23 @@ module RequestForgeryProtectionTests
end
def test_should_not_allow_xhr_post_without_token
- assert_blocked { xhr :post, :index }
+ assert_blocked { post :index, xhr: true }
end
def test_should_allow_post_with_token
- assert_not_blocked { post :index, :custom_authenticity_token => @token }
+ assert_not_blocked { post :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_patch_with_token
- assert_not_blocked { patch :index, :custom_authenticity_token => @token }
+ assert_not_blocked { patch :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_put_with_token
- assert_not_blocked { put :index, :custom_authenticity_token => @token }
+ assert_not_blocked { put :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_delete_with_token
- assert_not_blocked { delete :index, :custom_authenticity_token => @token }
+ assert_not_blocked { delete :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_post_with_token_in_header
@@ -340,21 +340,21 @@ module RequestForgeryProtectionTests
get :negotiate_same_origin
end
- assert_cross_origin_not_blocked { xhr :get, :same_origin_js }
- assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked { get :same_origin_js, xhr: true }
+ assert_cross_origin_not_blocked { get :same_origin_js, xhr: true, format: 'js'}
assert_cross_origin_not_blocked do
@request.accept = 'text/javascript'
- xhr :get, :negotiate_same_origin
+ get :negotiate_same_origin, xhr: true
end
end
# Allow non-GET requests since GET is all a remote <script> tag can muster.
def test_should_allow_non_get_js_without_xhr_header
- assert_cross_origin_not_blocked { post :same_origin_js, custom_authenticity_token: @token }
- assert_cross_origin_not_blocked { post :same_origin_js, format: 'js', custom_authenticity_token: @token }
+ assert_cross_origin_not_blocked { post :same_origin_js, params: { custom_authenticity_token: @token } }
+ assert_cross_origin_not_blocked { post :same_origin_js, params: { format: 'js', custom_authenticity_token: @token } }
assert_cross_origin_not_blocked do
@request.accept = 'text/javascript'
- post :negotiate_same_origin, custom_authenticity_token: @token
+ post :negotiate_same_origin, params: { custom_authenticity_token: @token}
end
end
@@ -366,11 +366,18 @@ module RequestForgeryProtectionTests
get :negotiate_cross_origin
end
- assert_cross_origin_not_blocked { xhr :get, :cross_origin_js }
- assert_cross_origin_not_blocked { xhr :get, :cross_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true }
+ assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true, format: 'js' }
assert_cross_origin_not_blocked do
@request.accept = 'text/javascript'
- xhr :get, :negotiate_cross_origin
+ get :negotiate_cross_origin, xhr: true
+ end
+ end
+
+ def test_should_not_raise_error_if_token_is_not_a_string
+ @controller.unstub(:valid_authenticity_token?)
+ assert_blocked do
+ patch :index, params: { custom_authenticity_token: { foo: 'bar' } }
end
end
@@ -543,7 +550,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase
@controller.stubs(:valid_authenticity_token?).returns(:true)
begin
- post :index, :custom_token_name => 'foobar'
+ post :index, params: { custom_token_name: 'foobar' }
assert_equal 0, @logger.logged(:warn).size
ensure
ActionController::Base.logger = @old_logger
@@ -554,7 +561,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase
ActionController::Base.logger = @logger
begin
- post :index, :custom_token_name => 'bazqux'
+ post :index, params: { custom_token_name: 'bazqux' }
assert_equal 1, @logger.logged(:warn).size
ensure
ActionController::Base.logger = @old_logger
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index 6803dbbb62..a901e56332 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -12,21 +12,21 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase
test "missing required parameters will raise exception" do
assert_raise ActionController::ParameterMissing do
- post :create, { magazine: { name: "Mjallo!" } }
+ post :create, params: { magazine: { name: "Mjallo!" } }
end
assert_raise ActionController::ParameterMissing do
- post :create, { book: { title: "Mjallo!" } }
+ post :create, params: { book: { title: "Mjallo!" } }
end
end
test "required parameters that are present will not raise" do
- post :create, { book: { name: "Mjallo!" } }
+ post :create, params: { book: { name: "Mjallo!" } }
assert_response :ok
end
test "required parameters with false value will not raise" do
- post :create, { book: { name: false } }
+ post :create, params: { book: { name: false } }
assert_response :ok
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 0e15883f43..f3da2df3ef 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -1047,6 +1047,28 @@ class ResourcesTest < ActionController::TestCase
end
end
+ def test_assert_routing_accepts_all_as_a_valid_method
+ with_routing do |set|
+ set.draw do
+ match "/products", to: "products#show", via: :all
+ end
+
+ assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" })
+ end
+ end
+
+ def test_assert_routing_fails_when_not_all_http_methods_are_recognized
+ with_routing do |set|
+ set.draw do
+ match "/products", to: "products#show", via: [:get, :post, :put]
+ end
+
+ assert_raises(Minitest::Assertion) do
+ assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" })
+ end
+ end
+ end
+
def test_singleton_resource_name_is_not_singularized
with_singleton_resources(:preferences) do
assert_singleton_restful_for :preferences
@@ -1184,10 +1206,10 @@ class ResourcesTest < ActionController::TestCase
end
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
- @controller.singleton_class.send(:include, @routes.url_helpers)
+ @controller.singleton_class.include(@routes.url_helpers)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
- get :index, options[:options]
+ get :index, params: options[:options]
options[:options].delete :action
path = "#{options[:as] || controller_name}"
@@ -1254,10 +1276,10 @@ class ResourcesTest < ActionController::TestCase
def assert_singleton_named_routes_for(singleton_name, options = {})
(options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
- @controller.singleton_class.send(:include, @routes.url_helpers)
+ @controller.singleton_class.include(@routes.url_helpers)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
- get :show, options[:options]
+ get :show, params: options[:options]
options[:options].delete :action
full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 9caa5cbe57..2d08987ca6 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/core_ext/object/with_options'
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index c002cf4d8f..36c57ec9b2 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
module TestFileUtils
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index f7eba1ef43..fba5ebba15 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -58,13 +58,13 @@ module ShowExceptions
class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest
test 'show error page' do
@app = ShowExceptionsOverriddenController.action(:boom)
- get '/', {'detailed' => '0'}
+ get '/', params: { 'detailed' => '0' }
assert_equal "500 error fixture\n", body
end
test 'show diagnostics message' do
@app = ShowExceptionsOverriddenController.action(:boom)
- get '/', {'detailed' => '1'}
+ get '/', params: { 'detailed' => '1' }
assert_match(/boom/, body)
end
end
@@ -72,7 +72,7 @@ module ShowExceptions
class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
def test_render_json_exception
@app = ShowExceptionsOverriddenController.action(:boom)
- get "/", {}, 'HTTP_ACCEPT' => 'application/json'
+ get "/", headers: { 'HTTP_ACCEPT' => 'application/json' }
assert_response :internal_server_error
assert_equal 'application/json', response.content_type.to_s
assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_json, response.body)
@@ -80,7 +80,7 @@ module ShowExceptions
def test_render_xml_exception
@app = ShowExceptionsOverriddenController.action(:boom)
- get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
+ get "/", headers: { 'HTTP_ACCEPT' => 'application/xml' }
assert_response :internal_server_error
assert_equal 'application/xml', response.content_type.to_s
assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_xml, response.body)
@@ -88,7 +88,7 @@ module ShowExceptions
def test_render_fallback_exception
@app = ShowExceptionsOverriddenController.action(:boom)
- get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
+ get "/", headers: { 'HTTP_ACCEPT' => 'text/csv' }
assert_response :internal_server_error
assert_equal 'text/html', response.content_type.to_s
end
@@ -101,7 +101,7 @@ module ShowExceptions
@app.instance_variable_set(:@exceptions_app, nil)
$stderr = StringIO.new
- get '/', {}, 'HTTP_ACCEPT' => 'text/json'
+ get '/', headers: { 'HTTP_ACCEPT' => 'text/json' }
assert_response :internal_server_error
assert_equal 'text/plain', response.content_type.to_s
ensure
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 2e1f21c645..ca854040b7 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -6,57 +6,62 @@ require 'rails/engine'
class TestCaseTest < ActionController::TestCase
class TestController < ActionController::Base
def no_op
- render :text => 'dummy'
+ render text: 'dummy'
end
def set_flash
flash["test"] = ">#{flash["test"]}<"
+ render text: 'ignore me'
+ end
+
+ def delete_flash
+ flash.delete("test")
render :text => 'ignore me'
end
def set_flash_now
flash.now["test_now"] = ">#{flash["test_now"]}<"
- render :text => 'ignore me'
+ render text: 'ignore me'
end
def set_session
session['string'] = 'A wonder'
session[:symbol] = 'it works'
- render :text => 'Success'
+ render text: 'Success'
end
def reset_the_session
reset_session
- render :text => 'ignore me'
+ render text: 'ignore me'
end
def render_raw_post
raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank?
- render :text => request.raw_post
+ render text: request.raw_post
end
def render_body
- render :text => request.body.read
+ render text: request.body.read
end
def test_params
- render :text => params.inspect
+ render text: params.inspect
end
def test_uri
- render :text => request.fullpath
+ render text: request.fullpath
end
def test_format
- render :text => request.format
+ render text: request.format
end
def test_query_string
- render :text => request.query_string
+ render text: request.query_string
end
def test_protocol
- render :text => request.protocol
+ render text: request.protocol
end
def test_headers
@@ -64,7 +69,7 @@ class TestCaseTest < ActionController::TestCase
end
def test_html_output
- render :text => <<HTML
+ render text: <<HTML
<html>
<body>
<a href="/"><img src="/images/button.png" /></a>
@@ -86,7 +91,7 @@ HTML
def test_xml_output
response.content_type = "application/xml"
- render :text => <<XML
+ render text: <<XML
<?xml version="1.0" encoding="UTF-8"?>
<root>
<area>area is an empty tag in HTML, raising an error if not in xml mode</area>
@@ -95,15 +100,15 @@ XML
end
def test_only_one_param
- render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
+ render text: (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
end
def test_remote_addr
- render :text => (request.remote_addr || "not specified")
+ render text: (request.remote_addr || "not specified")
end
def test_file_upload
- render :text => params[:file].size
+ render text: params[:file].size
end
def test_send_file
@@ -111,26 +116,26 @@ XML
end
def redirect_to_same_controller
- redirect_to :controller => 'test', :action => 'test_uri', :id => 5
+ redirect_to controller: 'test', action: 'test_uri', id: 5
end
def redirect_to_different_controller
- redirect_to :controller => 'fail', :id => 5
+ redirect_to controller: 'fail', id: 5
end
def create
- head :created, :location => 'created resource'
+ head :created, location: 'created resource'
end
def delete_cookie
cookies.delete("foo")
- render :nothing => true
+ render nothing: true
end
def test_assigns
@foo = "foo"
- @foo_hash = {:foo => :bar}
- render :nothing => true
+ @foo_hash = { foo: :bar }
+ render nothing: true
end
def test_without_body
@@ -144,7 +149,7 @@ XML
private
def generate_url(opts)
- url_for(opts.merge(:action => "test_uri"))
+ url_for(opts.merge(action: "test_uri"))
end
end
@@ -164,7 +169,7 @@ XML
class ViewAssignsController < ActionController::Base
def test_assigns
@foo = "foo"
- render :nothing => true
+ render nothing: true
end
def view_assigns
@@ -209,32 +214,50 @@ XML
end
def test_raw_post_handling
- params = Hash[:page, {:name => 'page name'}, 'some key', 123]
- post :render_raw_post, params.dup
+ params = Hash[:page, { name: 'page name' }, 'some key', 123]
+ post :render_raw_post, params: params.dup
assert_equal params.to_query, @response.body
end
def test_body_stream
- params = Hash[:page, { :name => 'page name' }, 'some key', 123]
+ params = Hash[:page, { name: 'page name' }, 'some key', 123]
+
+ post :render_body, params: params.dup
+
+ assert_equal params.to_query, @response.body
+ end
- post :render_body, params.dup
+ def test_deprecated_body_stream
+ params = Hash[:page, { name: 'page name' }, 'some key', 123]
+
+ assert_deprecated { post :render_body, params.dup }
assert_equal params.to_query, @response.body
end
def test_document_body_and_params_with_post
- post :test_params, :id => 1
- assert_equal("{\"id\"=>\"1\", \"controller\"=>\"test_case_test/test\", \"action\"=>\"test_params\"}", @response.body)
+ post :test_params, params: { id: 1 }
+ assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body)
end
def test_document_body_with_post
- post :render_body, "document body"
+ post :render_body, body: "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_deprecated_document_body_with_post
+ assert_deprecated { post :render_body, "document body" }
assert_equal "document body", @response.body
end
def test_document_body_with_put
- put :render_body, "document body"
+ put :render_body, body: "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_deprecated_document_body_with_put
+ assert_deprecated { put :render_body, "document body" }
assert_equal "document body", @response.body
end
@@ -243,25 +266,42 @@ XML
assert_equal 200, @response.status
end
- def test_head_params_as_string
- assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
- end
-
def test_process_without_flash
process :set_flash
assert_equal '><', flash['test']
end
+ def test_deprecated_process_with_flash
+ assert_deprecated { process :set_flash, "GET", nil, nil, { "test" => "value" } }
+ assert_equal '>value<', flash['test']
+ end
+
def test_process_with_flash
- process :set_flash, "GET", nil, nil, { "test" => "value" }
+ process :set_flash,
+ method: "GET",
+ flash: { "test" => "value" }
assert_equal '>value<', flash['test']
end
+ def test_deprecated_process_with_flash_now
+ assert_deprecated { process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" } }
+ assert_equal '>value_now<', flash['test_now']
+ end
+
def test_process_with_flash_now
- process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" }
+ process :set_flash_now,
+ method: "GET",
+ flash: { "test_now" => "value_now" }
assert_equal '>value_now<', flash['test_now']
end
+ def test_process_delete_flash
+ process :set_flash
+ process :delete_flash
+ assert_empty flash
+ assert_empty session
+ end
+
def test_process_with_session
process :set_session
assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key"
@@ -271,22 +311,48 @@ XML
end
def test_process_with_session_arg
- process :no_op, "GET", nil, { 'string' => 'value1', :symbol => 'value2' }
+ assert_deprecated { process :no_op, "GET", nil, { 'string' => 'value1', symbol: 'value2' } }
+ assert_equal 'value1', session['string']
+ assert_equal 'value1', session[:string]
+ assert_equal 'value2', session['symbol']
+ assert_equal 'value2', session[:symbol]
+ end
+
+ def test_process_with_session_kwarg
+ process :no_op, method: "GET", session: { 'string' => 'value1', symbol: 'value2' }
assert_equal 'value1', session['string']
assert_equal 'value1', session[:string]
assert_equal 'value2', session['symbol']
assert_equal 'value2', session[:symbol]
end
+ def test_deprecated_process_merges_session_arg
+ session[:foo] = 'bar'
+ assert_deprecated {
+ get :no_op, nil, { bar: 'baz' }
+ }
+ assert_equal 'bar', session[:foo]
+ assert_equal 'baz', session[:bar]
+ end
+
def test_process_merges_session_arg
session[:foo] = 'bar'
- get :no_op, nil, { :bar => 'baz' }
+ get :no_op, session: { bar: 'baz' }
assert_equal 'bar', session[:foo]
assert_equal 'baz', session[:bar]
end
+ def test_deprecated_merged_session_arg_is_retained_across_requests
+ assert_deprecated {
+ get :no_op, nil, { foo: 'bar' }
+ }
+ assert_equal 'bar', session[:foo]
+ get :no_op
+ assert_equal 'bar', session[:foo]
+ end
+
def test_merged_session_arg_is_retained_across_requests
- get :no_op, nil, { :foo => 'bar' }
+ get :no_op, session: { foo: 'bar' }
assert_equal 'bar', session[:foo]
get :no_op
assert_equal 'bar', session[:foo]
@@ -294,7 +360,7 @@ XML
def test_process_overwrites_existing_session_arg
session[:foo] = 'bar'
- get :no_op, nil, { :foo => 'baz' }
+ get :no_op, session: { foo: 'baz' }
assert_equal 'baz', session[:foo]
end
@@ -321,19 +387,40 @@ XML
assert_equal "/test_case_test/test/test_uri", @response.body
end
+ def test_process_with_symbol_method
+ process :test_uri, method: :get
+ assert_equal "/test_case_test/test/test_uri", @response.body
+ end
+
+ def test_deprecated_process_with_request_uri_with_params
+ assert_deprecated { process :test_uri, "GET", id: 7 }
+ assert_equal "/test_case_test/test/test_uri/7", @response.body
+ end
+
def test_process_with_request_uri_with_params
- process :test_uri, "GET", :id => 7
+ process :test_uri,
+ method: "GET",
+ params: { id: 7 }
+
assert_equal "/test_case_test/test/test_uri/7", @response.body
end
+ def test_deprecated_process_with_request_uri_with_params_with_explicit_uri
+ @request.env['PATH_INFO'] = "/explicit/uri"
+ assert_deprecated { process :test_uri, "GET", id: 7 }
+ assert_equal "/explicit/uri", @response.body
+ end
+
def test_process_with_request_uri_with_params_with_explicit_uri
@request.env['PATH_INFO'] = "/explicit/uri"
- process :test_uri, "GET", :id => 7
+ process :test_uri, method: "GET", params: { id: 7 }
assert_equal "/explicit/uri", @response.body
end
def test_process_with_query_string
- process :test_query_string, "GET", :q => 'test'
+ process :test_query_string,
+ method: "GET",
+ params: { q: 'test' }
assert_equal "q=test", @response.body
end
@@ -345,9 +432,9 @@ XML
end
def test_multiple_calls
- process :test_only_one_param, "GET", :left => true
+ process :test_only_one_param, method: "GET", params: { left: true }
assert_equal "OK", @response.body
- process :test_only_one_param, "GET", :right => true
+ process :test_only_one_param, method: "GET", params: { right: true }
assert_equal "OK", @response.body
end
@@ -362,7 +449,7 @@ XML
assert_equal "foo", assigns["foo"]
# but the assigned variable should not have its own keys stringified
- expected_hash = { :foo => :bar }
+ expected_hash = { foo: :bar }
assert_equal expected_hash, assigns(:foo_hash)
end
@@ -390,21 +477,21 @@ XML
end
def test_assert_generates
- assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5'
- assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action"}
- assert_generates 'controller/action/5', {:controller => "controller", :action => "action", :id => "5", :name => "bob"}, {}, {:name => "bob"}
- assert_generates 'controller/action/7', {:id => "7", :name => "bob"}, {:controller => "controller", :action => "action"}, {:name => "bob"}
- assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action", :name => "bob"}, {}
+ assert_generates 'controller/action/5', controller: 'controller', action: 'action', id: '5'
+ assert_generates 'controller/action/7', { id: "7" }, { controller: "controller", action: "action" }
+ assert_generates 'controller/action/5', { controller: "controller", action: "action", id: "5", name: "bob" }, {}, { name: "bob" }
+ assert_generates 'controller/action/7', { id: "7", name: "bob" }, { controller: "controller", action: "action" }, { name: "bob" }
+ assert_generates 'controller/action/7', { id: "7" }, { controller: "controller", action: "action", name: "bob" }, {}
end
def test_assert_routing
- assert_routing 'content', :controller => 'content', :action => 'index'
+ assert_routing 'content', controller: 'content', action: 'index'
end
def test_assert_routing_with_method
with_routing do |set|
set.draw { resources(:content) }
- assert_routing({ :method => 'post', :path => 'content' }, { :controller => 'content', :action => 'create' })
+ assert_routing({ method: 'post', path: 'content' }, { controller: 'content', action: 'create' })
end
end
@@ -416,29 +503,75 @@ XML
end
end
- assert_routing 'admin/user', :controller => 'admin/user', :action => 'index'
+ assert_routing 'admin/user', controller: 'admin/user', action: 'index'
end
end
def test_assert_routing_with_glob
with_routing do |set|
set.draw { get('*path' => "pages#show") }
- assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' })
+ assert_routing('/company/about', { controller: 'pages', action: 'show', path: 'company/about' })
end
end
+ def test_deprecated_params_passing
+ assert_deprecated {
+ get :test_params, page: { name: "Page name", month: '4', year: '2004', day: '6' }
+ }
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {
+ 'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'page' => { 'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6' }
+ },
+ parsed_params
+ )
+ end
+
def test_params_passing
- get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
+ get :test_params, params: {
+ page: {
+ name: "Page name",
+ month: '4',
+ year: '2004',
+ day: '6'
+ }
+ }
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {
+ 'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'page' => { 'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6' }
+ },
+ parsed_params
+ )
+ end
+
+ def test_kwarg_params_passing_with_session_and_flash
+ get :test_params, params: {
+ page: {
+ name: "Page name",
+ month: '4',
+ year: '2004',
+ day: '6'
+ }
+ }, session: { 'foo' => 'bar' }, flash: { notice: 'created' }
+
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
parsed_params
)
+
+ assert_equal 'bar', session[:foo]
+ assert_equal 'created', flash[:notice]
end
def test_params_passing_with_fixnums
- get :test_params, :page => {:name => "Page name", :month => 4, :year => 2004, :day => 6}
+ get :test_params, params: {
+ page: { name: "Page name", month: 4, year: 2004, day: 6 }
+ }
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -448,7 +581,7 @@ XML
end
def test_params_passing_with_fixnums_when_not_html_request
- get :test_params, :format => 'json', :count => 999
+ get :test_params, params: { format: 'json', count: 999 }
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -458,7 +591,17 @@ XML
end
def test_params_passing_path_parameter_is_string_when_not_html_request
- get :test_params, :format => 'json', :id => 1
+ get :test_params, params: { format: 'json', id: 1 }
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'id' => '1' },
+ parsed_params
+ )
+ end
+
+ def test_deprecated_params_passing_path_parameter_is_string_when_not_html_request
+ assert_deprecated { get :test_params, format: 'json', id: 1 }
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -469,7 +612,9 @@ XML
def test_params_passing_with_frozen_values
assert_nothing_raised do
- get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze, :deepfreeze => { :frozen => 'icy'.freeze }.freeze
+ get :test_params, params: {
+ frozen: 'icy'.freeze, frozens: ['icy'.freeze].freeze, deepfreeze: { frozen: 'icy'.freeze }.freeze
+ }
end
parsed_params = eval(@response.body)
assert_equal(
@@ -480,8 +625,8 @@ XML
end
def test_params_passing_doesnt_modify_in_place
- page = {:name => "Page name", :month => 4, :year => 2004, :day => 6}
- get :test_params, :page => page
+ page = { name: "Page name", month: 4, year: 2004, day: 6 }
+ get :test_params, params: { page: page }
assert_equal 2004, page[:year]
end
@@ -504,25 +649,32 @@ XML
end
def test_id_converted_to_string
- get :test_params, :id => 20, :foo => Object.new
+ get :test_params, params: {
+ id: 20, foo: Object.new
+ }
+ assert_kind_of String, @request.path_parameters[:id]
+ end
+
+ def test_deprecared_id_converted_to_string
+ assert_deprecated { get :test_params, id: 20, foo: Object.new}
assert_kind_of String, @request.path_parameters[:id]
end
def test_array_path_parameter_handled_properly
with_routing do |set|
set.draw do
- get 'file/*path', :to => 'test_case_test/test#test_params'
+ get 'file/*path', to: 'test_case_test/test#test_params'
get ':controller/:action'
end
- get :test_params, :path => ['hello', 'world']
+ get :test_params, params: { path: ['hello', 'world'] }
assert_equal ['hello', 'world'], @request.path_parameters[:path]
assert_equal 'hello/world', @request.path_parameters[:path].to_param
end
end
def test_assert_realistic_path_parameters
- get :test_params, :id => 20, :foo => Object.new
+ get :test_params, params: { id: 20, foo: Object.new }
# All elements of path_parameters should use Symbol keys
@request.path_parameters.each_key do |key|
@@ -554,19 +706,57 @@ XML
end
def test_header_properly_reset_after_remote_http_request
- xhr :get, :test_params
+ get :test_params, xhr: true
assert_nil @request.env['HTTP_X_REQUESTED_WITH']
assert_nil @request.env['HTTP_ACCEPT']
end
+ def test_deprecated_xhr_with_params
+ assert_deprecated { xhr :get, :test_params, params: { id: 1 } }
+
+ assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body)
+ end
+
+ def test_xhr_with_params
+ get :test_params, params: { id: 1 }, xhr: true
+
+ assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body)
+ end
+
+ def test_xhr_with_session
+ get :set_session, xhr: true
+
+ assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key"
+ assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access"
+ end
+
+ def test_deprecated_xhr_with_session
+ assert_deprecated { xhr :get, :set_session }
+
+ assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key"
+ assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access"
+ end
+
def test_header_properly_reset_after_get_request
get :test_params
@request.recycle!
assert_nil @request.instance_variable_get("@request_method")
end
+ def test_deprecated_params_reset_between_post_requests
+ assert_deprecated { post :no_op, foo: "bar" }
+ assert_equal "bar", @request.params[:foo]
+
+ post :no_op
+ assert @request.params[:foo].blank?
+ end
+
def test_params_reset_between_post_requests
- post :no_op, :foo => "bar"
+ post :no_op, params: { foo: "bar" }
assert_equal "bar", @request.params[:foo]
post :no_op
@@ -574,15 +764,15 @@ XML
end
def test_filtered_parameters_reset_between_requests
- get :no_op, :foo => "bar"
+ get :no_op, params: { foo: "bar" }
assert_equal "bar", @request.filtered_parameters[:foo]
- get :no_op, :foo => "baz"
+ get :no_op, params: { foo: "baz" }
assert_equal "baz", @request.filtered_parameters[:foo]
end
def test_path_params_reset_between_request
- get :test_params, :id => "foo"
+ get :test_params, params: { id: "foo" }
assert_equal "foo", @request.path_parameters[:id]
get :test_params
@@ -603,19 +793,38 @@ XML
end
def test_request_format
- get :test_format, :format => 'html'
+ get :test_format, params: { format: 'html' }
+ assert_equal 'text/html', @response.body
+
+ get :test_format, params: { format: 'json' }
+ assert_equal 'application/json', @response.body
+
+ get :test_format, params: { format: 'xml' }
+ assert_equal 'application/xml', @response.body
+
+ get :test_format
+ assert_equal 'text/html', @response.body
+ end
+
+ def test_request_format_kwarg
+ get :test_format, format: 'html'
assert_equal 'text/html', @response.body
- get :test_format, :format => 'json'
+ get :test_format, format: 'json'
assert_equal 'application/json', @response.body
- get :test_format, :format => 'xml'
+ get :test_format, format: 'xml'
assert_equal 'application/xml', @response.body
get :test_format
assert_equal 'text/html', @response.body
end
+ def test_request_format_kwarg_overrides_params
+ get :test_format, format: 'json', params: { format: 'html' }
+ assert_equal 'application/json', @response.body
+ end
+
def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
cookies['foo'] = 'bar'
get :no_op
@@ -700,7 +909,10 @@ XML
end
def test_fixture_file_upload
- post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ post :test_file_upload,
+ params: {
+ file: fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ }
assert_equal '159528', @response.body
end
@@ -716,10 +928,21 @@ XML
assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
end
+ def test_deprecated_action_dispatch_uploaded_file_upload
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ assert_deprecated {
+ post :test_file_upload, file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path))
+ }
+ assert_equal '159528', @response.body
+ end
+
def test_action_dispatch_uploaded_file_upload
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
- post :test_file_upload, :file => ActionDispatch::Http::UploadedFile.new(:filename => path, :type => "image/jpg", :tempfile => File.open(path))
+ post :test_file_upload, params: {
+ file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path))
+ }
assert_equal '159528', @response.body
end
@@ -753,7 +976,7 @@ module EngineControllerTests
class BarController < ActionController::Base
def index
- render :text => 'bar'
+ render text: 'bar'
end
end
@@ -830,7 +1053,7 @@ class NamedRoutesControllerTest < ActionController::TestCase
with_routing do |set|
set.draw { resources :contents }
assert_equal 'http://test.host/contents/new', new_content_url
- assert_equal 'http://test.host/contents/1', content_url(:id => 1)
+ assert_equal 'http://test.host/contents/1', content_url(id: 1)
end
end
end
@@ -839,7 +1062,7 @@ class AnonymousControllerTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def index
- render :text => params[:controller]
+ render text: params[:controller]
end
end.new
@@ -860,29 +1083,29 @@ class RoutingDefaultsTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def post
- render :text => request.fullpath
+ render text: request.fullpath
end
def project
- render :text => request.fullpath
+ render text: request.fullpath
end
end.new
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get '/posts/:id', :to => 'anonymous#post', :bucket_type => 'post'
- get '/projects/:id', :to => 'anonymous#project', :defaults => { :bucket_type => 'project' }
+ get '/posts/:id', to: 'anonymous#post', bucket_type: 'post'
+ get '/projects/:id', to: 'anonymous#project', defaults: { bucket_type: 'project' }
end
end
end
def test_route_option_can_be_passed_via_process
- get :post, :id => 1, :bucket_type => 'post'
+ get :post, params: { id: 1, bucket_type: 'post'}
assert_equal '/posts/1', @response.body
end
def test_route_default_is_not_required_for_building_request_uri
- get :project, :id => 2
+ get :project, params: { id: 2 }
assert_equal '/projects/2', @response.body
end
end
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index 24a09222b1..0e4c2b7c32 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/core_ext/object/with_options'
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 2b109ff19e..21fa670bb6 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -35,7 +35,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_post_json
with_test_route_set do
- post "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
+ post "/",
+ params: '{"entry":{"summary":"content..."}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'entry', @controller.response.body
assert @controller.params.has_key?(:entry)
@@ -45,7 +47,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_put_json
with_test_route_set do
- put "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
+ put "/",
+ params: '{"entry":{"summary":"content..."}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'entry', @controller.response.body
assert @controller.params.has_key?(:entry)
@@ -56,8 +60,9 @@ 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
- post "/", '{"request":{"summary":"content...","title":"JSON"}}',
- 'CONTENT_TYPE' => 'application/json'
+ post "/",
+ params: '{"request":{"summary":"content...","title":"JSON"}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'summary, title', @controller.response.body
assert @controller.params.has_key?(:summary)
@@ -70,14 +75,16 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_use_json_with_empty_request
with_test_route_set do
- assert_nothing_raised { post "/", "", 'CONTENT_TYPE' => 'application/json' }
+ assert_nothing_raised { post "/", headers: { 'CONTENT_TYPE' => 'application/json' } }
assert_equal '', @controller.response.body
end
end
def test_dasherized_keys_as_json
with_test_route_set do
- post "/?full=1", '{"first-key":{"sub-key":"..."}}', 'CONTENT_TYPE' => 'application/json'
+ post "/?full=1",
+ params: '{"first-key":{"sub-key":"..."}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'action, controller, first-key(sub-key), full', @controller.response.body
assert_equal "...", @controller.params['first-key']['sub-key']
end
@@ -87,7 +94,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest
with_test_route_set do
with_params_parsers Mime::JSON => Proc.new { |data| raise Interrupt } do
assert_raises(Interrupt) do
- post "/", '{"title":"JSON"}}', 'CONTENT_TYPE' => 'application/json'
+ post "/",
+ params: '{"title":"JSON"}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
end
end
end
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 1e5ed60b09..7921f05688 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -86,21 +86,21 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test 'skip diagnosis if not showing detailed exceptions' do
@app = ProductionApp
assert_raise RuntimeError do
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
end
end
test 'skip diagnosis if not showing exceptions' do
@app = DevelopmentApp
assert_raise RuntimeError do
- get "/", {}, {'action_dispatch.show_exceptions' => false}
+ get "/", headers: { 'action_dispatch.show_exceptions' => false }
end
end
test 'raise an exception on cascade pass' do
@app = ProductionApp
assert_raise ActionController::RoutingError do
- get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
end
end
@@ -108,14 +108,14 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
boomer = Boomer.new(false)
@app = ActionDispatch::DebugExceptions.new(boomer)
assert_raise ActionController::RoutingError do
- get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
end
assert boomer.closed, "Expected to close the response body"
end
test 'displays routes in a table when a RoutingError occurs' do
@app = DevelopmentApp
- get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
routing_table = body[/route_table.*<.table>/m]
assert_match '/:controller(/:action)(.:format)', routing_table
assert_match ':controller#:action', routing_table
@@ -125,7 +125,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test 'displays request and response info when a RoutingError occurs' do
@app = DevelopmentApp
- get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
assert_select 'h2', /Request/
assert_select 'h2', /Response/
@@ -134,27 +134,27 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "rescue with diagnostics message" do
@app = DevelopmentApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_match(/puke/, body)
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_match(/#{AbstractController::ActionNotFound.name}/, body)
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_match(/ActionController::MethodNotAllowed/, body)
- get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_match(/ActionController::UnknownHttpMethod/, body)
- get "/bad_request", {}, {'action_dispatch.show_exceptions' => true}
+ get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 400
assert_match(/ActionController::BadRequest/, body)
- get "/parameter_missing", {}, {'action_dispatch.show_exceptions' => true}
+ get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 400
assert_match(/ActionController::ParameterMissing/, body)
end
@@ -163,38 +163,38 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
@app = DevelopmentApp
xhr_request_env = {'action_dispatch.show_exceptions' => true, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'}
- get "/", {}, xhr_request_env
+ get "/", headers: xhr_request_env
assert_response 500
assert_no_match(/<header>/, body)
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/RuntimeError\npuke/, body)
- get "/not_found", {}, xhr_request_env
+ get "/not_found", headers: xhr_request_env
assert_response 404
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/#{AbstractController::ActionNotFound.name}/, body)
- get "/method_not_allowed", {}, xhr_request_env
+ get "/method_not_allowed", headers: xhr_request_env
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/ActionController::MethodNotAllowed/, body)
- get "/unknown_http_method", {}, xhr_request_env
+ get "/unknown_http_method", headers: xhr_request_env
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/ActionController::UnknownHttpMethod/, body)
- get "/bad_request", {}, xhr_request_env
+ get "/bad_request", headers: xhr_request_env
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/ActionController::BadRequest/, body)
- get "/parameter_missing", {}, xhr_request_env
+ get "/parameter_missing", headers: xhr_request_env
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
@@ -204,8 +204,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "does not show filtered parameters" do
@app = DevelopmentApp
- get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
- 'action_dispatch.parameter_filter' => [:foo]}
+ get "/", params: { "foo"=>"bar" }, headers: { 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.parameter_filter' => [:foo] }
assert_response 500
assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
end
@@ -213,7 +213,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "show registered original exception for wrapped exceptions" do
@app = DevelopmentApp
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_match(/AbstractController::ActionNotFound/, body)
end
@@ -221,7 +221,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "named urls missing keys raise 500 level error" do
@app = DevelopmentApp
- get "/missing_keys", {}, {'action_dispatch.show_exceptions' => true}
+ get "/missing_keys", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_match(/ActionController::UrlGenerationError/, body)
@@ -229,7 +229,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "show the controller name in the diagnostics template when controller name is present" do
@app = DevelopmentApp
- get("/runtime_error", {}, {
+ get("/runtime_error", headers: {
'action_dispatch.show_exceptions' => true,
'action_dispatch.request.parameters' => {
'action' => 'show',
@@ -252,7 +252,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
}
}
- get("/runtime_error", {}, {
+ get("/runtime_error", headers: {
'action_dispatch.show_exceptions' => true,
'action_dispatch.request.parameters' => {
'action' => 'show',
@@ -267,21 +267,21 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "sets the HTTP charset parameter" do
@app = DevelopmentApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test 'uses logger from env' do
@app = DevelopmentApp
output = StringIO.new
- get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output) }
assert_match(/puke/, output.rewind && output.read)
end
test 'uses backtrace cleaner from env' do
@app = DevelopmentApp
cleaner = stub(:clean => ['passed backtrace cleaner'])
- get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner }
assert_match(/passed backtrace cleaner/, body)
end
@@ -294,14 +294,14 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
'action_dispatch.logger' => Logger.new(output),
'action_dispatch.backtrace_cleaner' => backtrace_cleaner}
- get "/", {}, env
+ get "/", headers: env
assert_operator((output.rewind && output.read).lines.count, :>, 10)
end
test 'display backtrace when error type is SyntaxError' do
@app = DevelopmentApp
- get '/original_syntax_error', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+ get '/original_syntax_error', headers: { 'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new }
assert_response 500
assert_select '#Application-Trace' do
@@ -312,7 +312,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test 'display backtrace on template missing errors' do
@app = DevelopmentApp
- get "/missing_template", nil, {}
+ get "/missing_template"
assert_select "header h1", /Template is missing/
@@ -328,7 +328,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do
@app = DevelopmentApp
- get '/syntax_error_into_view', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+ get '/syntax_error_into_view', headers: { 'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new }
assert_response 500
assert_select '#Application-Trace' do
@@ -344,7 +344,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} }
end
- get '/framework_raises', {}, {'action_dispatch.backtrace_cleaner' => cleaner}
+ get '/framework_raises', headers: { 'action_dispatch.backtrace_cleaner' => cleaner }
# Assert correct error
assert_response 500
diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb
index d7408164ba..7a29a7ff97 100644
--- a/actionpack/test/dispatch/exception_wrapper_test.rb
+++ b/actionpack/test/dispatch/exception_wrapper_test.rb
@@ -34,6 +34,23 @@ module ActionDispatch
assert_equal [ code: 'foo', line_number: 42 ], wrapper.source_extracts
end
+ test '#source_extracts works with Windows paths' do
+ exc = TestError.new("c:/path/to/rails/app/controller.rb:27:in 'index':")
+
+ wrapper = ExceptionWrapper.new({}, exc)
+ wrapper.expects(:source_fragment).with('c:/path/to/rails/app/controller.rb', 27).returns('nothing')
+
+ assert_equal [ code: 'nothing', line_number: 27 ], wrapper.source_extracts
+ end
+
+ test '#source_extracts works with non standard backtrace' do
+ exc = TestError.new('invalid')
+
+ wrapper = ExceptionWrapper.new({}, exc)
+ wrapper.expects(:source_fragment).with('invalid', 0).returns('nothing')
+
+ assert_equal [ code: 'nothing', line_number: 0 ], wrapper.source_extracts
+ end
test '#application_trace returns traces only from the application' do
exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'"))
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index d5a4d8ee11..6a439be2b5 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -64,7 +64,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
end
def test_mounting_works_with_nested_script_name
- get "/foo/sprockets/omg", {}, 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg'
+ get "/foo/sprockets/omg", headers: { 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg' }
assert_equal "/foo/sprockets -- /omg", response.body
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index b765a13fa1..d77341bc64 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -56,7 +56,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
with_test_routing do
output = StringIO.new
json = "[\"person]\": {\"name\": \"David\"}}"
- post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output)}
+ post "/parse", params: json, headers: { 'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output) }
assert_response :bad_request
output.rewind && err = output.read
assert err =~ /Error occurred while parsing request parameters/
@@ -79,7 +79,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test 'raw_post is not empty for JSON request' do
with_test_routing do
- post '/parse', '{"posts": [{"title": "Post Title"}]}', 'CONTENT_TYPE' => 'application/json'
+ post '/parse', params: '{"posts": [{"title": "Post Title"}]}', headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal '{"posts": [{"title": "Post Title"}]}', request.raw_post
end
end
@@ -87,7 +87,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
private
def assert_parses(expected, actual, headers = {})
with_test_routing do
- post "/parse", actual, headers
+ post "/parse", params: actual, headers: headers
assert_response :ok
assert_equal(expected, TestController.last_request_parameters)
end
@@ -146,7 +146,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
private
def assert_parses(expected, actual, headers = {})
with_test_routing(UsersController) do
- post "/parse", actual, headers
+ post "/parse", params: actual, headers: headers
assert_response :ok
assert_equal(expected, UsersController.last_request_parameters)
assert_equal(expected.merge({"action" => "parse"}), UsersController.last_parameters)
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 926472163e..50f69c53cb 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
@@ -37,7 +36,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
test "parse single utf8 parameter" do
- assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'},
parse_multipart('single_utf8_param'), "request.request_parameters")
assert_equal(
'Iñtërnâtiônàlizætiøn_value',
@@ -45,8 +44,8 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
test "parse bracketed utf8 parameter" do
- assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => {
- 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} },
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => {
+ 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} },
parse_multipart('bracketed_utf8_param'), "request.request_parameters")
assert_equal(
{'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'},
@@ -134,13 +133,13 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
with_test_routing do
fixture = FIXTURE_PATH + "/mona_lisa.jpg"
params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") }
- post '/read', params
+ post '/read', params: params
end
end
test "uploads and reads file" do
with_test_routing do
- post '/read', :uploaded_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain")
+ post '/read', params: { uploaded_data: fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") }
assert_equal "File: Hello", response.body
end
end
@@ -152,7 +151,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
get ':action', controller: 'multipart_params_parsing_test/test'
end
headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
- get "/parse", {}, headers
+ get "/parse", headers: headers
assert_response :ok
end
end
@@ -169,7 +168,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def parse_multipart(name)
with_test_routing do
headers = fixture(name)
- post "/parse", headers.delete("rack.input"), headers
+ post "/parse", params: headers.delete("rack.input"), headers: headers
assert_response :ok
TestController.last_request_parameters
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index 50daafbb54..bc6716525e 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -147,7 +147,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
get ':action', :to => ::QueryStringParsingTest::TestController
end
- get "/parse", nil, "QUERY_STRING" => "foo[]=bar&foo[4]=bar"
+ get "/parse", headers: { "QUERY_STRING" => "foo[]=bar&foo[4]=bar" }
assert_response :bad_request
end
end
@@ -162,8 +162,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
middleware.use(EarlyParse)
end
-
- get "/parse", actual
+ get "/parse", params: actual
assert_response :ok
assert_equal(expected, ::QueryStringParsingTest::TestController.last_query_parameters)
end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 1de05cbf09..365edf849a 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -131,7 +131,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
test "ambiguous params returns a bad request" do
with_test_routing do
- post "/parse", "foo[]=bar&foo[4]=bar"
+ post "/parse", params: "foo[]=bar&foo[4]=bar"
assert_response :bad_request
end
end
@@ -148,7 +148,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
def assert_parses(expected, actual)
with_test_routing do
- post "/parse", actual
+ post "/parse", params: actual
assert_response :ok
assert_equal expected, TestController.last_request_parameters
assert_utf8 TestController.last_request_parameters
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
index a8050b4fab..a307483509 100644
--- a/actionpack/test/dispatch/request_id_test.rb
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -41,7 +41,7 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest
test "request id given on request is passed all the way to the response" do
with_test_route_set do
- get '/', {}, 'HTTP_X_REQUEST_ID' => 'X' * 500
+ get '/', headers: { 'HTTP_X_REQUEST_ID' => 'X' * 500 }
assert_equal "X" * 255, @response.headers["X-Request-Id"]
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index c61423dce4..5fbd19acdf 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -254,10 +254,6 @@ class ResponseTest < ActiveSupport::TestCase
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
- def app
- @app
- end
-
test "response cache control from railsish app" do
@app = lambda { |env|
ActionDispatch::Response.new.tap { |resp|
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 57cdecfb8a..deb289bd57 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -362,22 +362,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor
end
- get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/admin', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'queenbee#index', @response.body
- get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/admin', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/admin/accounts', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'queenbee#accounts', @response.body
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/admin/accounts', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/admin/passwords', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'queenbee#passwords', @response.body
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/admin/passwords', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
end
@@ -1692,9 +1692,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get '/products/0001/images/0001'
assert_equal 'images#show', @response.body
- get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/dashboard', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
- get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/dashboard', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'dashboards#show', @response.body
end
@@ -3486,6 +3486,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/post/comments/new', new_comment_path
end
+ def test_head_fetch_with_mount_on_root
+ draw do
+ get '/home' => 'test#index'
+ mount lambda { |env| [404, {"Content-Type" => "text/html"}, ["testing"]] }, at: '/'
+ end
+ head '/home'
+ assert_response :success
+
+ head '/'
+ assert_response :not_found
+ end
+
private
def draw(&block)
@@ -3582,12 +3594,12 @@ class TestAltApp < ActionDispatch::IntegrationTest
end
def test_alt_request_with_matched_header
- get "/", {}, "HTTP_X_HEADER" => "HEADER"
+ get "/", headers: { "HTTP_X_HEADER" => "HEADER" }
assert_equal "XHeader", @response.body
end
def test_alt_request_with_unmatched_header
- get "/", {}, "HTTP_X_HEADER" => "NON_MATCH"
+ get "/", headers: { "HTTP_X_HEADER" => "NON_MATCH" }
assert_equal "Alternative App", @response.body
end
end
@@ -3782,7 +3794,7 @@ class TestHttpMethods < ActionDispatch::IntegrationTest
(RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method|
test "request method #{method.underscore} can be matched" do
- get '/', nil, 'REQUEST_METHOD' => method
+ get '/', headers: { 'REQUEST_METHOD' => method }
assert_equal method, @response.body
end
end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index c5cd24d06e..2194efa503 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -125,7 +125,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_does_set_secure_cookies_over_https
with_test_route_set(:secure => true) do
- get '/set_session_value', nil, 'HTTPS' => 'on'
+ get '/set_session_value', headers: { 'HTTPS' => 'on' }
assert_response :success
assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly",
headers['Set-Cookie']
@@ -331,9 +331,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
private
# Overwrite get to send SessionSecret in env hash
- def get(path, parameters = nil, env = {})
- env["action_dispatch.key_generator"] ||= Generator
- super
+ def get(path, *args)
+ args[0] ||= {}
+ args[0][:headers] ||= {}
+ args[0][:headers]["action_dispatch.key_generator"] ||= Generator
+ super(path, *args)
end
def with_test_route_set(options = {})
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index f7a06cfed4..fbd82945cc 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -172,7 +172,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
reset!
- get '/set_session_value', :_session_id => session_id
+ get '/set_session_value', params: { _session_id: session_id }
assert_response :success
assert_not_equal session_id, cookies['_session_id']
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 323fbc285e..72eaa916bc 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -27,30 +27,30 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
test "skip exceptions app if not showing exceptions" do
@app = ProductionApp
assert_raise RuntimeError do
- get "/", {}, {'action_dispatch.show_exceptions' => false}
+ get "/", headers: { 'action_dispatch.show_exceptions' => false }
end
end
test "rescue with error page" do
@app = ProductionApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_equal "500 error fixture\n", body
- get "/bad_params", {}, {'action_dispatch.show_exceptions' => true}
+ get "/bad_params", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 400
assert_equal "400 error fixture\n", body
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_equal "404 error fixture\n", body
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_equal "", body
- get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_equal "", body
end
@@ -61,11 +61,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
begin
@app = ProductionApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_equal "500 localized error fixture\n", body
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_equal "404 error fixture\n", body
ensure
@@ -76,14 +76,14 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
test "sets the HTTP charset parameter" do
@app = ProductionApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test "show registered original exception for wrapped exceptions" do
@app = ProductionApp
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_match(/404 error/, body)
end
@@ -97,7 +97,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
@app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_equal "YOU FAILED BRO", body
end
@@ -108,7 +108,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
@app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_equal "", body
end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index c3598c5e8e..7ced41bc2e 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -20,7 +20,7 @@ class SSLTest < ActionDispatch::IntegrationTest
end
def test_allows_https_proxy_header_url
- get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https"
+ get "http://example.org/", headers: { 'HTTP_X_FORWARDED_PROTO' => "https" }
assert_response :success
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 9a6747fb8d..ebc9d71403 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'zlib'
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index 8f79e7bf9a..ce1e1d0a6a 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -39,12 +39,12 @@ module TestUrlGeneration
end
test "the request's SCRIPT_NAME takes precedence over the route" do
- get "/foo", {}, 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes
+ get "/foo", headers: { 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes }
assert_equal "/new/foo", response.body
end
test "the request's SCRIPT_NAME wraps the mounted app's" do
- get '/new/bar/foo', {}, 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes
+ get '/new/bar/foo', headers: { 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes }
assert_equal "/new/bar/foo", response.body
end
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
index 9b2b85ec73..2b505f081e 100644
--- a/actionpack/test/journey/router/utils_test.rb
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -1,4 +1,3 @@
-# coding: utf-8
require 'abstract_unit'
module ActionDispatch
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 743c01e393..5557285ef5 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,24 @@
+* Extracted `ActionView::Helpers::RecordTagHelper` to external gem
+ (`record_tag_helper`) and added removal notices.
+
+ *Todd Bealmear*
+
+* Allow to pass a string value to `size` option in `image_tag` and `video_tag`.
+
+ This makes the behavior more consistent with `width` or `height` options.
+
+ *Mehdi Lahmam*
+
+* Partial template name does no more have to be a valid Ruby identifier.
+
+ There used to be a naming rule that the partial name should start with
+ underscore, and should be followed by any combination of letters, numbers
+ and underscores.
+ But now we can give our partials any name starting with underscore, such as
+ _🍔.html.erb.
+
+ *Akira Matsuda*
+
* Change the default template handler from `ERB` to `Raw`.
Files without a template handler in their extension will be rendered using the raw
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 1feafc1094..43124bb904 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -70,6 +70,14 @@ module ActionView #:nodoc:
# Headline: <%= headline %>
# First name: <%= person.first_name %>
#
+ # The local variables passed to sub templates can be accessed as a hash using the <tt>local_assigns</tt> hash. This lets you access the
+ # variables as:
+ #
+ # Headline: <%= local_assigns[:headline] %>
+ #
+ # This is useful in cases where you aren't sure if the local variable has been assigned. Alternately, you could also use
+ # <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
+ #
# === Template caching
#
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index b7fdc16a9d..5c28043f8a 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -318,6 +318,7 @@ module ActionView
end
def extract_dimensions(size)
+ size = size.to_s
if size =~ %r{\A\d+x\d+\z}
size.split('x')
elsif size =~ %r{\A\d+\z}
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 4db8930a26..9dadfb5ce1 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -122,7 +122,7 @@ module ActionView
# Cache fragments of a view if +condition+ is true
#
- # <%= cache_if admin?, project do %>
+ # <% cache_if admin?, project do %>
# <b>All the topics on this project</b>
# <%= render project.topics %>
# <% end %>
@@ -138,7 +138,7 @@ module ActionView
# Cache fragments of a view unless +condition+ is true
#
- # <%= cache_unless admin?, project do %>
+ # <% cache_unless admin?, project do %>
# <b>All the topics on this project</b>
# <%= render project.topics %>
# <% end %>
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 4b4f0ae577..46ce7cf0be 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -177,6 +177,8 @@ module ActionView
# and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
# See <tt>Kernel.sprintf</tt> for documentation on format sequences.
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
+ # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing).
+ # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing).
# * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
# you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
# the current selected year minus 5.
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 8d78ba13d5..b76b35bf3c 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
require 'action_view/model_naming'
+require 'action_view/record_identifier'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/output_safety'
@@ -110,6 +111,7 @@ module ActionView
include FormTagHelper
include UrlHelper
include ModelNaming
+ include RecordIdentifier
# Creates a form that allows the user to create or update the attributes
# of a specific model object.
@@ -1224,11 +1226,11 @@ module ActionView
object_name = model_name_from_record_or_class(object).param_key
end
- builder = options[:builder] || default_form_builder
+ builder = options[:builder] || default_form_builder_class
builder.new(object_name, object, self, options)
end
- def default_form_builder
+ def default_form_builder_class
builder = ActionView::Base.default_form_builder
builder.respond_to?(:constantize) ? builder.constantize : builder
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 8bb243b8b7..65a0548ffb 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -84,14 +84,13 @@ module ActionView
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:include_blank</tt> - If set to true, an empty option will be created. If set to a string, the string will be used as the option's content and the value will be empty.
# * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something.
- # * <tt>:selected</tt> - Provide a default selected value. It should be of the exact type as the provided options.
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
# select_tag "people", options_from_collection_for_select(@people, "id", "name")
# # <select id="people" name="people"><option value="1">David</option></select>
#
- # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"]
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name", "1")
# # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
#
# select_tag "people", "<option>David</option>".html_safe
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index f66dbfe7d3..cfd617cedc 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/string/output_safety'
diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb
index 77c3e6d394..f7ee573035 100644
--- a/actionview/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/record_tag_helper.rb
@@ -1,108 +1,21 @@
-require 'action_view/record_identifier'
-
module ActionView
- # = Action View Record Tag Helpers
module Helpers
module RecordTagHelper
- include ActionView::RecordIdentifier
-
- # Produces a wrapper DIV element with id and class parameters that
- # relate to the specified Active Record object. Usage example:
- #
- # <%= div_for(@person, class: "foo") do %>
- # <%= @person.name %>
- # <% end %>
- #
- # produces:
- #
- # <div id="person_123" class="person foo"> Joe Bloggs </div>
- #
- # You can also pass an array of Active Record objects, which will then
- # get iterated over and yield each record as an argument for the block.
- # For example:
- #
- # <%= div_for(@people, class: "foo") do |person| %>
- # <%= person.name %>
- # <% end %>
- #
- # produces:
- #
- # <div id="person_123" class="person foo"> Joe Bloggs </div>
- # <div id="person_124" class="person foo"> Jane Bloggs </div>
- #
- def div_for(record, *args, &block)
- content_tag_for(:div, record, *args, &block)
+ def div_for(*)
+ raise NoMethodError, "The `div_for` method has been removed from " \
+ "Rails. To continue using it, add the `record_tag_helper` gem to " \
+ "your Gemfile:\n" \
+ " gem 'record_tag_helper', '~> 1.0'\n" \
+ "Consult the Rails upgrade guide for details."
end
- # content_tag_for creates an HTML element with id and class parameters
- # that relate to the specified Active Record object. For example:
- #
- # <%= content_tag_for(:tr, @person) do %>
- # <td><%= @person.first_name %></td>
- # <td><%= @person.last_name %></td>
- # <% end %>
- #
- # would produce the following HTML (assuming @person is an instance of
- # a Person object, with an id value of 123):
- #
- # <tr id="person_123" class="person">....</tr>
- #
- # If you require the HTML id attribute to have a prefix, you can specify it:
- #
- # <%= content_tag_for(:tr, @person, :foo) do %> ...
- #
- # produces:
- #
- # <tr id="foo_person_123" class="person">...
- #
- # You can also pass an array of objects which this method will loop through
- # and yield the current object to the supplied block, reducing the need for
- # having to iterate through the object (using <tt>each</tt>) beforehand.
- # For example (assuming @people is an array of Person objects):
- #
- # <%= content_tag_for(:tr, @people) do |person| %>
- # <td><%= person.first_name %></td>
- # <td><%= person.last_name %></td>
- # <% end %>
- #
- # produces:
- #
- # <tr id="person_123" class="person">...</tr>
- # <tr id="person_124" class="person">...</tr>
- #
- # content_tag_for also accepts a hash of options, which will be converted to
- # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
- # with the default class name for your object. For example:
- #
- # <%= content_tag_for(:li, @person, class: "bar") %>...
- #
- # produces:
- #
- # <li id="person_123" class="person bar">...
- #
- def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
- options, prefix = prefix, nil if prefix.is_a?(Hash)
-
- Array(single_or_multiple_records).map do |single_record|
- content_tag_for_single_record(tag_name, single_record, prefix, options, &block)
- end.join("\n").html_safe
+ def content_tag_for(*)
+ raise NoMethodError, "The `content_tag_for` method has been removed from " \
+ "Rails. To continue using it, add the `record_tag_helper` gem to " \
+ "your Gemfile:\n" \
+ " gem 'record_tag_helper', '~> 1.0'\n" \
+ "Consult the Rails upgrade guide for details."
end
-
- private
-
- # Called by <tt>content_tag_for</tt> internally to render a content tag
- # for each record.
- def content_tag_for_single_record(tag_name, record, prefix, options, &block)
- options = options ? options.dup : {}
- options[:class] = [ dom_class(record, prefix), options[:class] ].compact
- options[:id] = dom_id(record, prefix)
-
- if block_given?
- content_tag(tag_name, capture(record, &block), options)
- else
- content_tag(tag_name, "", options)
- end
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index e72e85ee5f..463a4e9f60 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -8,76 +8,77 @@ module ActionView
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
extend ActiveSupport::Concern
- # This +sanitize+ helper will HTML encode all tags and strip all attributes that
- # aren't specifically allowed.
+ # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
#
- # It also strips href/src tags with invalid protocols, like javascript: especially.
- # It does its best to counter any tricks that hackers may use, like throwing in
- # unicode/ascii/hex values to get past the javascript: filters. Check out
- # the extensive test suite.
+ # It also strips href/src attributes with unsafe protocols like
+ # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
+ # ASCII, and hex character references to work around these protocol filters.
#
- # <%= sanitize @article.body %>
+ # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
+ # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
#
- # You can add or remove tags/attributes if you want to customize it a bit.
- # See ActionView::Base for full docs on the available options. You can add
- # tags/attributes for single uses of +sanitize+ by passing either the
- # <tt>:attributes</tt> or <tt>:tags</tt> options:
+ # Custom sanitization rules can also be provided.
#
- # Normal Use
- #
- # <%= sanitize @article.body %>
+ # Please note that sanitizing user-provided text does not guarantee that the
+ # resulting markup is valid or even well-formed. For example, the output may still
+ # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>.
#
- # Custom Use - Custom Scrubber
- # (supply a Loofah::Scrubber that does the sanitization)
+ # ==== Options
#
- # scrubber can either wrap a block:
- # scrubber = Loofah::Scrubber.new do |node|
- # node.text = "dawn of cats"
- # end
+ # * <tt>:tags</tt> - An array of allowed tags.
+ # * <tt>:attributes</tt> - An array of allowed attributes.
+ # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
+ # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
+ # defines custom sanitization rules. A custom scrubber takes precedence over
+ # custom tags and attributes.
#
- # or be a subclass of Loofah::Scrubber which responds to scrub:
- # class KittyApocalypse < Loofah::Scrubber
- # def scrub(node)
- # node.text = "dawn of cats"
- # end
- # end
- # scrubber = KittyApocalypse.new
+ # ==== Examples
#
- # <%= sanitize @article.body, scrubber: scrubber %>
+ # Normal use:
#
- # A custom scrubber takes precedence over custom tags and attributes
- # Learn more about scrubbers here: https://github.com/flavorjones/loofah
+ # <%= sanitize @comment.body %>
#
- # Custom Use - tags and attributes
- # (only the mentioned tags and attributes are allowed, nothing else)
+ # Providing custom whitelisted tags and attributes:
#
- # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %>
+ # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
#
- # Add table tags to the default allowed tags
+ # Providing a custom Rails::Html scrubber:
#
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
- # end
+ # class CommentScrubber < Rails::Html::PermitScrubber
+ # def allowed_node?(node)
+ # !%w(form script comment blockquote).include?(node.name)
+ # end
#
- # Remove tags to the default allowed tags
+ # def skip_node?(node)
+ # node.text?
+ # end
#
- # class Application < Rails::Application
- # config.after_initialize do
- # ActionView::Base.sanitized_allowed_tags.delete 'div'
+ # def scrub_attribute?(name)
+ # name == 'style'
# end
# end
#
- # Change allowed default attributes
+ # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
+ #
+ # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
+ # documentation about Rails::Html scrubbers.
#
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style']
+ # Providing a custom Loofah::Scrubber:
+ #
+ # scrubber = Loofah::Scrubber.new do |node|
+ # node.remove if node.name == 'script'
# end
#
- # Please note that sanitizing user-provided text does not guarantee that the
- # resulting markup is valid (conforming to a document type) or even well-formed.
- # The output may still contain e.g. unescaped '<', '>', '&' characters and
- # confuse browsers.
+ # <%= sanitize @comment.body, scrubber: scrubber %>
+ #
+ # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
+ # information about defining custom Loofah::Scrubber objects.
#
+ # To set the default allowed tags or attributes across your application:
+ #
+ # # In config/application.rb
+ # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
+ # config.action_view.sanitized_allowed_attributes = ['href', 'title']
def sanitize(html, options = {})
self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
end
@@ -87,9 +88,7 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from the +html+, including comments. This uses
- # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability
- # is limited by that of Nokogiri.
+ # Strips all HTML tags from +html+, including comments.
#
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
@@ -103,7 +102,7 @@ module ActionView
self.class.full_sanitizer.sanitize(html)
end
- # Strips all link tags from +text+ leaving just the link text.
+ # Strips all link tags from +html+ leaving just the link text.
#
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
# # => Ruby on Rails
@@ -166,30 +165,6 @@ module ActionView
def white_list_sanitizer
@white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
end
-
- ##
- # :method: sanitized_allowed_tags=
- #
- # :call-seq: sanitized_allowed_tags=(tags)
- #
- # Replaces the allowed tags for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
- # end
- #
-
- ##
- # :method: sanitized_allowed_attributes=
- #
- # :call-seq: sanitized_allowed_attributes=(attributes)
- #
- # Replaces the allowed HTML attributes for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
- # end
- #
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb
index 45c75d10c0..a4f6eb0150 100644
--- a/actionview/lib/action_view/helpers/tags.rb
+++ b/actionview/lib/action_view/helpers/tags.rb
@@ -5,6 +5,7 @@ module ActionView
eager_autoload do
autoload :Base
+ autoload :Translator
autoload :CheckBox
autoload :CollectionCheckBoxes
autoload :CollectionRadioButtons
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 7740c60eac..acc6443a96 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -14,7 +14,7 @@ module ActionView
@object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
@object = retrieve_object(options.delete(:object))
@options = options
- @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
+ @auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil
end
# This is what child classes implement.
@@ -79,35 +79,30 @@ module ActionView
end
def add_default_name_and_id(options)
- if options.has_key?("index")
- options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
- options.delete("index")
- elsif defined?(@auto_index)
- options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
- else
- options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id }
+ index = name_and_id_index(options)
+ options["name"] = options.fetch("name"){ tag_name(options["multiple"], index) }
+ options["id"] = options.fetch("id"){ tag_id(index) }
+ if namespace = options.delete("namespace")
+ options['id'] = options['id'] ? "#{namespace}_#{options['id']}" : namespace
end
-
- options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
- end
-
- def tag_name(multiple = false)
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
- end
-
- def tag_name_with_index(index, multiple = false)
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
end
- def tag_id
- "#{sanitized_object_name}_#{sanitized_method_name}"
+ def tag_name(multiple = false, index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
+ else
+ "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
+ end
end
- def tag_id_with_index(index)
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ def tag_id(index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ else
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
end
def sanitized_object_name
@@ -149,6 +144,10 @@ module ActionView
end
option_tags
end
+
+ def name_and_id_index(options)
+ options.key?("index") ? options.delete("index") || "" : @auto_index
+ end
end
end
end
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 6242a2a085..1765fa6558 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -41,14 +41,7 @@ module ActionView
end
def hidden_field
- hidden_name = @html_options[:name]
-
- hidden_name ||= if @options.has_key?(:index)
- "#{tag_name_with_index(@options[:index])}[]"
- else
- "#{tag_name}[]"
- end
-
+ hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]"
@template_object.hidden_field_tag(hidden_name, "", id: nil)
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 08a23e497e..b31d5fda66 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -15,20 +15,10 @@ module ActionView
def translation
method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
- @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
-
- if object.respond_to?(:to_model)
- key = object.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_and_value)
- end
+ content ||= Translator
+ .new(object, @object_name, method_and_value, scope: "helpers.label")
+ .translate
content ||= @method_name.humanize
content
diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb
index ae67bc13af..cf7b117614 100644
--- a/actionview/lib/action_view/helpers/tags/placeholderable.rb
+++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb
@@ -7,24 +7,12 @@ module ActionView
if tag_value = @options[:placeholder]
placeholder = tag_value if tag_value.is_a?(String)
-
- object_name = @object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
- if object.respond_to?(:to_model)
- key = object.class.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- placeholder ||= I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence
-
- placeholder ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_and_value)
- end
-
+ placeholder ||= Tags::Translator
+ .new(object, @object_name, method_and_value, scope: "helpers.placeholder")
+ .translate
placeholder ||= @method_name.humanize
-
@options[:placeholder] = placeholder
end
end
diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb
new file mode 100644
index 0000000000..8b6655481d
--- /dev/null
+++ b/actionview/lib/action_view/helpers/tags/translator.rb
@@ -0,0 +1,40 @@
+module ActionView
+ module Helpers
+ module Tags # :nodoc:
+ class Translator # :nodoc:
+ def initialize(object, object_name, method_and_value, scope:)
+ @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
+ @method_and_value = method_and_value
+ @scope = scope
+ @model = object.respond_to?(:to_model) ? object.to_model : nil
+ end
+
+ def translate
+ translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence
+ translated_attribute || human_attribute_name
+ end
+
+ protected
+
+ attr_reader :object_name, :method_and_value, :scope, :model
+
+ private
+
+ def i18n_default
+ if model
+ key = model.model_name.i18n_key
+ ["#{key}.#{method_and_value}".to_sym, ""]
+ else
+ ""
+ end
+ end
+
+ def human_attribute_name
+ if model && model.class.respond_to?(:human_attribute_name)
+ model.class.human_attribute_name(method_and_value)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 8c2d5705f1..3dbce0738e 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -46,9 +46,9 @@ module ActionView
end
protected :_back_url
- # Creates a link tag of the given +name+ using a URL created by the set of +options+.
+ # Creates an anchor element of the given +name+ using a URL created by the set of +options+.
# See the valid options in the documentation for +url_for+. It's also possible to
- # pass a String instead of an options hash, which generates a link tag that uses the
+ # pass a String instead of an options hash, which generates an anchor element that uses the
# value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
# of an options hash will generate a link to the referrer (a JavaScript back link
# will be used in place of a referrer if none exists). If +nil+ is passed as the name
@@ -459,7 +459,7 @@ module ActionView
html_options = (html_options || {}).stringify_keys
extras = %w{ cc bcc body subject reply_to }.map! { |item|
- option = html_options.delete(item) || next
+ option = html_options.delete(item).presence || next
"#{item.dasherize}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + extras.join('&')
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 81f9c40b85..ee3dce24b7 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -38,7 +38,7 @@ module ActionView
initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
- ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
+ ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 6c3015180a..5ff15411cf 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -519,7 +519,7 @@ module ActionView
def retrieve_variable(path, as)
variable = as || begin
base = path[-1] == "/" ? "" : File.basename(path)
- raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/
$1.to_sym
end
if @collection
@@ -530,8 +530,7 @@ module ActionView
end
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
- "make sure your partial name starts with underscore, " +
- "and is followed by any combination of letters, numbers and underscores."
+ "make sure your partial name starts with underscore."
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
"make sure it starts with lowercase letter, " +
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index abd3b77c67..1e8e7415d1 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -92,12 +92,15 @@ module ActionView
# Find and render a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
- variant = options[:variant]
+ variant = options.delete(:variant)
+ assigns = options.delete(:assigns)
+ context = view_context
+ context.assign assigns if assigns
lookup_context.rendered_format = nil if options[:formats]
lookup_context.variants = variant if variant
- view_renderer.render(view_context, options)
+ view_renderer.render(context, options)
end
# Assign the rendered format to lookup context.
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 6b61378a1f..305d9c6ee3 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -87,6 +87,19 @@ module ActionView
# expected_encoding
# )
+ ##
+ # :method: local_assigns
+ #
+ # Returns a hash with the defined local variables.
+ #
+ # Given this sub template rendering:
+ #
+ # <%= render "shared/header", { headline: "Welcome", person: person } %>
+ #
+ # You can use +local_assigns+ in the sub templates to access the local variables:
+ #
+ # local_assigns[:headline] # => "Welcome"
+
eager_autoload do
autoload :Error
autoload :Handlers
@@ -103,7 +116,7 @@ module ActionView
# This finalizer is needed (and exactly with a proc inside another proc)
# otherwise templates leak in development.
- Finalizer = proc do |method_name, mod|
+ Finalizer = proc do |method_name, mod| # :nodoc:
proc do
mod.module_eval do
remove_possible_method method_name
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 812b011bd7..06810ad14d 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -204,7 +204,7 @@ module ActionView
def view
@view ||= begin
view = @controller.view_context
- view.singleton_class.send :include, _helpers
+ view.singleton_class.include(_helpers)
view.extend(Locals)
view.rendered_views = self.rendered_views
view.output_buffer = self.output_buffer
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 4aa56f60f7..fc1ca9efdf 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -268,7 +268,7 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
end
-ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
+ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
module ActionController
class Base
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 563caee8a2..fe4cf3688a 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -453,6 +453,10 @@ class TestController < ApplicationController
render :text => "foo"
end
+ def render_with_assigns_option
+ render inline: '<%= @hello %>', assigns: { hello: "world" }
+ end
+
def yield_content_for
render :action => "content_for", :layout => "yield"
end
@@ -953,23 +957,23 @@ class RenderTest < ActionController::TestCase
end
def test_accessing_params_in_template
- get :accessing_params_in_template, :name => "David"
+ get :accessing_params_in_template, params: { name: "David" }
assert_equal "Hello: David", @response.body
end
def test_accessing_local_assigns_in_inline_template
- get :accessing_local_assigns_in_inline_template, :local_name => "Local David"
+ get :accessing_local_assigns_in_inline_template, params: { local_name: "Local David" }
assert_equal "Goodbye, Local David", @response.body
assert_equal "text/html", @response.content_type
end
def test_should_implicitly_render_html_template_from_xhr_request
- xhr :get, :render_implicit_html_template_from_xhr_request
+ get :render_implicit_html_template_from_xhr_request, xhr: true
assert_equal "XHR!\nHello HTML!", @response.body
end
def test_should_implicitly_render_js_template_without_layout
- xhr :get, :render_implicit_js_template_without_layout, :format => :js
+ get :render_implicit_js_template_without_layout, format: :js, xhr: true
assert_no_match %r{<html>}, @response.body
end
@@ -1042,7 +1046,7 @@ class RenderTest < ActionController::TestCase
end
def test_accessing_params_in_template_with_layout
- get :accessing_params_in_template_with_layout, :name => "David"
+ get :accessing_params_in_template_with_layout, params: { name: "David" }
assert_equal "<html>Hello: David</html>", @response.body
end
@@ -1102,6 +1106,11 @@ class RenderTest < ActionController::TestCase
assert_equal "world", assigns["hello"]
end
+ def test_render_text_with_assigns_option
+ get :render_with_assigns_option
+ assert_equal 'world', response.body
+ end
+
# :ported:
def test_template_with_locals
get :render_with_explicit_template_with_locals
diff --git a/actionview/test/activerecord/controller_runtime_test.rb b/actionview/test/activerecord/controller_runtime_test.rb
index 469adff39a..af91348d76 100644
--- a/actionview/test/activerecord/controller_runtime_test.rb
+++ b/actionview/test/activerecord/controller_runtime_test.rb
@@ -4,7 +4,7 @@ require 'fixtures/project'
require 'active_support/log_subscriber/test_helper'
require 'action_controller/log_subscriber'
-ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
+ActionController::Base.include(ActiveRecord::Railties::ControllerRuntime)
class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
class LogSubscriberController < ActionController::Base
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 8e1ed2776d..6b51775cf3 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -113,7 +113,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_passing_routes_proxy
with_namespaced_routes(:blog) do
- proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self)
+ proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self, _routes.url_helpers)
@blog_post.save
assert_url "http://example.com/posts/#{@blog_post.id}", [proxy, @blog_post]
end
diff --git a/actionview/test/fixtures/test/_FooBar.html.erb b/actionview/test/fixtures/test/_FooBar.html.erb
new file mode 100644
index 0000000000..4bbe59410a
--- /dev/null
+++ b/actionview/test/fixtures/test/_FooBar.html.erb
@@ -0,0 +1 @@
+🍣
diff --git a/actionview/test/fixtures/test/_a-in.html.erb b/actionview/test/fixtures/test/_a-in.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionview/test/fixtures/test/_a-in.html.erb
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index 789b1d198b..65c68fc34a 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -54,6 +54,22 @@ class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :writt
def tags_attributes=(attributes); end
end
+class PostDelegator < Post
+ def to_model
+ PostDelegate.new
+ end
+end
+
+class PostDelegate < Post
+ def self.human_attribute_name(attribute)
+ "Delegate #{super}"
+ end
+
+ def model_name
+ ActiveModel::Name.new(self.class)
+ end
+end
+
class Comment
extend ActiveModel::Naming
include ActiveModel::Conversion
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index dac1c7024d..a15c6fac90 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -180,6 +180,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />),
%(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />),
%(image_tag("gold.png", :size => "20")) => %(<img alt="Gold" height="20" src="/images/gold.png" width="20" />),
+ %(image_tag("gold.png", :size => 20)) => %(<img alt="Gold" height="20" src="/images/gold.png" width="20" />),
%(image_tag("gold.png", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
%(image_tag("gold.png", "size" => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
%(image_tag("error.png", "size" => "45 x 70")) => %(<img alt="Error" src="/images/error.png" />),
@@ -238,6 +239,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>),
%(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>),
%(video_tag("error.avi", "size" => "100")) => %(<video height="100" src="/videos/error.avi" width="100"></video>),
+ %(video_tag("error.avi", "size" => 100)) => %(<video height="100" src="/videos/error.avi" width="100"></video>),
%(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>),
%(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>),
%(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>),
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 9e06621c83..525d99750d 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -231,28 +231,28 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_should_use_default_language_if_none_is_given
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_match(%r{xml:lang="en-US"}, @response.body)
end
end
def test_feed_should_include_two_entries
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "entry", 2
end
end
def test_entry_should_only_use_published_if_created_at_is_present
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "published", 1
end
end
def test_providing_builder_to_atom_feed
with_restful_routing(:scrolls) do
- get :index, :id=>"provide_builder"
+ get :index, params: { id: "provide_builder" }
# because we pass in the non-default builder, the content generated by the
# helper should go 'nowhere'. Leaving the response body blank.
assert @response.body.blank?
@@ -261,7 +261,7 @@ class AtomFeedTest < ActionController::TestCase
def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record
with_restful_routing(:scrolls) do
- get :index, :id => "entry_options"
+ get :index, params: { id: "entry_options" }
assert_select "updated", Time.utc(2007, 1, 1).xmlschema
assert_select "updated", Time.utc(2007, 1, 2).xmlschema
@@ -270,21 +270,21 @@ class AtomFeedTest < ActionController::TestCase
def test_self_url_should_default_to_current_request_url
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "link[rel=self][href=\"http://www.nextangle.com/scrolls?id=defaults\"]"
end
end
def test_feed_id_should_be_a_valid_tag
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "id", :text => "tag:www.nextangle.com,2008:/scrolls?id=defaults"
end
end
def test_entry_id_should_be_a_valid_tag
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "entry id", :text => "tag:www.nextangle.com,2008:Scroll/1"
assert_select "entry id", :text => "tag:www.nextangle.com,2008:Scroll/2"
end
@@ -292,14 +292,14 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_should_allow_nested_xml_blocks
with_restful_routing(:scrolls) do
- get :index, :id => "xml_block"
+ get :index, params: { id: "xml_block" }
assert_select "author name", :text => "DHH"
end
end
def test_feed_should_include_atomPub_namespace
with_restful_routing(:scrolls) do
- get :index, :id => "feed_with_atomPub_namespace"
+ get :index, params: { id: "feed_with_atomPub_namespace" }
assert_match %r{xml:lang="en-US"}, @response.body
assert_match %r{xmlns="http://www.w3.org/2005/Atom"}, @response.body
assert_match %r{xmlns:app="http://www.w3.org/2007/app"}, @response.body
@@ -308,7 +308,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_should_allow_overriding_ids
with_restful_routing(:scrolls) do
- get :index, :id => "feed_with_overridden_ids"
+ get :index, params: { id: "feed_with_overridden_ids" }
assert_select "id", :text => "tag:test.rubyonrails.org,2008:test/"
assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:1"
assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:2"
@@ -317,7 +317,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_xml_processing_instructions
with_restful_routing(:scrolls) do
- get :index, :id => 'feed_with_xml_processing_instructions'
+ get :index, params: { id: 'feed_with_xml_processing_instructions' }
assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body
assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body
end
@@ -325,7 +325,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_xml_processing_instructions_duplicate_targets
with_restful_routing(:scrolls) do
- get :index, :id => 'feed_with_xml_processing_instructions_duplicate_targets'
+ get :index, params: { id: 'feed_with_xml_processing_instructions_duplicate_targets' }
assert_match %r{<\?target1 (a="1" b="2"|b="2" a="1")\?>}, @response.body
assert_match %r{<\?target1 (c="3" d="4"|d="4" c="3")\?>}, @response.body
end
@@ -333,7 +333,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_xhtml
with_restful_routing(:scrolls) do
- get :index, :id => "feed_with_xhtml_content"
+ get :index, params: { id: "feed_with_xhtml_content" }
assert_match %r{xmlns="http://www.w3.org/1999/xhtml"}, @response.body
assert_select "summary", :text => /Something Boring/
assert_select "summary", :text => /after 2/
@@ -342,21 +342,21 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_entry_type_option_default_to_text_html
with_restful_routing(:scrolls) do
- get :index, :id => 'defaults'
+ get :index, params: { id: 'defaults' }
assert_select "entry link[rel=alternate][type=\"text/html\"]"
end
end
def test_feed_entry_type_option_specified
with_restful_routing(:scrolls) do
- get :index, :id => 'entry_type_options'
+ get :index, params: { id: 'entry_type_options' }
assert_select "entry link[rel=alternate][type=\"text/xml\"]"
end
end
def test_feed_entry_url_false_option_adds_no_link
with_restful_routing(:scrolls) do
- get :index, :id => 'entry_url_false_option'
+ get :index, params: { id: 'entry_url_false_option' }
assert_select "entry link", false
end
end
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index bb375076c6..d74da4c318 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'action_view/dependency_tracker'
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index dccf6a2090..4e336bea63 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -40,6 +40,9 @@ class FormHelperTest < ActionView::TestCase
},
tag: {
value: "Tag"
+ },
+ post_delegate: {
+ title: 'Delegate model_name title'
}
}
}
@@ -81,6 +84,9 @@ class FormHelperTest < ActionView::TestCase
body: "Write body here"
}
},
+ post_delegate: {
+ title: 'Delegate model_name title'
+ },
tag: {
value: "Tag"
}
@@ -117,6 +123,10 @@ class FormHelperTest < ActionView::TestCase
@post.tags = []
@post.tags << Tag.new
+ @post_delegator = PostDelegator.new
+
+ @post_delegator.title = 'Hello World'
+
@car = Car.new("#000FFF")
end
@@ -249,6 +259,18 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_label_with_non_active_record_object
+ form_for(OpenStruct.new(name:'ok'), as: 'person', url: 'an_url', html: { id: 'create-person' }) do |f|
+ f.label(:name)
+ end
+
+ expected = whole_form("an_url", "create-person", "new_person", method: "post") do
+ '<label for="person_name">Name</label>'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, for: "my_for"))
end
@@ -337,6 +359,22 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_label_with_to_model
+ assert_dom_equal(
+ %{<label for="post_delegator_title">Delegate Title</label>},
+ label(:post_delegator, :title)
+ )
+ end
+
+ def test_label_with_to_model_and_overriden_model_name
+ with_locale :label do
+ assert_dom_equal(
+ %{<label for="post_delegator_title">Delegate model_name title</label>},
+ label(:post_delegator, :title)
+ )
+ end
+ end
+
def test_text_field_placeholder_without_locales
with_locale :placeholder do
assert_dom_equal('<input id="post_body" name="post[body]" placeholder="Body" type="text" value="Back to the hill and over it again!" />', text_field(:post, :body, placeholder: true))
@@ -349,12 +387,28 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_text_field_placeholder_with_locales_and_to_model
+ with_locale :placeholder do
+ assert_dom_equal(
+ '<input id="post_delegator_title" name="post_delegator[title]" placeholder="Delegate model_name title" type="text" value="Hello World" />',
+ text_field(:post_delegator, :title, placeholder: true)
+ )
+ end
+ end
+
def test_text_field_placeholder_with_human_attribute_name
with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Total cost" type="text" />', text_field(:post, :cost, placeholder: true))
end
end
+ def test_text_field_placeholder_with_human_attribute_name_and_to_model
+ assert_dom_equal(
+ '<input id="post_delegator_title" name="post_delegator[title]" placeholder="Delegate Title" type="text" value="Hello World" />',
+ text_field(:post_delegator, :title, placeholder: true)
+ )
+ end
+
def test_text_field_placeholder_with_string_value
with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="HOW MUCH?" type="text" />', text_field(:post, :cost, placeholder: "HOW MUCH?"))
diff --git a/actionview/test/template/record_tag_helper_test.rb b/actionview/test/template/record_tag_helper_test.rb
index ab84bccb56..4b7b653916 100644
--- a/actionview/test/template/record_tag_helper_test.rb
+++ b/actionview/test/template/record_tag_helper_test.rb
@@ -24,94 +24,10 @@ class RecordTagHelperTest < ActionView::TestCase
end
def test_content_tag_for
- expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>)
- actual = content_tag_for(:li, @post)
- assert_dom_equal expected, actual
+ assert_raises(NoMethodError) { content_tag_for(:li, @post) }
end
- def test_content_tag_for_prefix
- expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>)
- actual = content_tag_for(:ul, @post, :archived)
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_with_extra_html_options
- expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, class: "special", style: "background-color: #f0f0f0")
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_with_array_css_class
- expected = %(<tr class="record_tag_post special odd" id="record_tag_post_45"></tr>)
- actual = content_tag_for(:tr, @post, class: ["special", "odd"])
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_with_prefix_and_extra_html_options
- expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0")
- assert_dom_equal expected, actual
- end
-
- def test_block_not_in_erb_multiple_calls
- expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
- actual = div_for(@post, class: "special") { @post.body }
- assert_dom_equal expected, actual
- actual = div_for(@post, class: "special") { @post.body }
- assert_dom_equal expected, actual
- end
-
- def test_block_works_with_content_tag_for_in_erb
- expected = %(<tr class="record_tag_post" id="record_tag_post_45">What a wonderful world!</tr>)
- actual = render_erb("<%= content_tag_for(:tr, @post) do %><%= @post.body %><% end %>")
- assert_dom_equal expected, actual
- end
-
- def test_div_for_in_erb
- expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
- actual = render_erb("<%= div_for(@post, class: 'special') do %><%= @post.body %><% end %>")
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_collection
- post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
- expected = %(<li class="record_tag_post" id="record_tag_post_101">Hello!</li>\n<li class="record_tag_post" id="record_tag_post_102">World!</li>)
- actual = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_collection_without_given_block
- post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!" }
- expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>)
- actual = content_tag_for(:li, [post_1, post_2])
- assert_dom_equal expected, actual
- end
-
- def test_div_for_collection
- post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
- expected = %(<div class="record_tag_post" id="record_tag_post_101">Hello!</div>\n<div class="record_tag_post" id="record_tag_post_102">World!</div>)
- actual = div_for([post_1, post_2]) { |post| post.body }
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_single_record_is_html_safe
- result = div_for(@post, class: "special") { @post.body }
- assert result.html_safe?
- end
-
- def test_content_tag_for_collection_is_html_safe
- post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
- result = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
- assert result.html_safe?
- end
-
- def test_content_tag_for_does_not_change_options_hash
- options = { class: "important" }
- content_tag_for(:li, @post, options)
- assert_equal({ class: "important" }, options)
+ def test_div_for
+ assert_raises(NoMethodError) { div_for(@post, class: "special") }
end
end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index dc4abca048..f77b81f0ee 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_models'
@@ -172,18 +171,12 @@ module RenderTestCases
assert_equal "only partial", @view.render("test/partial_only", :counter_counter => 5)
end
- def test_render_partial_with_invalid_name
- e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") }
- assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
- "make sure your partial name starts with underscore, " +
- "and is followed by any combination of letters, numbers and underscores.", e.message
+ def test_render_partial_with_number
+ assert_nothing_raised { @view.render(:partial => "test/200") }
end
def test_render_partial_with_missing_filename
- e = assert_raises(ArgumentError) { @view.render(:partial => "test/") }
- assert_equal "The partial name (test/) is not a valid Ruby identifier; " +
- "make sure your partial name starts with underscore, " +
- "and is followed by any combination of letters, numbers and underscores.", e.message
+ assert_raises(ActionView::MissingTemplate) { @view.render(:partial => "test/") }
end
def test_render_partial_with_incompatible_object
@@ -191,11 +184,12 @@ module RenderTestCases
assert_equal "'#{nil.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.", e.message
end
+ def test_render_partial_starting_with_a_capital
+ assert_nothing_raised { @view.render(:partial => 'test/FooBar') }
+ end
+
def test_render_partial_with_hyphen
- e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in") }
- assert_equal "The partial name (test/a-in) is not a valid Ruby identifier; " +
- "make sure your partial name starts with underscore, " +
- "and is followed by any combination of letters, numbers and underscores.", e.message
+ assert_nothing_raised { @view.render(:partial => "test/a-in") }
end
def test_render_partial_with_invalid_option_as
diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb
index 8a24d78e74..ec537775be 100644
--- a/actionview/test/template/streaming_render_test.rb
+++ b/actionview/test/template/streaming_render_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
class TestController < ActionController::Base
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index f05b845e46..f1b84c4786 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
class TextHelperTest < ActionView::TestCase
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 0d6f31af9b..ef4df0407a 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'minitest/mock'
@@ -496,6 +495,11 @@ class UrlHelperTest < ActiveSupport::TestCase
%{<a href="mailto:me@example.com?cc=ccaddress%40example.com&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email&amp;reply-to=foo%40bar.com">My email</a>},
mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.", reply_to: "foo@bar.com")
)
+
+ assert_dom_equal(
+ %{<a href="mailto:me@example.com?body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email">My email</a>},
+ mail_to("me@example.com", "My email", cc: '', bcc: '', subject: "This is an example email", body: "This is the body of the message.")
+ )
end
def test_mail_to_with_img
@@ -624,13 +628,13 @@ class UrlHelperControllerTest < ActionController::TestCase
end
def test_named_route_url_shows_host_and_path
- get :show_named_route, kind: 'url'
+ get :show_named_route, params: { kind: 'url' }
assert_equal 'http://test.host/url_helper_controller_test/url_helper/show_named_route',
@response.body
end
def test_named_route_path_shows_only_path
- get :show_named_route, kind: 'path'
+ get :show_named_route, params: { kind: 'path' }
assert_equal '/url_helper_controller_test/url_helper/show_named_route', @response.body
end
@@ -646,7 +650,7 @@ class UrlHelperControllerTest < ActionController::TestCase
end
end
- get :show_named_route, kind: 'url'
+ get :show_named_route, params: { kind: 'url' }
assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body
end
@@ -661,11 +665,11 @@ class UrlHelperControllerTest < ActionController::TestCase
end
def test_recall_params_should_normalize_id
- get :show, id: '123'
+ get :show, params: { id: '123' }
assert_equal 302, @response.status
assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location
- get :show, name: '123'
+ get :show, params: { name: '123' }
assert_equal 'ok', @response.body
end
@@ -704,7 +708,7 @@ class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase
end
def test_link_to_unless_current_shows_link
- get :show, id: 1
+ get :show, params: { id: 1 }
assert_equal %{<a href="/tasks">tasks</a>\n} +
%{<a href="#{@request.protocol}#{@request.host_with_port}/tasks">tasks</a>},
@response.body
@@ -778,21 +782,21 @@ class PolymorphicControllerTest < ActionController::TestCase
def test_existing_resource
@controller = WorkshopsController.new
- get :show, id: 1
+ get :show, params: { id: 1 }
assert_equal %{/workshops/1\n<a href="/workshops/1">Workshop</a>}, @response.body
end
def test_new_nested_resource
@controller = SessionsController.new
- get :index, workshop_id: 1
+ get :index, params: { workshop_id: 1 }
assert_equal %{/workshops/1/sessions\n<a href="/workshops/1/sessions">Session</a>}, @response.body
end
def test_existing_nested_resource
@controller = SessionsController.new
- get :show, workshop_id: 1, id: 1
+ get :show, params: { workshop_id: 1, id: 1 }
assert_equal %{/workshops/1/sessions/1\n<a href="/workshops/1/sessions/1">Session</a>}, @response.body
end
end
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 794d05d1b4..d4e19274fa 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,42 @@
+* Add an `:only` option to `perform_enqueued_jobs` to filter jobs based on
+ type.
+
+ This allows specific jobs to be tested, while preventing others from
+ being performed unnecessarily.
+
+ Example:
+
+ def test_hello_job
+ assert_performed_jobs 1, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+
+ An array may also be specified, to support testing multiple jobs.
+
+ Example:
+
+ def test_hello_and_logging_jobs
+ assert_nothing_raised do
+ assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later('stewie')
+ RescueJob.perform_later('david')
+ end
+ end
+ end
+
+ Fixes #18802.
+
+ *Michael Ryan*
+
+* Allow keyword arguments to be used with Active Job.
+
+ Fixes #18741.
+
+ *Sean Griffin*
+
* Add `:only` option to `assert_enqueued_jobs`, to check the number of times
a specific kind of job is enqueued.
@@ -38,5 +77,4 @@
*Isaac Seymour*
-
Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 752be6898e..622c37098e 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/hash'
module ActiveJob
# Raised when an exception is raised during job arguments deserialization.
@@ -44,7 +44,9 @@ module ActiveJob
private
GLOBALID_KEY = '_aj_globalid'.freeze
- private_constant :GLOBALID_KEY
+ SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze
+ WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze
+ private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
def serialize_argument(argument)
case argument
@@ -54,10 +56,15 @@ module ActiveJob
{ GLOBALID_KEY => argument.to_global_id.to_s }
when Array
argument.map { |arg| serialize_argument(arg) }
+ when ActiveSupport::HashWithIndifferentAccess
+ result = serialize_hash(argument)
+ result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true)
+ result
when Hash
- argument.each_with_object({}) do |(key, value), hash|
- hash[serialize_hash_key(key)] = serialize_argument(value)
- end
+ symbol_keys = argument.each_key.grep(Symbol).map(&:to_s)
+ result = serialize_hash(argument)
+ result[SYMBOL_KEYS_KEY] = symbol_keys
+ result
else
raise SerializationError.new("Unsupported argument type: #{argument.class.name}")
end
@@ -75,7 +82,7 @@ module ActiveJob
if serialized_global_id?(argument)
deserialize_global_id argument
else
- deserialize_hash argument
+ deserialize_hash(argument)
end
else
raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}"
@@ -90,13 +97,27 @@ module ActiveJob
GlobalID::Locator.locate hash[GLOBALID_KEY]
end
+ def serialize_hash(argument)
+ argument.each_with_object({}) do |(key, value), hash|
+ hash[serialize_hash_key(key)] = serialize_argument(value)
+ end
+ end
+
def deserialize_hash(serialized_hash)
- serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash|
- hash[key] = deserialize_argument(value)
+ result = serialized_hash.transform_values { |v| deserialize_argument(v) }
+ if result.delete(WITH_INDIFFERENT_ACCESS_KEY)
+ result = result.with_indifferent_access
+ elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY)
+ result = transform_symbol_keys(result, symbol_keys)
end
+ result
end
- RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym]
+ RESERVED_KEYS = [
+ GLOBALID_KEY, GLOBALID_KEY.to_sym,
+ SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym,
+ WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym,
+ ]
private_constant :RESERVED_KEYS
def serialize_hash_key(key)
@@ -109,5 +130,15 @@ module ActiveJob
raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")
end
end
+
+ def transform_symbol_keys(hash, symbol_keys)
+ hash.transform_keys do |key|
+ if symbol_keys.include?(key)
+ key.to_sym
+ else
+ key
+ end
+ end
+ end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
index ea9df9a063..c9e2bdca27 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -11,7 +11,7 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :test
class TestAdapter
delegate :name, to: :class
- attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs)
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter)
attr_writer(:enqueued_jobs, :performed_jobs)
def initialize
@@ -30,22 +30,33 @@ module ActiveJob
end
def enqueue(job) #:nodoc:
- if perform_enqueued_jobs
- performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name}
- Base.execute job.serialize
- else
- enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name}
- end
+ return if filtered?(job)
+
+ job_data = { job: job.class, args: job.serialize['arguments'], queue: job.queue_name }
+ enqueue_or_perform(perform_enqueued_jobs, job, job_data)
end
def enqueue_at(job, timestamp) #:nodoc:
- if perform_enqueued_at_jobs
- performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp}
- Base.execute job.serialize
- else
- enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp}
- end
+ return if filtered?(job)
+
+ job_data = { job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp }
+ enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
end
+
+ private
+
+ def enqueue_or_perform(perform, job, job_data)
+ if perform
+ performed_jobs << job_data
+ Base.execute job.serialize
+ else
+ enqueued_jobs << job_data
+ end
+ end
+
+ def filtered?(job)
+ filter && !Array(filter).include?(job.class)
+ end
end
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index d18656e398..25bc99a4f8 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -125,10 +125,32 @@ module ActiveJob
# HelloJob.perform_later('sean')
# end
# end
- def assert_performed_jobs(number)
+ #
+ # The block form supports filtering. If the :only option is specified,
+ # then only the listed job(s) will be performed.
+ #
+ # def test_hello_job
+ # assert_performed_jobs 1, only: HelloJob do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later
+ # end
+ # end
+ #
+ # An array may also be specified, to support testing multiple jobs.
+ #
+ # def test_hello_and_logging_jobs
+ # assert_nothing_raised do
+ # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later('stewie')
+ # RescueJob.perform_later('david')
+ # end
+ # end
+ # end
+ def assert_performed_jobs(number, only: nil)
if block_given?
original_count = performed_jobs.size
- perform_enqueued_jobs { yield }
+ perform_enqueued_jobs(only: only) { yield }
new_count = performed_jobs.size
assert_equal original_count + number, new_count,
"#{number} jobs expected, but #{new_count - original_count} were performed"
@@ -157,11 +179,33 @@ module ActiveJob
# end
# end
#
+ # The block form supports filtering. If the :only option is specified,
+ # then only the listed job(s) will be performed.
+ #
+ # def test_hello_job
+ # assert_performed_jobs 1, only: HelloJob do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later
+ # end
+ # end
+ #
+ # An array may also be specified, to support testing multiple jobs.
+ #
+ # def test_hello_and_logging_jobs
+ # assert_nothing_raised do
+ # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later('stewie')
+ # RescueJob.perform_later('david')
+ # end
+ # end
+ # end
+ #
# Note: This assertion is simply a shortcut for:
#
# assert_performed_jobs 0, &block
- def assert_no_performed_jobs(&block)
- assert_performed_jobs 0, &block
+ def assert_no_performed_jobs(only: nil, &block)
+ assert_performed_jobs 0, only: only, &block
end
# Asserts that the job passed in the block has been enqueued with the given arguments.
@@ -175,9 +219,10 @@ module ActiveJob
original_enqueued_jobs = enqueued_jobs.dup
clear_enqueued_jobs
args.assert_valid_keys(:job, :args, :at, :queue)
+ serialized_args = serialize_args_for_assertion(args)
yield
matching_job = enqueued_jobs.any? do |job|
- args.all? { |key, value| value == job[key] }
+ serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No enqueued job found with #{args}"
ensure
@@ -195,20 +240,22 @@ module ActiveJob
original_performed_jobs = performed_jobs.dup
clear_performed_jobs
args.assert_valid_keys(:job, :args, :at, :queue)
+ serialized_args = serialize_args_for_assertion(args)
perform_enqueued_jobs { yield }
matching_job = performed_jobs.any? do |job|
- args.all? { |key, value| value == job[key] }
+ serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No performed job found with #{args}"
ensure
queue_adapter.performed_jobs = original_performed_jobs + performed_jobs
end
- def perform_enqueued_jobs
+ def perform_enqueued_jobs(only: nil)
@old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
@old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
queue_adapter.perform_enqueued_jobs = true
queue_adapter.perform_enqueued_at_jobs = true
+ queue_adapter.filter = only
yield
ensure
queue_adapter.perform_enqueued_jobs = @old_perform_enqueued_jobs
@@ -239,6 +286,14 @@ module ActiveJob
enqueued_jobs.size
end
end
+
+ def serialize_args_for_assertion(args)
+ serialized_args = args.dup
+ if job_args = serialized_args.delete(:args)
+ serialized_args[:args] = ActiveJob::Arguments.serialize(job_args)
+ end
+ serialized_args
+ end
end
end
end
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index dbe36fc572..8b9b62190f 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -2,6 +2,7 @@ require 'helper'
require 'active_job/arguments'
require 'models/person'
require 'active_support/core_ext/hash/indifferent_access'
+require 'jobs/kwargs_job'
class ArgumentSerializationTest < ActiveSupport::TestCase
setup do
@@ -31,16 +32,26 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
test 'should convert records to Global IDs' do
- assert_arguments_roundtrip [@person], ['_aj_globalid' => @person.to_gid.to_s]
+ assert_arguments_roundtrip [@person]
end
test 'should dive deep into arrays and hashes' do
- assert_arguments_roundtrip [3, [@person]], [3, ['_aj_globalid' => @person.to_gid.to_s]]
- assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => { '_aj_globalid' => @person.to_gid.to_s }}.with_indifferent_access]
+ assert_arguments_roundtrip [3, [@person]]
+ assert_arguments_roundtrip [{ 'a' => @person }]
end
- test 'should stringify symbol hash keys' do
- assert_equal [ 'a' => 1 ], ActiveJob::Arguments.serialize([ a: 1 ])
+ test 'should maintain string and symbol keys' do
+ assert_arguments_roundtrip([a: 1, "b" => 2])
+ end
+
+ test 'should maintain hash with indifferent access' do
+ symbol_key = { a: 1 }
+ string_key = { 'a' => 1 }
+ indifferent_access = { a: 1 }.with_indifferent_access
+
+ assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([symbol_key]).first
+ assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([string_key]).first
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([indifferent_access]).first
end
test 'should disallow non-string/symbol hash keys' do
@@ -71,14 +82,22 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
end
+ test 'allows for keyword arguments' do
+ KwargsJob.perform_later(argument: 2)
+
+ assert_equal "Job with argument: 2", JobBuffer.last_value
+ end
+
private
def assert_arguments_unchanged(*args)
- assert_arguments_roundtrip args, args
+ assert_arguments_roundtrip args
+ end
+
+ def assert_arguments_roundtrip(args)
+ assert_equal args, perform_round_trip(args)
end
- def assert_arguments_roundtrip(args, expected_serialized_args)
- serialized = ActiveJob::Arguments.serialize(args)
- assert_equal expected_serialized_args, serialized
- assert_equal args, ActiveJob::Arguments.deserialize(serialized)
+ def perform_round_trip(args)
+ ActiveJob::Arguments.deserialize(ActiveJob::Arguments.serialize(args))
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index f34638e7d8..58de2f2588 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -4,6 +4,8 @@ require 'active_support/core_ext/date'
require 'jobs/hello_job'
require 'jobs/logging_job'
require 'jobs/nested_job'
+require 'jobs/rescue_job'
+require 'models/person'
class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_jobs
@@ -175,6 +177,25 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
end
+
+ def test_assert_enqueued_job_with_global_id_args
+ ricardo = Person.new(9)
+ assert_enqueued_with(job: HelloJob, args: [ricardo]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ def test_assert_enqueued_job_failure_with_global_id_args
+ ricardo = Person.new(9)
+ wilma = Person.new(11)
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_with(job: HelloJob, args: [wilma]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ assert_equal "No enqueued job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message
+ end
end
class PerformedJobsTest < ActiveJob::TestCase
@@ -263,6 +284,83 @@ class PerformedJobsTest < ActiveJob::TestCase
assert_match(/0 .* but 1/, error.message)
end
+ def test_assert_performed_jobs_with_only_option
+ assert_nothing_raised do
+ assert_performed_jobs 1, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_performed_jobs_with_only_option_as_array
+ assert_nothing_raised do
+ assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later('stewie')
+ RescueJob.perform_later('david')
+ end
+ end
+ end
+
+ def test_assert_performed_jobs_with_only_option_and_none_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 1, only: HelloJob do
+ LoggingJob.perform_later
+ end
+ end
+
+ assert_match(/1 .* but 0/, error.message)
+ end
+
+ def test_assert_performed_jobs_with_only_option_and_too_few_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 5, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ 4.times { LoggingJob.perform_later }
+ end
+ end
+
+ assert_match(/5 .* but 1/, error.message)
+ end
+
+ def test_assert_performed_jobs_with_only_option_and_too_many_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 1, only: HelloJob do
+ 2.times { HelloJob.perform_later('jeremy') }
+ end
+ end
+
+ assert_match(/1 .* but 2/, error.message)
+ end
+
+ def test_assert_no_performed_jobs_with_only_option
+ assert_nothing_raised do
+ assert_no_performed_jobs only: HelloJob do
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_no_performed_jobs_with_only_option_as_array
+ assert_nothing_raised do
+ assert_no_performed_jobs only: [HelloJob, RescueJob] do
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_no_performed_jobs_with_only_option_failure
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_no_performed_jobs only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+
+ assert_match(/0 .* but 1/, error.message)
+ end
+
def test_assert_performed_job
assert_performed_with(job: NestedJob, queue: 'default') do
NestedJob.perform_later
@@ -282,4 +380,23 @@ class PerformedJobsTest < ActiveJob::TestCase
end
end
end
+
+ def test_assert_performed_job_with_global_id_args
+ ricardo = Person.new(9)
+ assert_performed_with(job: HelloJob, args: [ricardo]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ def test_assert_performed_job_failure_with_global_id_args
+ ricardo = Person.new(9)
+ wilma = Person.new(11)
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_with(job: HelloJob, args: [wilma]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ assert_equal "No performed job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message
+ end
end
diff --git a/activejob/test/jobs/kwargs_job.rb b/activejob/test/jobs/kwargs_job.rb
new file mode 100644
index 0000000000..2df17d15ae
--- /dev/null
+++ b/activejob/test/jobs/kwargs_job.rb
@@ -0,0 +1,7 @@
+require_relative '../support/job_buffer'
+
+class KwargsJob < ActiveJob::Base
+ def perform(argument: 1)
+ JobBuffer.add("Job with argument: #{argument}")
+ end
+end
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
index 9bd45e09e8..39e41b6d29 100644
--- a/activejob/test/support/integration/helper.rb
+++ b/activejob/test/support/integration/helper.rb
@@ -20,7 +20,7 @@ require 'rails/test_help'
Rails.backtrace_cleaner.remove_silencers!
require_relative 'test_case_helpers'
-ActiveSupport::TestCase.send(:include, TestCaseHelpers)
+ActiveSupport::TestCase.include(TestCaseHelpers)
JobsManager.current_manager.start_workers
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index fb4b972282..c21083698e 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,7 +1,68 @@
+* Deprecate `ActiveModel::Errors#add_on_empty` and `ActiveModel::Errors#add_on_blank`
+ with no replacement.
+
+ *Wojciech Wnętrzak*
+
+* Deprecate `ActiveModel::Errors#get`, `ActiveModel::Errors#set` and
+ `ActiveModel::Errors#[]=` methods that have inconsistent behaviour.
+
+ *Wojciech Wnętrzak*
+
+* Allow symbol as values for `tokenize` of `LengthValidator`
+
+ *Kensuke Naito*
+
+* Assigning an unknown attribute key to an `ActiveModel` instance during initialization
+ will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of
+ `NoMethodError`
+
+ Example:
+
+ User.new(foo: 'some value')
+ # => ActiveModel::AttributeAssignment::UnknownAttributeError: unknown attribute 'foo' for User.
+
+ *Eugene Gilburg*
+
+* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment`
+ allowing to use it for any object as an includable module.
+
+ Example:
+
+ class Cat
+ include ActiveModel::AttributeAssignment
+ attr_accessor :name, :status
+ end
+
+ cat = Cat.new
+ cat.assign_attributes(name: "Gorby", status: "yawning")
+ cat.name # => 'Gorby'
+ cat.status => 'yawning'
+ cat.assign_attributes(status: "sleeping")
+ cat.name # => 'Gorby'
+ cat.status => 'sleeping'
+
+ *Bogdan Gusiev*
+
+* Add `ActiveModel::Errors#details`
+
+ To be able to return type of used validator, one can now call `details`
+ on errors instance.
+
+ Example:
+
+ class User < ActiveRecord::Base
+ validates :name, presence: true
+ end
+
+ user = User.new; user.valid?; user.errors.details
+ => {name: [{error: :blank}]}
+
+ *Wojciech Wnętrzak*
+
* Change validates_acceptance_of to accept true by default.
The default for validates_acceptance_of is now "1" and true.
- In the past, only "1" was the default and you were required to add
+ In the past, only "1" was the default and you were required to add
accept: true.
* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index f6beff14e1..0985f56bfb 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -219,7 +219,7 @@ behavior out of the box:
class HasNameValidator < ActiveModel::Validator
def validate(record)
- record.errors[:name] = "must exist" if record.name.blank?
+ record.errors.messages[:name] << "must exist" if record.name.blank?
end
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 46d60db756..58fe08cc11 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -28,6 +28,7 @@ require 'active_model/version'
module ActiveModel
extend ActiveSupport::Autoload
+ autoload :AttributeAssignment
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
new file mode 100644
index 0000000000..356421476c
--- /dev/null
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -0,0 +1,63 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActiveModel
+ module AttributeAssignment
+ include ActiveModel::ForbiddenAttributesProtection
+
+ # Allows you to set all the attributes by passing in a hash of attributes with
+ # keys matching the attribute names.
+ #
+ # If the passed hash responds to <tt>permitted?</tt> method and the return value
+ # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
+ # exception is raised.
+ #
+ # class Cat
+ # include ActiveModel::AttributeAssignment
+ # attr_accessor :name, :status
+ # end
+ #
+ # cat = Cat.new
+ # cat.assign_attributes(name: "Gorby", status: "yawning")
+ # cat.name # => 'Gorby'
+ # cat.status => 'yawning'
+ # cat.assign_attributes(status: "sleeping")
+ # cat.name # => 'Gorby'
+ # cat.status => 'sleeping'
+ def assign_attributes(new_attributes)
+ if !new_attributes.respond_to?(:stringify_keys)
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
+ end
+ return if new_attributes.blank?
+
+ attributes = new_attributes.stringify_keys
+ _assign_attributes(sanitize_for_mass_assignment(attributes))
+ end
+
+ private
+
+ def _assign_attributes(attributes)
+ attributes.each do |k, v|
+ _assign_attribute(k, v)
+ end
+ end
+
+ def _assign_attribute(k, v)
+ if respond_to?("#{k}=")
+ public_send("#{k}=", v)
+ else
+ raise UnknownAttributeError.new(self, k)
+ end
+ end
+
+ # Raised when unknown attributes are supplied via mass assignment.
+ class UnknownAttributeError < NoMethodError
+ attr_reader :record, :attribute
+
+ def initialize(record, attribute)
+ @record = record
+ @attribute = attribute
+ super("unknown attribute '#{attribute}' for #{@record.class}.")
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 6214802074..2cf39b68fb 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -49,7 +49,7 @@ module ActiveModel
# puts 'block successfully called.'
# end
#
- # You can choose not to have all three callbacks by passing a hash to the
+ # You can choose to have only specific callbacks by passing a hash to the
# +define_model_callbacks+ method.
#
# define_model_callbacks :create, only: [:after, :before]
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 614bc6a5d8..c03e5fac79 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -52,10 +52,10 @@ module ActiveModel
# end
# end
#
- # A newly instantiated object is unchanged:
+ # A newly instantiated +Person+ object is unchanged:
#
- # person = Person.find_by(name: 'Uncle Bob')
- # person.changed? # => false
+ # person = Person.new
+ # person.changed? # => false
#
# Change the name:
#
@@ -71,8 +71,8 @@ module ActiveModel
# Save the changes:
#
# person.save
- # person.changed? # => false
- # person.name_changed? # => false
+ # person.changed? # => false
+ # person.name_changed? # => false
#
# Reset the changes:
#
@@ -84,20 +84,20 @@ module ActiveModel
#
# person.name = "Uncle Bob"
# person.rollback!
- # person.name # => "Bill"
- # person.name_changed? # => false
+ # person.name # => "Bill"
+ # person.name_changed? # => false
#
# Assigning the same value leaves the attribute unchanged:
#
# person.name = 'Bill'
- # person.name_changed? # => false
- # person.name_change # => nil
+ # person.name_changed? # => false
+ # person.name_change # => nil
#
# Which attributes have changed?
#
# person.name = 'Bob'
- # person.changed # => ["name"]
- # person.changes # => {"name" => ["Bill", "Bob"]}
+ # person.changed # => ["name"]
+ # person.changes # => {"name" => ["Bill", "Bob"]}
#
# If an attribute is modified in-place then make use of
# +[attribute_name]_will_change!+ to mark that the attribute is changing.
@@ -106,9 +106,9 @@ module ActiveModel
# not need to call +[attribute_name]_will_change!+ on Active Record models.
#
# person.name_will_change!
- # person.name_change # => ["Bill", "Bill"]
+ # person.name_change # => ["Bill", "Bill"]
# person.name << 'y'
- # person.name_change # => ["Bill", "Billy"]
+ # person.name_change # => ["Bill", "Billy"]
module Dirty
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
@@ -118,7 +118,7 @@ module ActiveModel
attribute_method_affix prefix: 'restore_', suffix: '!'
end
- # Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
+ # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
#
# person.changed? # => false
# person.name = 'bob'
@@ -166,7 +166,7 @@ module ActiveModel
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
end
- # Handle <tt>*_changed?</tt> for +method_missing+.
+ # Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, options = {}) #:nodoc:
result = changes_include?(attr)
result &&= options[:to] == __send__(attr) if options.key?(:to)
@@ -174,7 +174,7 @@ module ActiveModel
result
end
- # Handle <tt>*_was</tt> for +method_missing+.
+ # Handles <tt>*_was</tt> for +method_missing+.
def attribute_was(attr) # :nodoc:
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
@@ -186,6 +186,7 @@ module ActiveModel
private
+ # Returns +true+ if attr_name is changed, +false+ otherwise.
def changes_include?(attr_name)
attributes_changed_by_setter.include?(attr_name)
end
@@ -197,18 +198,18 @@ module ActiveModel
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Clear all dirty data: current changes and previous changes.
+ # Clears all dirty data: current changes and previous changes.
def clear_changes_information # :doc:
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Handle <tt>*_change</tt> for +method_missing+.
+ # Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
- # Handle <tt>*_will_change!</tt> for +method_missing+.
+ # Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
@@ -221,7 +222,7 @@ module ActiveModel
set_attribute_was(attr, value)
end
- # Handle <tt>restore_*!</tt> for +method_missing+.
+ # Handles <tt>restore_*!</tt> for +method_missing+.
def restore_attribute!(attr)
if attribute_changed?(attr)
__send__("#{attr}=", changed_attributes[attr])
@@ -230,7 +231,7 @@ module ActiveModel
end
# This is necessary because `changed_attributes` might be overridden in
- # other implemntations (e.g. in `ActiveRecord`)
+ # other implementations (e.g. in `ActiveRecord`)
alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
# Force an attribute to have a particular "before" value
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 477edbd120..166911f0fa 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -2,6 +2,8 @@
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/object/deep_dup'
+require 'active_support/deprecation'
module ActiveModel
# == Active \Model \Errors
@@ -23,7 +25,7 @@ module ActiveModel
# attr_reader :errors
#
# def validate!
- # errors.add(:name, "cannot be nil") if name.nil?
+ # errors.add(:name, :blank, message: "cannot be nil") if name.nil?
# end
#
# # The following methods are needed to be minimally implemented
@@ -58,8 +60,9 @@ module ActiveModel
include Enumerable
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
+ MESSAGE_OPTIONS = [:message]
- attr_reader :messages
+ attr_reader :messages, :details
# Pass in the instance of the object that is using the errors object.
#
@@ -70,11 +73,13 @@ module ActiveModel
# end
def initialize(base)
@base = base
- @messages = {}
+ @messages = Hash.new { |messages, attribute| messages[attribute] = [] }
+ @details = Hash.new { |details, attribute| details[attribute] = [] }
end
def initialize_dup(other) # :nodoc:
@messages = other.messages.dup
+ @details = other.details.deep_dup
super
end
@@ -85,6 +90,7 @@ module ActiveModel
# person.errors.full_messages # => []
def clear
messages.clear
+ details.clear
end
# Returns +true+ if the error messages include an error for the given key
@@ -105,8 +111,14 @@ module ActiveModel
#
# person.errors.messages # => {:name=>["cannot be nil"]}
# person.errors.get(:name) # => ["cannot be nil"]
- # person.errors.get(:age) # => nil
+ # person.errors.get(:age) # => []
def get(key)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#get is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use messages[:#{key}]
+ MESSAGE
+
messages[key]
end
@@ -116,6 +128,12 @@ module ActiveModel
# person.errors.set(:name, ["can't be nil"])
# person.errors.get(:name) # => ["can't be nil"]
def set(key, value)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#set is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use messages[:#{key}] = "#{value}"
+ MESSAGE
+
messages[key] = value
end
@@ -123,9 +141,10 @@ module ActiveModel
#
# person.errors.get(:name) # => ["cannot be nil"]
# person.errors.delete(:name) # => ["cannot be nil"]
- # person.errors.get(:name) # => nil
+ # person.errors.get(:name) # => []
def delete(key)
messages.delete(key)
+ details.delete(key)
end
# When passed a symbol or a name of a method, returns an array of errors
@@ -134,7 +153,7 @@ module ActiveModel
# person.errors[:name] # => ["cannot be nil"]
# person.errors['name'] # => ["cannot be nil"]
def [](attribute)
- get(attribute.to_sym) || set(attribute.to_sym, [])
+ messages[attribute.to_sym]
end
# Adds to the supplied attribute the supplied error message.
@@ -142,34 +161,40 @@ module ActiveModel
# person.errors[:name] = "must be set"
# person.errors[:name] # => ['must be set']
def []=(attribute, error)
- self[attribute] << error
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#[]= is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use messages[:#{attribute}] << "#{error}"
+ MESSAGE
+
+ messages[attribute.to_sym] << error
end
# Iterates through each error key, value pair in the error messages hash.
# Yields the attribute and the error for that attribute. If the attribute
# has more than one error message, yields once for each error message.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
def each
messages.each_key do |attribute|
- self[attribute].each { |error| yield attribute, error }
+ messages[attribute].each { |error| yield attribute, error }
end
end
# Returns the number of error messages.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.size # => 1
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.size # => 2
def size
values.flatten.size
@@ -193,8 +218,8 @@ module ActiveModel
# Returns an array of error messages, with the attribute name included.
#
- # person.errors.add(:name, "can't be blank")
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :blank, message: "can't be blank")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
def to_a
full_messages
@@ -202,9 +227,9 @@ module ActiveModel
# Returns the number of error messages.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.count # => 1
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.count # => 2
def count
to_a.size
@@ -223,8 +248,8 @@ module ActiveModel
# Returns an xml formatted representation of the Errors hash.
#
- # person.errors.add(:name, "can't be blank")
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :blank, message: "can't be blank")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.to_xml
# # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
@@ -261,17 +286,20 @@ module ActiveModel
end
end
- # Adds +message+ to the error messages on +attribute+. More than one error
- # can be added to the same +attribute+. If no +message+ is supplied,
- # <tt>:invalid</tt> is assumed.
+ # Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
+ # More than one error can be added to the same +attribute+.
+ # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
# person.errors.add(:name)
# # => ["is invalid"]
- # person.errors.add(:name, 'must be implemented')
+ # person.errors.add(:name, :not_implemented, message: "must be implemented")
# # => ["is invalid", "must be implemented"]
#
# person.errors.messages
- # # => {:name=>["must be implemented", "is invalid"]}
+ # # => {:name=>["is invalid", "must be implemented"]}
+ #
+ # person.errors.details
+ # # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
#
# If +message+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
@@ -293,17 +321,23 @@ module ActiveModel
# +attribute+ should be set to <tt>:base</tt> if the error is not
# directly associated with a single attribute.
#
- # person.errors.add(:base, "either name or email must be present")
+ # person.errors.add(:base, :name_or_email_blank,
+ # message: "either name or email must be present")
# person.errors.messages
# # => {:base=>["either name or email must be present"]}
+ # person.errors.details
+ # # => {:base=>[{error: :name_or_email_blank}]}
def add(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
+ detail = normalize_detail(attribute, message, options)
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
raise exception, full_message(attribute, message)
end
- self[attribute] << message
+ details[attribute.to_sym] << detail
+ messages[attribute.to_sym] << message
end
# Will add an error message to each of the attributes in +attributes+
@@ -313,6 +347,14 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be empty"]}
def add_on_empty(attributes, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use:
+
+ errors.add(attribute, :empty, options) if value.nil? || value.empty?
+ MESSAGE
+
Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
is_empty = value.respond_to?(:empty?) ? value.empty? : false
@@ -327,6 +369,14 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be blank"]}
def add_on_blank(attributes, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use:
+
+ errors.add(attribute, :empty, options) if value.blank?
+ MESSAGE
+
Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
add(attribute, :blank, options) if value.blank?
@@ -339,6 +389,7 @@ module ActiveModel
# person.errors.add :name, :blank
# person.errors.added? :name, :blank # => true
def added?(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
message = normalize_message(attribute, message, options)
self[attribute].include? message
end
@@ -368,7 +419,7 @@ module ActiveModel
# person.errors.full_messages_for(:name)
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
def full_messages_for(attribute)
- (get(attribute) || []).map { |message| full_message(attribute, message) }
+ messages[attribute].map { |message| full_message(attribute, message) }
end
# Returns a full message for a given attribute.
@@ -447,12 +498,14 @@ module ActiveModel
case message
when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- when Proc
- message.call
else
message
end
end
+
+ def normalize_detail(attribute, message, options)
+ { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
+ end
end
# Raised when a validation cannot be corrected by end users and are considered
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 38087521a2..010eaeb170 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -21,28 +21,27 @@ module ActiveModel
# +self+.
module Tests
- # == Responds to <tt>to_key</tt>
+ # Passes if the object's model responds to <tt>to_key</tt> and if calling
+ # this method returns +nil+ when the object is not persisted.
+ # Fails otherwise.
#
- # Returns an Enumerable of all (primary) key attributes
- # or nil if <tt>model.persisted?</tt> is false. This is used by
- # <tt>dom_id</tt> to generate unique ids for the object.
+ # <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
+ # of the model, and is used to a generate unique DOM id for the object.
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
end
- # == Responds to <tt>to_param</tt>
- #
- # Returns a string representing the object's key suitable for use in URLs
- # or +nil+ if <tt>model.persisted?</tt> is +false+.
+ # Passes if the object's model responds to <tt>to_param</tt> and if
+ # calling this method returns +nil+ when the object is not persisted.
+ # Fails otherwise.
#
+ # <tt>to_param</tt> is used to represent the object's key in URLs.
# Implementers can decide to either raise an exception or provide a
# default in case the record uses a composite primary key. There are no
# tests for this behavior in lint because it doesn't make sense to force
# any of the possible implementation strategies on the implementer.
- # However, if the resource is not persisted?, then <tt>to_param</tt>
- # should always return +nil+.
def test_to_param
assert model.respond_to?(:to_param), "The model should respond to to_param"
def model.to_key() [1] end
@@ -50,32 +49,34 @@ module ActiveModel
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
end
- # == Responds to <tt>to_partial_path</tt>
+ # Passes if the object's model responds to <tt>to_partial_path</tt> and if
+ # calling this method returns a string. Fails otherwise.
#
- # Returns a string giving a relative path. This is used for looking up
- # partials. For example, a BlogPost model might return "blog_posts/blog_post"
+ # <tt>to_partial_path</tt> is used for looking up partials. For example,
+ # a BlogPost model might return "blog_posts/blog_post".
def test_to_partial_path
assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
assert_kind_of String, model.to_partial_path
end
- # == Responds to <tt>persisted?</tt>
+ # Passes if the object's model responds to <tt>persisted?</tt> and if
+ # calling this method returns either +true+ or +false+. Fails otherwise.
#
- # Returns a boolean that specifies whether the object has been persisted
- # yet. This is used when calculating the URL for an object. If the object
- # is not persisted, a form for that object, for instance, will route to
- # the create action. If it is persisted, a form for the object will routes
- # to the update action.
+ # <tt>persisted?</tt> is used when calculating the URL for an object.
+ # If the object is not persisted, a form for that object, for instance,
+ # will route to the create action. If it is persisted, a form for the
+ # object will route to the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
end
- # == \Naming
+ # Passes if the object's model responds to <tt>model_name</tt> both as
+ # an instance method and as a class method, and if calling this method
+ # returns a string with some convenience methods: <tt>:human</tt>,
+ # <tt>:singular</tt> and <tt>:plural</tt>.
#
- # Model.model_name and Model#model_name must return a string with some
- # convenience methods: # <tt>:human</tt>, <tt>:singular</tt> and
- # <tt>:plural</tt>. Check ActiveModel::Naming for more information.
+ # Check ActiveModel::Naming for more information.
def test_model_naming
assert model.class.respond_to?(:model_name), "The model class should respond to model_name"
model_name = model.class.model_name
@@ -88,12 +89,15 @@ module ActiveModel
assert_equal model.model_name, model.class.model_name
end
- # == \Errors Testing
+ # Passes if the object's model responds to <tt>errors</tt> and if calling
+ # <tt>[](attribute)</tt> on the result of this method returns an array.
+ # Fails otherwise.
#
- # Returns an object that implements [](attribute) defined which returns an
- # Array of Strings that are the errors for the attribute in question.
- # If localization is used, the Strings should be localized for the current
- # locale. If no error is present, this method should return an empty Array.
+ # <tt>errors[attribute]</tt> is used to retrieve the errors of a model
+ # for a given attribute. If errors are present, the method should return
+ # an array of strings that are the errors for the attribute in question.
+ # If localization is used, the strings should be localized for the current
+ # locale. If no error is present, the method should return an empty array.
def test_errors_aref
assert model.respond_to?(:errors), "The model should respond to errors"
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index bf07945fe1..061e35dd1e 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -6,6 +6,7 @@ en:
# The values :model, :attribute and :value are always available for interpolation
# The value :count is available when applicable. Can be used for pluralization.
messages:
+ model_invalid: "Validation failed: %{errors}"
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
@@ -16,7 +17,7 @@ en:
present: "must be blank"
too_long:
one: "is too long (maximum is 1 character)"
- other: "is too long (maximum is %{count} characters)"
+ other: "is too long (maximum is %{count} characters)"
too_short:
one: "is too short (minimum is 1 character)"
other: "is too short (minimum is %{count} characters)"
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index d51d6ddcc9..dac8d549a7 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -57,6 +57,7 @@ module ActiveModel
# (see below).
module Model
extend ActiveSupport::Concern
+ include ActiveModel::AttributeAssignment
include ActiveModel::Validations
include ActiveModel::Conversion
@@ -75,10 +76,8 @@ module ActiveModel
# person = Person.new(name: 'bob', age: '18')
# person.name # => "bob"
# person.age # => "18"
- def initialize(params={})
- params.each do |attr, value|
- self.public_send("#{attr}=", value)
- end if params
+ def initialize(attributes={})
+ assign_attributes(attributes) if attributes
super()
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index ada1f9a4f3..2bc3eeaa19 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
+require 'active_support/core_ext/module/remove_method'
module ActiveModel
class Name
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 6a2668b8f7..176d4c0607 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -371,6 +371,15 @@ module ActiveModel
!valid?(context)
end
+ # Runs all the validations within the specified context. Returns +true+ if
+ # no errors are found, raises +ValidationError+ otherwise.
+ #
+ # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
+ # some <tt>:on</tt> option will only run in the specified context.
+ def validate!(context = nil)
+ valid?(context) || raise_validation_error
+ end
+
# Hook method defining how an attribute value should be retrieved. By default
# this is assumed to be an instance named after the attribute. Override this
# method in subclasses should you need to retrieve the value for a given
@@ -395,6 +404,30 @@ module ActiveModel
_run_validate_callbacks
errors.empty?
end
+
+ def raise_validation_error
+ raise(ValidationError.new(self))
+ end
+ end
+
+ # = Active Model ValidationError
+ #
+ # Raised by <tt>validate!</tt> when the model is invalid. Use the
+ # +model+ method to retrieve the record which did not validate.
+ #
+ # begin
+ # complex_operation_that_internally_calls_validate!
+ # rescue ActiveModel::ValidationError => invalid
+ # puts invalid.model.errors
+ # end
+ class ValidationError < StandardError
+ attr_reader :model
+
+ def initialize(model)
+ @model = model
+ errors = @model.errors.full_messages.join(", ")
+ super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 02478dd5b6..46a2e54fba 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -77,7 +77,7 @@ module ActiveModel
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
# end
#
- # Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
+ # Note: use <tt>\A</tt> and <tt>\z</tt> to match the start and end of the
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index a96b30cadd..23201b264a 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -38,7 +38,7 @@ module ActiveModel
end
def validate_each(record, attribute, value)
- value = tokenize(value)
+ value = tokenize(record, value)
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
errors_options = options.except(*RESERVED_OPTIONS)
@@ -59,10 +59,14 @@ module ActiveModel
end
private
-
- def tokenize(value)
- if options[:tokenizer] && value.kind_of?(String)
- options[:tokenizer].call(value)
+ def tokenize(record, value)
+ tokenizer = options[:tokenizer]
+ if tokenizer && value.kind_of?(String)
+ if tokenizer.kind_of?(Proc)
+ tokenizer.call(value)
+ elsif record.respond_to?(tokenizer)
+ record.send(tokenizer, value)
+ end
end || value
end
@@ -108,10 +112,12 @@ module ActiveModel
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
- # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
- # (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
- # as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
- # which counts individual characters.
+ # * <tt>:tokenizer</tt> - A method (as a symbol), proc or string to
+ # specify how to split up the attribute string. (e.g.
+ # <tt>tokenizer: :word_tokenizer</tt> to call the +word_tokenizer+ method
+ # or <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words as in
+ # above example). Defaults to <tt>->(value) { value.split(//) }</tt> which
+ # counts individual characters.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+ and +:strict+.
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index ac32750946..5752771d8c 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -15,7 +15,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
- # record.errors[:base] = "This record is invalid"
+ # record.errors.messages[:base] << "This record is invalid"
# end
# end
#
@@ -163,6 +163,10 @@ module ActiveModel
# +ArgumentError+ when invalid options are supplied.
def check_validity!
end
+
+ def should_validate?(record) # :nodoc:
+ !record.persisted? || record.changed? || record.marked_for_destruction?
+ end
end
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
new file mode 100644
index 0000000000..402caf21f7
--- /dev/null
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -0,0 +1,107 @@
+require "cases/helper"
+require "active_support/hash_with_indifferent_access"
+
+class AttributeAssignmentTest < ActiveModel::TestCase
+ class Model
+ include ActiveModel::AttributeAssignment
+
+ attr_accessor :name, :description
+
+ def initialize(attributes = {})
+ assign_attributes(attributes)
+ end
+
+ def broken_attribute=(value)
+ raise ErrorFromAttributeWriter
+ end
+
+ protected
+
+ attr_writer :metadata
+ end
+
+ class ErrorFromAttributeWriter < StandardError
+ end
+
+ class ProtectedParams < ActiveSupport::HashWithIndifferentAccess
+ def permit!
+ @permitted = true
+ end
+
+ def permitted?
+ @permitted ||= false
+ end
+
+ def dup
+ super.tap do |duplicate|
+ duplicate.instance_variable_set :@permitted, permitted?
+ end
+ end
+ end
+
+ test "simple assignment" do
+ model = Model.new
+
+ model.assign_attributes(name: "hello", description: "world")
+ assert_equal "hello", model.name
+ assert_equal "world", model.description
+ end
+
+ test "assign non-existing attribute" do
+ model = Model.new
+ error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do
+ model.assign_attributes(hz: 1)
+ end
+
+ assert_equal model, error.record
+ assert_equal "hz", error.attribute
+ end
+
+ test "assign private attribute" do
+ model = Model.new
+ assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do
+ model.assign_attributes(metadata: { a: 1 })
+ end
+ end
+
+ test "does not swallow errors raised in an attribute writer" do
+ assert_raises(ErrorFromAttributeWriter) do
+ Model.new(broken_attribute: 1)
+ end
+ end
+
+ test "an ArgumentError is raised if a non-hash-like obejct is passed" do
+ assert_raises(ArgumentError) do
+ Model.new(1)
+ end
+ end
+
+ test "forbidden attributes cannot be used for mass assignment" do
+ params = ProtectedParams.new(name: "Guille", description: "m")
+
+ assert_raises(ActiveModel::ForbiddenAttributesError) do
+ Model.new(params)
+ end
+ end
+
+ test "permitted attributes can be used for mass assignment" do
+ params = ProtectedParams.new(name: "Guille", description: "desc")
+ params.permit!
+ model = Model.new(params)
+
+ assert_equal "Guille", model.name
+ assert_equal "desc", model.description
+ end
+
+ test "regular hash should still be used for mass assignment" do
+ model = Model.new(name: "Guille", description: "m")
+
+ assert_equal "Guille", model.name
+ assert_equal "m", model.description
+ end
+
+ test "assigning no attributes should not raise, even if the hash is un-permitted" do
+ model = Model.new
+ assert_nil model.assign_attributes(ProtectedParams.new({}))
+ end
+end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index efedd9055f..da142ea2c0 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -29,28 +29,28 @@ class ErrorsTest < ActiveModel::TestCase
def test_delete
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
errors.delete(:foo)
assert_empty errors[:foo]
end
def test_include?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert errors.include?(:foo), 'errors should include :foo'
end
def test_dup
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'bar'
+ errors[:foo] << 'bar'
errors_dup = errors.dup
- errors_dup[:bar] = 'omg'
+ errors_dup[:bar] << 'omg'
assert_not_same errors_dup.messages, errors.messages
end
def test_has_key?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert_equal true, errors.has_key?(:foo), 'errors should have key :foo'
end
@@ -61,7 +61,7 @@ class ErrorsTest < ActiveModel::TestCase
def test_key?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert_equal true, errors.key?(:foo), 'errors should have key :foo'
end
@@ -81,37 +81,41 @@ class ErrorsTest < ActiveModel::TestCase
test "get returns the errors for the provided key" do
errors = ActiveModel::Errors.new(self)
- errors[:foo] = "omg"
+ errors[:foo] << "omg"
- assert_equal ["omg"], errors.get(:foo)
+ assert_deprecated do
+ assert_equal ["omg"], errors.get(:foo)
+ end
end
test "sets the error with the provided key" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
+ assert_deprecated do
+ errors.set(:foo, "omg")
+ end
assert_equal({ foo: "omg" }, errors.messages)
end
test "error access is indifferent" do
errors = ActiveModel::Errors.new(self)
- errors[:foo] = "omg"
+ errors[:foo] << "omg"
assert_equal ["omg"], errors["foo"]
end
test "values returns an array of messages" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
- errors.set(:baz, "zomg")
+ errors.messages[:foo] = "omg"
+ errors.messages[:baz] = "zomg"
assert_equal ["omg", "zomg"], errors.values
end
test "keys returns the error keys" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
- errors.set(:baz, "zomg")
+ errors.messages[:foo] << "omg"
+ errors.messages[:baz] << "zomg"
assert_equal [:foo, :baz], errors.keys
end
@@ -133,7 +137,9 @@ class ErrorsTest < ActiveModel::TestCase
test "assign error" do
person = Person.new
- person.errors[:name] = 'should not be nil'
+ assert_deprecated do
+ person.errors[:name] = 'should not be nil'
+ end
assert_equal ["should not be nil"], person.errors[:name]
end
@@ -282,45 +288,101 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :empty, {})
- person.errors.add_on_empty :name
+ assert_deprecated do
+ person.errors.add_on_empty :name
+ end
end
test "add_on_empty generates message for multiple attributes" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :empty, {})
person.errors.expects(:generate_message).with(:age, :empty, {})
- person.errors.add_on_empty [:name, :age]
+ assert_deprecated do
+ person.errors.add_on_empty [:name, :age]
+ end
end
test "add_on_empty generates message with custom default message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :empty, { message: 'custom' })
- person.errors.add_on_empty :name, message: 'custom'
+ assert_deprecated do
+ person.errors.add_on_empty :name, message: 'custom'
+ end
end
test "add_on_empty generates message with empty string value" do
person = Person.new
person.name = ''
person.errors.expects(:generate_message).with(:name, :empty, {})
- person.errors.add_on_empty :name
+ assert_deprecated do
+ person.errors.add_on_empty :name
+ end
end
test "add_on_blank generates message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :blank, {})
- person.errors.add_on_blank :name
+ assert_deprecated do
+ person.errors.add_on_blank :name
+ end
end
test "add_on_blank generates message for multiple attributes" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :blank, {})
person.errors.expects(:generate_message).with(:age, :blank, {})
- person.errors.add_on_blank [:name, :age]
+ assert_deprecated do
+ person.errors.add_on_blank [:name, :age]
+ end
end
test "add_on_blank generates message with custom default message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' })
- person.errors.add_on_blank :name, message: 'custom'
+ assert_deprecated do
+ person.errors.add_on_blank :name, message: 'custom'
+ end
+ end
+
+ test "details returns added error detail" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+ assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
+ end
+
+ test "details returns added error detail with custom option" do
+ person = Person.new
+ person.errors.add(:name, :greater_than, count: 5)
+ assert_equal({ name: [{ error: :greater_than, count: 5 }] }, person.errors.details)
+ end
+
+ test "details do not include message option" do
+ person = Person.new
+ person.errors.add(:name, :invalid, message: "is bad")
+ assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
+ end
+
+ test "dup duplicates details" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ errors_dup = errors.dup
+ errors_dup.add(:name, :taken)
+ assert_not_equal errors_dup.details, errors.details
+ end
+
+ test "delete removes details on given attribute" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ errors.delete(:name)
+ assert_empty errors.details[:name]
+ end
+
+ test "clear removes details" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+
+ assert_equal 1, person.errors.details.count
+ person.errors.clear
+ assert person.errors.details.empty?
end
end
diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb
index ee0fa26546..9a8d873ec9 100644
--- a/activemodel/test/cases/model_test.rb
+++ b/activemodel/test/cases/model_test.rb
@@ -70,6 +70,8 @@ class ModelTest < ActiveModel::TestCase
end
def test_mixin_initializer_when_args_dont_exist
- assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') }
+ assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do
+ SimpleModel.new(hello: 'world')
+ end
end
end
diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb
index ebfe1cf4e4..9cbc77dfb5 100644
--- a/activemodel/test/cases/validations/absence_validation_test.rb
+++ b/activemodel/test/cases/validations/absence_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
require 'models/person'
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index b7872ea6bf..9c2114d83d 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index cc50ffbbef..75eb18e795 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
class Dog
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 1261937b56..296d3b4407 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index 65a2a1eb49..c1431548f7 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index 1ce41f9bc9..b269c3691a 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 0f91b73cd7..86bbbe6ebe 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 3eeb80a48b..da63df9152 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -62,7 +62,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
assert_equal 'custom message', @person.errors.generate_message(:title, :empty, message: 'custom message')
end
- # add_on_blank: generate_message(attr, :blank, message: custom_message)
+ # validates_presence_of: generate_message(attr, :blank, message: custom_message)
def test_generate_message_blank_with_default_message
assert_equal "can't be blank", @person.errors.generate_message(:title, :blank)
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 96084a32ba..70ee7afecc 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
require "cases/helper"
require 'models/person'
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 3a8f3080e1..55d1fb4dcb 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'active_support/all'
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 046ffcb16f..209903898e 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
@@ -331,6 +330,19 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
end
+
+ def test_validates_length_of_with_symbol
+ Topic.validates_length_of :content, minimum: 5, too_short: "Your essay must be at least %{count} words.",
+ tokenizer: :my_word_tokenizer
+ t = Topic.new(content: "this content should be long enough")
+ assert t.valid?
+
+ t.content = "not long enough"
+ assert t.invalid?
+ assert t.errors[:content].any?
+ assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
+ end
+
def test_validates_length_of_for_fixnum
Topic.validates_length_of(:approved, is: 4)
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 12a22f9c40..05432abaff 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index ecf16d1e16..59b9db0795 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 8d4b74ee49..04101f3545 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/person'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb
index 005bf118c6..150dce379f 100644
--- a/activemodel/test/cases/validations/validations_context_test.rb
+++ b/activemodel/test/cases/validations/validations_context_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 736c2deea8..01804032f0 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 2b932683ea..f0317ad219 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
@@ -352,6 +351,25 @@ class ValidationsTest < ActiveModel::TestCase
assert_not_empty topic.errors
end
+ def test_validate_with_bang
+ Topic.validates :title, presence: true
+
+ assert_raise(ActiveModel::ValidationError) do
+ Topic.new.validate!
+ end
+ end
+
+ def test_validate_with_bang_and_context
+ Topic.validates :title, presence: true, on: :context
+
+ assert_raise(ActiveModel::ValidationError) do
+ Topic.new.validate!(:context)
+ end
+
+ t = Topic.new(title: "Valid title")
+ assert t.validate!(:context)
+ end
+
def test_strict_validation_in_validates
Topic.validates :title, strict: true, presence: true
assert_raises ActiveModel::StrictValidationFailed do
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
index 1411a093e9..fed50bc361 100644
--- a/activemodel/test/models/topic.rb
+++ b/activemodel/test/models/topic.rb
@@ -37,4 +37,8 @@ class Topic
errors.add attr, "is missing" unless send(attr)
end
+ def my_word_tokenizer(str)
+ str.scan(/\w+/)
+ end
+
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1be5245e83..52aaa2371d 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,258 @@
+* Fixed ActiveRecord::Relation#becomes! and changed_attributes issues for type column
+
+ Fixes #17139.
+
+ *Miklos Fazekas*
+
+* Format the time string according to the precision of the time column.
+
+ *Ryuta Kamizono*
+
+* Allow `:precision` option for time type columns.
+
+ *Ryuta Kamizono*
+
+* Add `ActiveRecord::Base.suppress` to prevent the receiver from being saved
+ during the given block.
+
+ For example, here's a pattern of creating notifications when new comments
+ are posted. (The notification may in turn trigger an email, a push
+ notification, or just appear in the UI somewhere):
+
+ class Comment < ActiveRecord::Base
+ belongs_to :commentable, polymorphic: true
+ after_create -> { Notification.create! comment: self,
+ recipients: commentable.recipients }
+ end
+
+ That's what you want the bulk of the time. New comment creates a new
+ Notification. But there may well be off cases, like copying a commentable
+ and its comments, where you don't want that. So you'd have a concern
+ something like this:
+
+ module Copyable
+ def copy_to(destination)
+ Notification.suppress do
+ # Copy logic that creates new comments that we do not want triggering
+ # notifications.
+ end
+ end
+ end
+
+ *Michael Ryan*
+
+* `:time` option added for `#touch`
+ Fixes #18905.
+
+ *Hyonjee Joo*
+
+* Deprecated passing of `start` value to `find_in_batches` and `find_each`
+ in favour of `begin_at` value.
+
+ *Vipul A M*
+
+* Add `foreign_key_exists?` method.
+
+ *Tõnis Simo*
+
+* Use SQL COUNT and LIMIT 1 queries for `none?` and `one?` methods if no block or limit is given,
+ instead of loading the entire collection to memory.
+ This applies to relations (e.g. `User.all`) as well as associations (e.g. `account.users`)
+
+ # Before:
+
+ users.none?
+ # SELECT "users".* FROM "users"
+
+ users.one?
+ # SELECT "users".* FROM "users"
+
+ # After:
+
+ users.none?
+ # SELECT 1 AS one FROM "users" LIMIT 1
+
+ users.one?
+ # SELECT COUNT(*) FROM "users"
+
+ *Eugene Gilburg*
+
+* Have `enum` perform type casting consistently with the rest of Active
+ Record, such as `where`.
+
+ *Sean Griffin*
+
+* `scoping` no longer pollutes the current scope of sibling classes when using
+ STI. e.x.
+
+ StiOne.none.scoping do
+ StiTwo.all
+ end
+
+ Fixes #18806.
+
+ *Sean Griffin*
+
+* `remove_reference` with `foreign_key: true` removes the foreign key before
+ removing the column. This fixes a bug where it was not possible to remove
+ the column on MySQL.
+
+ Fixes #18664.
+
+ *Yves Senn*
+
+* `find_in_batches` now accepts an `:end_at` parameter that complements the `:start`
+ parameter to specify where to stop batch processing.
+
+ *Vipul A M*
+
+* Fix rounding problem for PostgreSQL timestamp column.
+
+ If timestamp column have the precision, it need to format according to
+ the precision of timestamp column.
+
+ *Ryuta Kamizono*
+
+* Respect the database default charset for `schema_migrations` table.
+
+ The charset of `version` column in `schema_migrations` table is depend
+ on the database default charset and collation rather than the encoding
+ of the connection.
+
+ *Ryuta Kamizono*
+
+* Raise `ArgumentError` when passing `nil` or `false` to `Relation#merge`.
+
+ These are not valid values to merge in a relation so it should warn the users
+ early.
+
+ *Rafael Mendonça França*
+
+* Use `SCHEMA` instead of `DB_STRUCTURE` for specifying structure file.
+
+ This makes the db:structure tasks consistent with test:load_structure.
+
+ *Dieter Komendera*
+
+* Respect custom primary keys for associations when calling `Relation#where`
+
+ Fixes #18813.
+
+ *Sean Griffin*
+
+* Fixed several edge cases which could result in a counter cache updating
+ twice or not updating at all for `has_many` and `has_many :through`.
+
+ Fixes #10865.
+
+ *Sean Griffin*
+
+* Foreign keys added by migrations were given random, generated names. This
+ meant a different `structure.sql` would be generated every time a developer
+ ran migrations on their machine.
+
+ The generated part of foreign key names is now a hash of the table name and
+ column name, which is consistent every time you run the migration.
+
+ *Chris Sinjakli*
+
+* Validation errors would be raised for parent records when an association
+ was saved when the parent had `validate: false`. It should not be the
+ responsibility of the model to validate an associated object unless the
+ object was created or modified by the parent.
+
+ This fixes the issue by skipping validations if the parent record is
+ persisted, not changed, and not marked for destruction.
+
+ Fixes #17621.
+
+ *Eileen M. Uchitelle, Aaron Patterson*
+
+* Fix n+1 query problem when eager loading nil associations (fixes #18312)
+
+ *Sammy Larbi*
+
+* Change the default error message from `can't be blank` to `must exist` for
+ the presence validator of the `:required` option on `belongs_to`/`has_one` associations.
+
+ *Henrik Nygren*
+
+* Fixed ActiveRecord::Relation#group method when argument is SQL reserved key word:
+
+ Example:
+
+ SplitTest.group(:key).count
+ Property.group(:value).count
+
+ *Bogdan Gusiev*
+
+* Added the `#or` method on ActiveRecord::Relation, allowing use of the OR
+ operator to combine WHERE or HAVING clauses.
+
+ Example:
+
+ Post.where('id = 1').or(Post.where('id = 2'))
+ # => SELECT * FROM posts WHERE (id = 1) OR (id = 2)
+
+ *Sean Griffin*, *Matthew Draper*, *Gael Muller*, *Olivier El Mekki*
+
+* Don't define autosave association callbacks twice from
+ `accepts_nested_attributes_for`.
+
+ Fixes #18704.
+
+ *Sean Griffin*
+
+* Integer types will no longer raise a `RangeError` when assigning an
+ attribute, but will instead raise when going to the database.
+
+ Fixes several vague issues which were never reported directly. See the
+ commit message from the commit which added this line for some examples.
+
+ *Sean Griffin*
+
+* Values which would error while being sent to the database (such as an
+ ASCII-8BIT string with invalid UTF-8 bytes on SQLite3), no longer error on
+ assignment. They will still error when sent to the database, but you are
+ given the ability to re-assign it to a valid value.
+
+ Fixes #18580.
+
+ *Sean Griffin*
+
+* Don't remove join dependencies in `Relation#exists?`
+
+ Fixes #18632.
+
+ *Sean Griffin*
+
+* Invalid values assigned to a JSON column are assumed to be `nil`.
+
+ Fixes #18629.
+
+ *Sean Griffin*
+
+* Add `ActiveRecord::Base#accessed_fields`, which can be used to quickly
+ discover which fields were read from a model when you are looking to only
+ select the data you need from the database.
+
+ *Sean Griffin*
+
+* Introduce the `:if_exists` option for `drop_table`.
+
+ Example:
+
+ drop_table(:posts, if_exists: true)
+
+ That would execute:
+
+ DROP TABLE IF EXISTS posts
+
+ If the table doesn't exist, `if_exists: false` (the default) raises an
+ exception whereas `if_exists: true` does nothing.
+
+ *Cody Cutrer*, *Stefan Kanev*, *Ryuta Kamizono*
+
* Don't run SQL if attribute value is not changed for update_attribute method.
*Prathamesh Sonpatki*
@@ -12,10 +267,19 @@
A deprecation warning will be emitted if you have a `:time` column, and have
not explicitly opted out.
- Fixes #3145
+ Fixes #3145.
*Sean Griffin*
+* Tests now run after_commit callbacks. You no longer have to declare
+ `uses_transaction ‘test name’` to test the results of an after_commit.
+
+ after_commit callbacks run after committing a transaction whose parent
+ is not `joinable?`: un-nested transactions, transactions within test cases,
+ and transactions in `console --sandbox`.
+
+ *arthurnn*, *Ravil Bayramgalin*, *Matthew Draper*
+
* `nil` as a value for a binary column in a query no longer logs as
"<NULL binary data>", and instead logs as just "nil".
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index d9d47c3d99..ef14e065ae 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -62,6 +62,7 @@ module ActiveRecord
autoload :Serialization
autoload :StatementCache
autoload :Store
+ autoload :Suppressor
autoload :TableMetadata
autoload :Timestamp
autoload :Transactions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 1040e6e3bb..3d497a30fb 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -3,10 +3,27 @@ module ActiveRecord
module Aggregations # :nodoc:
extend ActiveSupport::Concern
- def clear_aggregation_cache #:nodoc:
- @aggregation_cache.clear if persisted?
+ def initialize_dup(*) # :nodoc:
+ @aggregation_cache = {}
+ super
end
+ def reload(*) # :nodoc:
+ clear_aggregation_cache
+ super
+ end
+
+ private
+
+ def clear_aggregation_cache # :nodoc:
+ @aggregation_cache.clear if persisted?
+ end
+
+ def init_internals # :nodoc:
+ @aggregation_cache = {}
+ super
+ 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]
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
@@ -87,11 +104,6 @@ module ActiveRecord
# customer.address_city = "Copenhagen"
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
#
- # customer.address_street = "Vesterbrogade"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- # customer.clear_aggregation_cache
- # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
- #
# customer.address = Address.new("May Street", "Chicago")
# customer.address_street # => "May Street"
# customer.address_city # => "Chicago"
@@ -245,7 +257,8 @@ module ActiveRecord
define_method("#{name}=") do |part|
klass = class_name.constantize
if part.is_a?(Hash)
- part = klass.new(*part.values)
+ raise ArgumentError unless part.size == part.keys.max
+ part = klass.new(*part.sort.map(&:last))
end
unless part.is_a?(klass) || converter.nil? || part.nil?
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 35bc09bb10..499b00a815 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -145,20 +145,14 @@ module ActiveRecord
autoload :AliasTracker, 'active_record/associations/alias_tracker'
end
- # Clears out the association cache.
- def clear_association_cache #:nodoc:
- @association_cache.clear if persisted?
- end
-
- # :nodoc:
- attr_reader :association_cache
-
# Returns the association instance for the given name, instantiating it if it doesn't already exist
def association(name) #:nodoc:
association = association_instance_get(name)
if association.nil?
- raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
+ unless reflection = self.class._reflect_on_association(name)
+ raise AssociationNotFoundError.new(self, name)
+ end
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
@@ -166,7 +160,31 @@ module ActiveRecord
association
end
+ def association_cached?(name) # :nodoc
+ @association_cache.key?(name)
+ end
+
+ def initialize_dup(*) # :nodoc:
+ @association_cache = {}
+ super
+ end
+
+ def reload(*) # :nodoc:
+ clear_association_cache
+ super
+ end
+
private
+ # Clears out the association cache.
+ def clear_association_cache # :nodoc:
+ @association_cache.clear if persisted?
+ end
+
+ def init_internals # :nodoc:
+ @association_cache = {}
+ super
+ end
+
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
def association_instance_get(name)
@association_cache[name]
@@ -1000,6 +1018,8 @@ module ActiveRecord
# callbacks declared either before or after the <tt>:dependent</tt> option
# can affect what it does.
#
+ # Note that <tt>:dependent</tt> option is ignored for +has_one+ <tt>:through</tt> associations.
+ #
# === Delete or destroy?
#
# +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
@@ -1012,7 +1032,7 @@ module ActiveRecord
# record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
- # The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
+ # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
# +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
# the join records, without running their callbacks).
#
@@ -1330,6 +1350,8 @@ module ActiveRecord
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
+ #
+ # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
@@ -1382,7 +1404,7 @@ module ActiveRecord
# has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
# has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
# has_one :attachment, as: :attachable
- # has_one :boss, readonly: :true
+ # has_one :boss, -> { readonly }
# has_one :club, through: :membership
# has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
# has_one :credit_card, required: true
@@ -1434,7 +1456,7 @@ module ActiveRecord
# when you access the associated object.
#
# Scope examples:
- # belongs_to :user, -> { where(id: 2) }
+ # belongs_to :firm, -> { where(id: 2) }
# belongs_to :user, -> { joins(:friends) }
# belongs_to :level, ->(level) { where("game_level > ?", level.current) }
#
@@ -1510,11 +1532,11 @@ module ActiveRecord
# belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
# class_name: "Coupon", foreign_key: "coupon_id"
# belongs_to :attachable, polymorphic: true
- # belongs_to :project, readonly: true
+ # belongs_to :project, -> { readonly }
# belongs_to :post, counter_cache: true
- # belongs_to :company, touch: true
+ # belongs_to :comment, touch: true
# belongs_to :company, touch: :employees_last_updated_at
- # belongs_to :company, required: true
+ # belongs_to :user, required: true
def belongs_to(name, scope = nil, options = {})
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index d06b7b3508..2416167834 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -2,42 +2,30 @@ module ActiveRecord
module Associations
class AssociationScope #:nodoc:
def self.scope(association, connection)
- INSTANCE.scope association, connection
- end
-
- class BindSubstitution
- def initialize(block)
- @block = block
- end
-
- def bind_value(scope, column, value, connection)
- substitute = connection.substitute_at(column)
- scope.bind_values += [[column, @block.call(value)]]
- substitute
- end
+ INSTANCE.scope(association, connection)
end
def self.create(&block)
- block = block ? block : lambda { |val| val }
- new BindSubstitution.new(block)
+ block ||= lambda { |val| val }
+ new(block)
end
- def initialize(bind_substitution)
- @bind_substitution = bind_substitution
+ def initialize(value_transformation)
+ @value_transformation = value_transformation
end
INSTANCE = create
def scope(association, connection)
- klass = association.klass
- reflection = association.reflection
- scope = klass.unscoped
- owner = association.owner
+ klass = association.klass
+ reflection = association.reflection
+ scope = klass.unscoped
+ owner = association.owner
alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster
chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
scope.extending! Array(reflection.options[:extend])
- add_constraints(scope, owner, klass, reflection, connection, chain_head, chain_tail)
+ add_constraints(scope, owner, klass, reflection, chain_head, chain_tail)
end
def join_type
@@ -61,43 +49,36 @@ module ActiveRecord
binds
end
+ protected
+
+ attr_reader :value_transformation
+
private
def join(table, constraint)
table.create_join(table, table.create_on(constraint), join_type)
end
- def column_for(table_name, column_name, connection)
- columns = connection.schema_cache.columns_hash(table_name)
- columns[column_name]
- end
-
- def bind_value(scope, column, value, connection)
- @bind_substitution.bind_value scope, column, value, connection
- end
-
- def bind(scope, table_name, column_name, value, connection)
- column = column_for table_name, column_name, connection
- bind_value scope, column, value, connection
- end
-
- def last_chain_scope(scope, table, reflection, owner, connection, association_klass)
+ def last_chain_scope(scope, table, reflection, owner, association_klass)
join_keys = reflection.join_keys(association_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], connection
- scope = scope.where(table[key].eq(bind_val))
+ value = transform_value(owner[foreign_key])
+ scope = scope.where(table.name => { key => value })
if reflection.type
- value = owner.class.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, connection
- scope = scope.where(table[reflection.type].eq(bind_val))
- else
- scope
+ polymorphic_type = transform_value(owner.class.base_class.name)
+ scope = scope.where(table.name => { reflection.type => polymorphic_type })
end
+
+ scope
+ end
+
+ def transform_value(value)
+ value_transformation.call(value)
end
- def next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection)
+ def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
join_keys = reflection.join_keys(association_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
@@ -105,9 +86,8 @@ module ActiveRecord
constraint = table[key].eq(foreign_table[foreign_key])
if reflection.type
- value = next_reflection.klass.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, connection
- scope = scope.where(table[reflection.type].eq(bind_val))
+ value = transform_value(next_reflection.klass.base_class.name)
+ scope = scope.where(table.name => { reflection.type => value })
end
scope = scope.joins(join(foreign_table, constraint))
@@ -138,10 +118,10 @@ module ActiveRecord
[runtime_reflection, previous_reflection]
end
- def add_constraints(scope, owner, association_klass, refl, connection, chain_head, chain_tail)
+ def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail)
owner_reflection = chain_tail
table = owner_reflection.alias_name
- scope = last_chain_scope(scope, table, owner_reflection, owner, connection, association_klass)
+ scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
reflection = chain_head
loop do
@@ -151,7 +131,7 @@ module ActiveRecord
unless reflection == chain_tail
next_reflection = reflection.next
foreign_table = next_reflection.alias_name
- scope = next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection)
+ scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
end
# Exclude the scope of the association itself, because that
@@ -160,15 +140,14 @@ module ActiveRecord
item = eval_scope(reflection.klass, scope_chain_item, owner)
if scope_chain_item == refl.scope
- scope.merge! item.except(:where, :includes, :bind)
+ scope.merge! item.except(:where, :includes)
end
reflection.all_includes do
scope.includes! item.includes_values
end
- scope.where_values += item.where_values
- scope.bind_values += item.bind_values
+ scope.where_clause += item.where_clause
scope.order_values |= item.order_values
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index c63b42e2a0..265a65c4c1 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -68,6 +68,9 @@ module ActiveRecord
def increment_counter(counter_cache_name)
if foreign_key_present?
klass.increment_counter(counter_cache_name, target_id)
+ if target && !stale_target?
+ target.increment(counter_cache_name)
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 1369212837..f6274c027e 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -31,7 +31,7 @@ module ActiveRecord::Associations::Builder
def self.define_validations(model, reflection)
super
if reflection.options[:required]
- model.validates_presence_of reflection.name
+ model.validates_presence_of reflection.name, message: :required
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index f2c96e9a2a..82a7c27799 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -63,7 +63,7 @@ module ActiveRecord
def ids_writer(ids)
pk_type = reflection.primary_key_type
ids = Array(ids).reject(&:blank?)
- ids.map! { |i| pk_type.type_cast_from_user(i) }
+ ids.map! { |i| pk_type.cast(i) }
replace(klass.find(ids).index_by(&:id).values_at(*ids))
end
@@ -129,6 +129,16 @@ module ActiveRecord
first_nth_or_last(:last, *args)
end
+ def take
+ if loaded?
+ target.first
+ else
+ scope.take.tap do |record|
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
+ end
+ end
+ end
+
def build(attributes = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| build(attr, &block) }
@@ -151,6 +161,7 @@ module ActiveRecord
# be chained. Since << flattens its argument list and inserts each record,
# +push+ and +concat+ behave identically.
def concat(*records)
+ records = records.flatten
if owner.new_record?
load_target
concat_records(records)
@@ -318,7 +329,8 @@ module ActiveRecord
end
# Returns true if the collections is not empty.
- # Equivalent to +!collection.empty?+.
+ # If block given, loads all records and checks for one or more matches.
+ # Otherwise, equivalent to +!collection.empty?+.
def any?
if block_given?
load_target.any? { |*block_args| yield(*block_args) }
@@ -328,7 +340,8 @@ module ActiveRecord
end
# Returns true if the collection has more than 1 record.
- # Equivalent to +collection.size > 1+.
+ # If block given, loads all records and checks for two or more matches.
+ # Otherwise, equivalent to +collection.size > 1+.
def many?
if block_given?
load_target.many? { |*block_args| yield(*block_args) }
@@ -549,7 +562,7 @@ module ActiveRecord
def concat_records(records, should_raise = false)
result = true
- records.flatten.each do |record|
+ records.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index c22dc6e11e..87e80e88b2 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -227,6 +227,10 @@ module ActiveRecord
@association.last(*args)
end
+ def take
+ @association.take
+ end
+
# Returns a new object of the collection type that has been instantiated
# with +attributes+ and linked to this object, but have not yet been saved.
# You can pass an array of attributes hashes, this will return an array
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 2a782c06d0..ca27c9fdde 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -85,7 +85,11 @@ module ActiveRecord
end
def cached_counter_attribute_name(reflection = reflection())
- options[:counter_cache] || "#{reflection.name}_count"
+ if reflection.options[:counter_cache]
+ reflection.options[:counter_cache].to_s
+ else
+ "#{reflection.name}_count"
+ end
end
def update_counter(difference, reflection = reflection())
@@ -101,7 +105,7 @@ module ActiveRecord
end
def update_counter_in_memory(difference, reflection = reflection())
- if has_cached_counter?(reflection)
+ if counter_must_be_updated_by_has_many?(reflection)
counter = cached_counter_attribute_name(reflection)
owner[counter] += difference
owner.send(:clear_attribute_changes, counter) # eww
@@ -118,18 +122,28 @@ module ActiveRecord
# it will be decremented twice.
#
# Hence this method.
- def inverse_updates_counter_cache?(reflection = reflection())
+ def inverse_which_updates_counter_cache(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
- inverse_updates_counter_named?(counter_name, reflection)
+ inverse_which_updates_counter_named(counter_name, reflection)
end
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
- def inverse_updates_counter_named?(counter_name, reflection = reflection())
- reflection.klass._reflections.values.any? { |inverse_reflection|
+ def inverse_which_updates_counter_named(counter_name, reflection)
+ reflection.klass._reflections.values.find { |inverse_reflection|
inverse_reflection.belongs_to? &&
inverse_reflection.counter_cache_column == counter_name
}
end
+ def inverse_updates_counter_in_memory?(reflection)
+ inverse = inverse_which_updates_counter_cache(reflection)
+ inverse && inverse == reflection.inverse_of
+ end
+
+ def counter_must_be_updated_by_has_many?(reflection)
+ !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
+ end
+
def delete_count(method, scope)
if method == :delete_all
scope.delete_all
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index f1e784d771..4897ec44e9 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -143,7 +143,7 @@ module ActiveRecord
stmt.from scope.klass.arel_table
stmt.wheres = arel.constraints
- count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
+ count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes)
end
when :nullify
count = scope.update_all(source_reflection.foreign_key => nil)
@@ -160,9 +160,9 @@ module ActiveRecord
if through_reflection.collection? && update_through_counter?(method)
update_counter(-count, through_reflection)
+ else
+ update_counter(-count)
end
-
- update_counter(-count)
end
def through_records_for(record)
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 4b75370171..81eb5136a1 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -232,23 +232,26 @@ module ActiveRecord
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
+ return if ar_parent.nil?
primary_id = ar_parent.id
parent.children.each do |node|
if node.reflection.collection?
other = ar_parent.association(node.reflection.name)
other.loaded!
- else
- if ar_parent.association_cache.key?(node.reflection.name)
- model = ar_parent.association(node.reflection.name).target
- construct(model, node, row, rs, seen, model_cache, aliases)
- next
- end
+ elsif ar_parent.association_cached?(node.reflection.name)
+ model = ar_parent.association(node.reflection.name).target
+ construct(model, node, row, rs, seen, model_cache, aliases)
+ next
end
key = aliases.column_alias(node, node.primary_key)
id = row[key]
- next if id.nil?
+ if id.nil?
+ nil_association = ar_parent.association(node.reflection.name)
+ nil_association.loaded!
+ next
+ end
model = seen[parent.base_klass][primary_id][node.base_klass][id]
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index c1ef86a95b..a6ad09a38a 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -25,7 +25,7 @@ module ActiveRecord
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
joins = []
- bind_values = []
+ binds = []
tables = tables.reverse
scope_chain_index = 0
@@ -66,7 +66,7 @@ module ActiveRecord
end
if rel && !rel.arel.constraints.empty?
- bind_values.concat rel.bind_values
+ binds += rel.bound_attributes
constraint = constraint.and rel.arel.constraints
end
@@ -75,7 +75,7 @@ module ActiveRecord
column = klass.columns_hash[reflection.type.to_s]
substitute = klass.connection.substitute_at(column)
- bind_values.push [column, value]
+ binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
constraint = constraint.and table[reflection.type].eq substitute
end
@@ -85,7 +85,7 @@ module ActiveRecord
foreign_table, foreign_klass = table, klass
end
- JoinInformation.new joins, bind_values
+ JoinInformation.new joins, binds
end
# Builds equality condition.
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 4358f3b581..97f4bd3811 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -89,7 +89,7 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
- NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
+ NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index afcaa5d55a..1dc8bff193 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -104,11 +104,11 @@ module ActiveRecord
end
def association_key_type
- @klass.column_for_attribute(association_key_name).type
+ @klass.type_for_attribute(association_key_name.to_s).type
end
def owner_key_type
- @model.column_for_attribute(owner_key_name).type
+ @model.type_for_attribute(owner_key_name.to_s).type
end
def load_slices(slices)
@@ -131,18 +131,19 @@ module ActiveRecord
def build_scope
scope = klass.unscoped
- values = reflection_scope.values
- reflection_binds = reflection_scope.bind_values
+ values = reflection_scope.values
preload_values = preload_scope.values
- preload_binds = preload_scope.bind_values
- scope.where_values = Array(values[:where]) + Array(preload_values[:where])
+ scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
- scope.bind_values = (reflection_binds + preload_binds)
- scope._select! preload_values[:select] || values[:select] || table[Arel.star]
+ scope._select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
- scope.joins! preload_values[:joins] || values[:joins]
+ if preload_scope.joins_values.any?
+ scope.joins!(preload_scope.joins_values)
+ else
+ scope.joins!(reflection_scope.joins_values)
+ end
scope.order! preload_values[:order] || values[:order]
if preload_values[:readonly] || values[:readonly]
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 12bf3ef138..56aa23b173 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -78,10 +78,9 @@ module ActiveRecord
if options[:source_type]
scope.where! reflection.foreign_type => options[:source_type]
else
- unless reflection_scope.where_values.empty?
+ unless reflection_scope.where_clause.empty?
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
- scope.where_values = reflection_scope.values[:where]
- scope.bind_values = reflection_scope.bind_values
+ scope.where_clause = reflection_scope.where_clause
end
scope.references! reflection_scope.values[:references]
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 09828dbd9b..3ce9cffdbc 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -18,7 +18,7 @@ module ActiveRecord
reflection_scope = reflection.scope
if reflection_scope && reflection_scope.arity.zero?
- relation.merge!(reflection_scope)
+ relation = relation.merge(reflection_scope)
end
scope.merge!(
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 51e4fae62e..73dd3fa041 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
def value_for_database
- type.type_cast_for_database(value)
+ type.serialize(value)
end
def changed_from?(old_value)
@@ -51,7 +51,7 @@ module ActiveRecord
end
def changed_in_place_from?(old_value)
- type.changed_in_place?(old_value, value)
+ has_been_read? && type.changed_in_place?(old_value, value)
end
def with_value_from_user(value)
@@ -66,6 +66,10 @@ module ActiveRecord
self.class.with_cast_value(name, value, type)
end
+ def with_type(type)
+ self.class.new(name, value_before_type_cast, type)
+ end
+
def type_cast(*)
raise NotImplementedError
end
@@ -78,12 +82,21 @@ module ActiveRecord
false
end
+ def has_been_read?
+ defined?(@value)
+ end
+
def ==(other)
self.class == other.class &&
name == other.name &&
value_before_type_cast == other.value_before_type_cast &&
type == other.type
end
+ alias eql? ==
+
+ def hash
+ [self.class, name, value_before_type_cast, type].hash
+ end
protected
@@ -95,13 +108,13 @@ module ActiveRecord
class FromDatabase < Attribute # :nodoc:
def type_cast(value)
- type.type_cast_from_database(value)
+ type.deserialize(value)
end
end
class FromUser < Attribute # :nodoc:
def type_cast(value)
- type.type_cast_from_user(value)
+ type.cast(value)
end
def came_from_user?
@@ -128,6 +141,10 @@ module ActiveRecord
nil
end
+ def with_type(type)
+ self.class.with_cast_value(name, nil, type)
+ end
+
def with_value_from_database(value)
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
end
@@ -152,6 +169,6 @@ module ActiveRecord
false
end
end
- private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index bf64830417..483d4200bd 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -3,63 +3,32 @@ require 'active_model/forbidden_attributes_protection'
module ActiveRecord
module AttributeAssignment
extend ActiveSupport::Concern
- include ActiveModel::ForbiddenAttributesProtection
+ include ActiveModel::AttributeAssignment
- # Allows you to set all the attributes by passing in a hash of attributes with
- # keys matching the attribute names (which again matches the column names).
- #
- # If the passed hash responds to <tt>permitted?</tt> method and the return value
- # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
- # exception is raised.
- #
- # cat = Cat.new(name: "Gorby", status: "yawning")
- # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
- # cat.assign_attributes(status: "sleeping")
- # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
- #
- # New attributes will be persisted in the database when the object is saved.
- #
- # Aliased to <tt>attributes=</tt>.
- def assign_attributes(new_attributes)
- if !new_attributes.respond_to?(:stringify_keys)
- raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
- end
- return if new_attributes.blank?
+ # Alias for `assign_attributes`. See +ActiveModel::AttributeAssignment+.
+ def attributes=(attributes)
+ assign_attributes(attributes)
+ end
- attributes = new_attributes.stringify_keys
- multi_parameter_attributes = []
- nested_parameter_attributes = []
+ private
- attributes = sanitize_for_mass_assignment(attributes)
+ def _assign_attributes(attributes) # :nodoc:
+ multi_parameter_attributes = {}
+ nested_parameter_attributes = {}
attributes.each do |k, v|
if k.include?("(")
- multi_parameter_attributes << [ k, v ]
+ multi_parameter_attributes[k] = attributes.delete(k)
elsif v.is_a?(Hash)
- nested_parameter_attributes << [ k, v ]
- else
- _assign_attribute(k, v)
+ nested_parameter_attributes[k] = attributes.delete(k)
end
end
+ super(attributes)
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- alias attributes= assign_attributes
-
- private
-
- def _assign_attribute(k, v)
- public_send("#{k}=", v)
- rescue NoMethodError
- if respond_to?("#{k}=")
- raise
- else
- raise UnknownAttributeError.new(self, k)
- end
- end
-
# Assign any deferred nested attributes after the base attributes have been set.
def assign_nested_parameter_attributes(pairs)
pairs.each { |k, v| _assign_attribute(k, v) }
@@ -81,7 +50,12 @@ module ActiveRecord
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
+ if values_with_empty_parameters.each_value.all?(&:nil?)
+ values = nil
+ else
+ values = values_with_empty_parameters
+ end
+ send("#{name}=", values)
rescue => ex
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
end
@@ -113,100 +87,5 @@ module ActiveRecord
def find_parameter_position(multiparameter_name)
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
-
- class MultiparameterAttribute #:nodoc:
- attr_reader :object, :name, :values, :cast_type
-
- def initialize(object, name, values)
- @object = object
- @name = name
- @values = values
- end
-
- def read_value
- return if values.values.compact.empty?
-
- @cast_type = object.type_for_attribute(name)
- klass = cast_type.klass
-
- if klass == Time
- read_time
- elsif klass == Date
- read_date
- else
- read_other
- end
- end
-
- private
-
- def instantiate_time_object(set_values)
- if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
- Time.zone.local(*set_values)
- else
- Time.send(object.class.default_timezone, *set_values)
- end
- end
-
- def read_time
- # If column is a :time (and not :date or :datetime) there is no need to validate if
- # there are year/month/day fields
- if cast_type.type == :time
- # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
- { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
- values[key] ||= value
- end
- else
- # else column is a timestamp, so if Date bits were not provided, error
- validate_required_parameters!([1,2,3])
-
- # If Date bits were provided but blank, then return nil
- return if blank_date_parameter?
- end
-
- max_position = extract_max_param(6)
- set_values = values.values_at(*(1..max_position))
- # If Time bits are not there, then default to 0
- (3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
- instantiate_time_object(set_values)
- end
-
- def read_date
- return if blank_date_parameter?
- set_values = values.values_at(1,2,3)
- begin
- Date.new(*set_values)
- rescue ArgumentError # if Date.new raises an exception on an invalid date
- instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- end
-
- def read_other
- max_position = extract_max_param
- positions = (1..max_position)
- validate_required_parameters!(positions)
-
- values.slice(*positions)
- end
-
- # Checks whether some blank date parameter exists. Note that this is different
- # than the validate_required_parameters! method, since it just checks for blank
- # positions instead of missing ones, and does not raise in case one blank position
- # exists. The caller is responsible to handle the case of this returning true.
- def blank_date_parameter?
- (1..3).any? { |position| values[position].blank? }
- end
-
- # If some position is not provided, it errors out a missing parameter exception.
- def validate_required_parameters!(positions)
- if missing_parameter = positions.detect { |position| !values.key?(position) }
- raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
- end
- end
-
- def extract_max_param(upper_cap = 100)
- [values.keys.max, upper_cap].min
- end
- end
end
end
diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb
index 5b96623b6e..7d0ae32411 100644
--- a/activerecord/lib/active_record/attribute_decorators.rb
+++ b/activerecord/lib/active_record/attribute_decorators.rb
@@ -15,7 +15,7 @@ module ActiveRecord
end
def decorate_matching_attribute_types(matcher, decorator_name, &block)
- clear_caches_calculated_from_columns
+ reload_schema_from_cache
decorator_name = decorator_name.to_s
# Create new hashes so we don't modify parent classes
@@ -24,10 +24,11 @@ module ActiveRecord
private
- def add_user_provided_columns(*)
- super.map do |column|
- decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
- column.with_type(decorated_type)
+ def load_schema!
+ super
+ attribute_types.each do |name, type|
+ decorated_type = attribute_type_decorations.apply(name, type)
+ define_attribute(name, decorated_type)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 8f165fb1dc..9d58a19304 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -83,7 +83,7 @@ module ActiveRecord
generated_attribute_methods.synchronize do
return false if @attribute_methods_generated
superclass.define_attribute_methods unless self == base_class
- super(column_names)
+ super(attribute_names)
@attribute_methods_generated = true
end
true
@@ -185,7 +185,7 @@ module ActiveRecord
# # => ["id", "created_at", "updated_at", "name", "age"]
def attribute_names
@attribute_names ||= if !abstract_class? && table_exists?
- column_names
+ attribute_types.keys
else
[]
end
@@ -369,6 +369,39 @@ module ActiveRecord
write_attribute(attr_name, value)
end
+ # Returns the name of all database fields which have been read from this
+ # model. This can be useful in development mode to determine which fields
+ # need to be selected. For performance critical pages, selecting only the
+ # required fields can be an easy performance win (assuming you aren't using
+ # all of the fields on the model).
+ #
+ # For example:
+ #
+ # class PostsController < ActionController::Base
+ # after_action :print_accessed_fields, only: :index
+ #
+ # def index
+ # @posts = Post.all
+ # end
+ #
+ # private
+ #
+ # def print_accessed_fields
+ # p @posts.first.accessed_fields
+ # end
+ # end
+ #
+ # Which allows you to quickly change your code to:
+ #
+ # class PostsController < ActionController::Base
+ # def index
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
+ # end
+ # end
+ def accessed_fields
+ @attributes.accessed
+ end
+
protected
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index bce9c5e1e3..7ba907f786 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -131,10 +131,8 @@ module ActiveRecord
partial_writes? ? super(keys_for_partial_write) : super
end
- # Serialized attributes should always be written in case they've been
- # changed in place.
def keys_for_partial_write
- changed & persistable_attribute_names
+ changed & self.class.column_names
end
def _field_changed?(attr, old_value)
@@ -165,7 +163,7 @@ module ActiveRecord
end
def store_original_raw_attribute(attr_name)
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
end
def store_original_raw_attributes
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index dc689f399a..553122a5fc 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -22,7 +22,7 @@ module ActiveRecord
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
!value.blank?
end
- elsif column.number?
+ elsif value.respond_to?(:zero?)
!value.zero?
else
!value.blank?
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 24e30b6608..0d989c2eca 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -69,9 +69,18 @@ module ActiveRecord
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the read_attribute API
- def _read_attribute(attr_name) # :nodoc:
- @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
+ if defined?(JRUBY_VERSION)
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
+ # https://github.com/jruby/jruby/pull/2562
+ def _read_attribute(attr_name, &block) # :nodoc
+ @attributes.fetch_value(attr_name.to_s, &block)
+ end
+ else
+ def _read_attribute(attr_name) # :nodoc:
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
+ end
end
+
alias :attribute :_read_attribute
private :attribute
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 98671178cb..f9beb43e4b 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -2,15 +2,15 @@ module ActiveRecord
module AttributeMethods
module TimeZoneConversion
class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
- include Type::Decorator
-
- def type_cast_from_database(value)
+ def deserialize(value)
convert_time_to_time_zone(super)
end
- def type_cast_from_user(value)
+ def cast(value)
if value.is_a?(Array)
- value.map { |v| type_cast_from_user(v) }
+ value.map { |v| cast(v) }
+ elsif value.is_a?(Hash)
+ set_time_zone_without_conversion(super)
elsif value.respond_to?(:in_time_zone)
begin
user_input_in_time_zone(value) || super
@@ -20,6 +20,8 @@ module ActiveRecord
end
end
+ private
+
def convert_time_to_time_zone(value)
if value.is_a?(Array)
value.map { |v| convert_time_to_time_zone(v) }
@@ -29,6 +31,10 @@ module ActiveRecord
value
end
end
+
+ def set_time_zone_without_conversion(value)
+ ::Time.zone.local_to_utc(value).in_time_zone
+ end
end
extend ActiveSupport::Concern
@@ -73,7 +79,7 @@ module ActiveRecord
time_zone_aware_types.include?(:not_explicitly_configured)
ActiveSupport::Deprecation.warn(<<-MESSAGE)
Time columns will become time zone aware in Rails 5.1. This
- still cause `String`s to be parsed as if they were in `Time.zone`,
+ still causes `String`s to be parsed as if they were in `Time.zone`,
and `Time`s to be converted to `Time.zone`.
To keep the old behavior, you must add the following to your initializer:
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 66fcaf6945..013a7d0e01 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -10,6 +10,10 @@ module ActiveRecord
attributes[name] || Attribute.null(name)
end
+ def []=(name, value)
+ attributes[name] = value
+ end
+
def values_before_type_cast
attributes.transform_values(&:value_before_type_cast)
end
@@ -24,11 +28,19 @@ module ActiveRecord
end
def keys
- attributes.initialized_keys
+ attributes.each_key.select { |name| self[name].initialized? }
end
- def fetch_value(name)
- self[name].value { |n| yield n if block_given? }
+ if defined?(JRUBY_VERSION)
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
+ # https://github.com/jruby/jruby/pull/2562
+ def fetch_value(name, &block)
+ self[name].value(&block)
+ end
+ else
+ def fetch_value(name)
+ self[name].value { |n| yield n if block_given? }
+ end
end
def write_from_database(name, value)
@@ -49,7 +61,7 @@ module ActiveRecord
end
def initialize_dup(_)
- @attributes = attributes.dup
+ @attributes = attributes.deep_dup
super
end
@@ -64,6 +76,10 @@ module ActiveRecord
end
end
+ def accessed
+ attributes.select { |_, attr| attr.has_been_read? }.keys
+ 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 3a76f5262d..e85777c335 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
class LazyAttributeHash # :nodoc:
- delegate :select, :transform_values, to: :materialize
+ delegate :transform_values, :each_key, to: :materialize
def initialize(types, values, additional_types)
@types = types
@@ -45,15 +45,21 @@ module ActiveRecord
delegate_hash[key] = value
end
- def initialized_keys
- delegate_hash.keys | values.keys
- end
-
def initialize_dup(_)
@delegate_hash = delegate_hash.transform_values(&:dup)
super
end
+ def select
+ keys = types.keys | values.keys | delegate_hash.keys
+ keys.each_with_object({}) do |key, hash|
+ attribute = self[key]
+ if yield(key, attribute)
+ hash[key] = attribute
+ end
+ end
+ end
+
protected
attr_reader :types, :values, :additional_types, :delegate_hash
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index b263a89d79..c8979a60d7 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,33 +1,42 @@
module ActiveRecord
- module Attributes # :nodoc:
+ # See ActiveRecord::Attributes::ClassMethods for documentation
+ module Attributes
extend ActiveSupport::Concern
+ # :nodoc:
Type = ActiveRecord::Type
included do
- class_attribute :user_provided_columns, instance_accessor: false # :internal:
- class_attribute :user_provided_defaults, instance_accessor: false # :internal:
- self.user_provided_columns = {}
- self.user_provided_defaults = {}
-
- delegate :persistable_attribute_names, to: :class
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal:
+ self.attributes_to_define_after_schema_loads = {}
end
- module ClassMethods # :nodoc:
- # Defines or overrides a attribute on this model. This allows customization of
- # Active Record's type casting behavior, as well as adding support for user defined
- # types.
+ module ClassMethods
+ # Defines an attribute with a type on this model. It will override the
+ # type of existing attributes if needed. This allows control over how
+ # values are converted to and from SQL when assigned to a model. It also
+ # changes the behavior of values passed to
+ # ActiveRecord::QueryMethods#where. This will let you use
+ # your domain objects across much of Active Record, without having to
+ # rely on implementation details or monkey patching.
+ #
+ # +name+ The name of the methods to define attribute methods for, and the
+ # column which this will persist to.
+ #
+ # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
+ # to be used for this attribute. See the examples below for more
+ # information about providing custom type objects.
#
- # +name+ The name of the methods to define attribute methods for, and the column which
- # this will persist to.
+ # ==== Options
+ # The following options are accepted:
#
- # +cast_type+ A type object that contains information about how to type cast the value.
- # See the examples section for more information.
+ # +default+ The default value to use when no value is provided. If this option
+ # is not passed, the previous default value (if any) will be used.
+ # Otherwise, the default will be +nil+.
#
- # ==== Options
- # The options hash accepts the following options:
+ # +array+ (PG only) specifies that the type should be an array (see the examples below).
#
- # +default+ is the default value that the column should use on a new record.
+ # +range+ (PG only) specifies that the type should be a range (see the examples below).
#
# ==== Examples
#
@@ -48,99 +57,188 @@ module ActiveRecord
# store_listing.price_in_cents # => BigDecimal.new(10.1)
#
# class StoreListing < ActiveRecord::Base
- # attribute :price_in_cents, Type::Integer.new
+ # attribute :price_in_cents, :integer
# end
#
# # after
# store_listing.price_in_cents # => 10
#
- # Users may also define their own custom types, as long as they respond to the methods
- # defined on the value type. The +type_cast+ method on your type object will be called
- # with values both from the database, and from your controllers. See
- # +ActiveRecord::Attributes::Type::Value+ for the expected API. It is recommended that your
- # type objects inherit from an existing type, or the base value type.
+ # A default can also be provided.
+ #
+ # create_table :store_listings, force: true do |t|
+ # t.string :my_string, default: "original default"
+ # end
+ #
+ # StoreListing.new.my_string # => "original default"
+ #
+ # class StoreListing < ActiveRecord::Base
+ # attribute :my_string, :string, default: "new default"
+ # end
+ #
+ # StoreListing.new.my_string # => "new default"
+ #
+ # Attributes do not need to be backed by a database column.
+ #
+ # class MyModel < ActiveRecord::Base
+ # attribute :my_string, :string
+ # attribute :my_int_array, :integer, array: true
+ # attribute :my_float_range, :float, range: true
+ # end
+ #
+ # model = MyModel.new(
+ # my_string: "string",
+ # my_int_array: ["1", "2", "3"],
+ # my_float_range: "[1,3.5]",
+ # )
+ # model.attributes
+ # # =>
+ # {
+ # my_string: "string",
+ # my_int_array: [1, 2, 3],
+ # my_float_range: 1.0..3.5
+ # }
+ #
+ # ==== Creating Custom Types
+ #
+ # Users may also define their own custom types, as long as they respond
+ # to the methods defined on the value type. The method
+ # +deserialize+ or +cast+ will be called on
+ # your type object, with raw input from the database or from your
+ # controllers. See ActiveRecord::Type::Value for the expected API. It is
+ # recommended that your type objects inherit from an existing type, or
+ # from ActiveRecord::Type::Value
#
# class MoneyType < ActiveRecord::Type::Integer
- # def type_cast(value)
+ # def cast(value)
# if value.include?('$')
# price_in_dollars = value.gsub(/\$/, '').to_f
- # price_in_dollars * 100
+ # super(price_in_dollars * 100)
# else
- # value.to_i
+ # super
# end
# end
# end
#
+ # # config/initializers/types.rb
+ # ActiveRecord::Type.register(:money, MoneyType)
+ #
+ # # /app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
- # attribute :price_in_cents, MoneyType.new
+ # attribute :price_in_cents, :money
# end
#
# store_listing = StoreListing.new(price_in_cents: '$10.00')
# store_listing.price_in_cents # => 1000
- def attribute(name, cast_type, options = {})
+ #
+ # For more details on creating custom types, see the documentation for
+ # ActiveRecord::Type::Value. For more details on registering your types
+ # to be referenced by a symbol, see ActiveRecord::Type.register. You can
+ # also pass a type object directly, in place of a symbol.
+ #
+ # ==== Querying
+ #
+ # When ActiveRecord::QueryMethods#where is called, it will
+ # use the type defined by the model class to convert the value to SQL,
+ # calling +serialize+ on your type object. For example:
+ #
+ # class Money < Struct.new(:amount, :currency)
+ # end
+ #
+ # class MoneyType < Type::Value
+ # def initialize(currency_converter)
+ # @currency_converter = currency_converter
+ # end
+ #
+ # # value will be the result of +deserialize+ or
+ # # +cast+. Assumed to be in instance of +Money+ in
+ # # this case.
+ # def serialize(value)
+ # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
+ # value_in_bitcoins.amount
+ # end
+ # end
+ #
+ # ActiveRecord::Type.register(:money, MoneyType)
+ #
+ # class Product < ActiveRecord::Base
+ # currency_converter = ConversionRatesFromTheInternet.new
+ # attribute :price_in_bitcoins, :money, currency_converter
+ # end
+ #
+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
+ #
+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
+ #
+ # ==== Dirty Tracking
+ #
+ # The type of an attribute is given the opportunity to change how dirty
+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
+ # will be called from ActiveModel::Dirty. See the documentation for those
+ # methods in ActiveRecord::Type::Value for more details.
+ def attribute(name, cast_type, **options)
name = name.to_s
- clear_caches_calculated_from_columns
- # Assign a new hash to ensure that subclasses do not share a hash
- self.user_provided_columns = user_provided_columns.merge(name => cast_type)
-
- if options.key?(:default)
- self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
- end
- end
+ reload_schema_from_cache
- # Returns an array of column objects for the table associated with this class.
- def columns
- @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
+ self.attributes_to_define_after_schema_loads =
+ attributes_to_define_after_schema_loads.merge(
+ name => [cast_type, options]
+ )
end
- # Returns a hash of column objects for the table associated with this class.
- def columns_hash
- @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
+ # This is the low level API which sits beneath +attribute+. It only
+ # accepts type objects, and will do its work immediately instead of
+ # waiting for the schema to load. Automatic schema detection and
+ # ClassMethods#attribute both call this under the hood. While this method
+ # is provided so it can be used by plugin authors, application code
+ # should probably use ClassMethods#attribute.
+ #
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
+ #
+ # +cast_type+ The type object to use for this attribute.
+ #
+ # +default+ The default value to use when no value is provided. If this option
+ # is not passed, the previous default value (if any) will be used.
+ # Otherwise, the default will be +nil+.
+ #
+ # +user_provided_default+ Whether the default value should be cast using
+ # +cast+ or +deserialize+.
+ def define_attribute(
+ name,
+ cast_type,
+ default: NO_DEFAULT_PROVIDED,
+ user_provided_default: true
+ )
+ attribute_types[name] = cast_type
+ define_default_attribute(name, default, cast_type, from_user: user_provided_default)
end
- def persistable_attribute_names # :nodoc:
- @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
- end
-
- def reset_column_information # :nodoc:
+ def load_schema! # :nodoc:
super
- clear_caches_calculated_from_columns
- end
-
- private
-
- def add_user_provided_columns(schema_columns)
- existing_columns = schema_columns.map do |column|
- new_type = user_provided_columns[column.name]
- if new_type
- column.with_type(new_type)
- else
- column
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
+ if type.is_a?(Symbol)
+ type = ActiveRecord::Type.lookup(type, **options.except(:default))
end
- end
- existing_column_names = existing_columns.map(&:name)
- new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
- connection.new_column(name, nil, type)
+ define_attribute(name, type, **options.slice(:default))
end
-
- existing_columns + new_columns
end
- def clear_caches_calculated_from_columns
- @arel_table = nil
- @attributes_builder = nil
- @column_names = nil
- @column_types = nil
- @columns = nil
- @columns_hash = nil
- @content_columns = nil
- @default_attributes = nil
- @persistable_attribute_names = nil
- end
+ private
+
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
+ private_constant :NO_DEFAULT_PROVIDED
- def raw_default_values
- super.merge(user_provided_defaults)
+ def define_default_attribute(name, value, type, from_user:)
+ if value == NO_DEFAULT_PROVIDED
+ default_attribute = _default_attributes[name].with_type(type)
+ elsif from_user
+ default_attribute = Attribute.from_user(name, value, type)
+ else
+ default_attribute = Attribute.from_database(name, value, type)
+ end
+ _default_attributes[name] = default_attribute
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index fcaaffb852..0792d19c3e 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -177,10 +177,8 @@ module ActiveRecord
# before actually defining them.
def add_autosave_association_callbacks(reflection)
save_method = :"autosave_associated_records_for_#{reflection.name}"
- validation_method = :"validate_associated_records_for_#{reflection.name}"
- collection = reflection.collection?
- if collection
+ if reflection.collection?
before_save :before_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
@@ -204,8 +202,18 @@ module ActiveRecord
before_save save_method
end
+ define_autosave_validation_callbacks(reflection)
+ end
+
+ def define_autosave_validation_callbacks(reflection)
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
if reflection.validate? && !method_defined?(validation_method)
- method = (collection ? :validate_collection_association : :validate_single_association)
+ if reflection.collection?
+ method = :validate_collection_association
+ else
+ method = :validate_single_association
+ end
+
define_non_cyclic_method(validation_method) do
send(method, reflection)
# TODO: remove the following line as soon as the return value of
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 100d3780f6..cc03e37a12 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -313,6 +313,7 @@ module ActiveRecord #:nodoc:
include Serialization
include Store
include SecureToken
+ include Suppressor
end
ActiveSupport.run_load_hooks(:active_record, Base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 787d07c4c2..42ad285340 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -201,16 +201,14 @@ module ActiveRecord
# isolation level. However, support is disabled for MySQL versions below 5,
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
# which means the isolation level gets persisted outside the transaction.
- def transaction(options = {})
- options.assert_valid_keys :requires_new, :joinable, :isolation
-
- if !options[:requires_new] && current_transaction.joinable?
- if options[:isolation]
+ def transaction(requires_new: nil, isolation: nil, joinable: true)
+ if !requires_new && current_transaction.joinable?
+ if isolation
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
end
yield
else
- transaction_manager.within_new_transaction(options) { yield }
+ transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable) { yield }
end
rescue ActiveRecord::Rollback
# rollbacks are silently swallowed
@@ -234,6 +232,10 @@ module ActiveRecord
current_transaction.add_record(record)
end
+ def transaction_state
+ current_transaction.state
+ end
+
# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
@@ -286,10 +288,11 @@ module ActiveRecord
columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
- [columns[name], value]
+ type = lookup_cast_type_from_column(columns[name])
+ Relation::QueryAttribute.new(name, value, type)
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
- value_list = prepare_binds_for_database(binds).map do |_, value|
+ value_list = prepare_binds_for_database(binds).map do |value|
begin
quote(value)
rescue TypeError
@@ -381,7 +384,7 @@ module ActiveRecord
def binds_from_relation(relation, binds)
if relation.is_a?(Relation) && binds.empty?
- relation, binds = relation.arel, relation.bind_values
+ relation, binds = relation.arel, relation.bound_attributes
end
[relation, binds]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index c18caa2a2f..d2840b9498 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -16,7 +16,7 @@ module ActiveRecord
https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11
for more information.
MSG
- value = column.cast_type.type_cast_for_database(value)
+ value = type_cast_from_column(column, value)
end
_quote(value)
@@ -31,7 +31,7 @@ module ActiveRecord
end
if column
- value = column.cast_type.type_cast_for_database(value)
+ value = type_cast_from_column(column, value)
end
_type_cast(value)
@@ -40,6 +40,40 @@ module ActiveRecord
raise TypeError, "can't cast #{value.class}#{to_type}"
end
+ # If you are having to call this function, you are likely doing something
+ # wrong. The column does not have sufficient type information if the user
+ # provided a custom type on the class level either explicitly (via
+ # `attribute`) or implicitly (via `serialize`,
+ # `time_zone_aware_attributes`). In almost all cases, the sql type should
+ # only be used to change quoting behavior, when the primitive to
+ # represent the type doesn't sufficiently reflect the differences
+ # (varchar vs binary) for example. The type used to get this primitive
+ # should have been provided before reaching the connection adapter.
+ def type_cast_from_column(column, value) # :nodoc:
+ if column
+ type = lookup_cast_type_from_column(column)
+ type.serialize(value)
+ else
+ value
+ end
+ end
+
+ # See docs for +type_cast_from_column+
+ def lookup_cast_type_from_column(column) # :nodoc:
+ lookup_cast_type(column.sql_type)
+ end
+
+ def fetch_type_metadata(sql_type)
+ cast_type = lookup_cast_type(sql_type)
+ SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ end
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
@@ -68,6 +102,11 @@ module ActiveRecord
quote_table_name("#{table}.#{attr}")
end
+ def quote_default_expression(value, column) #:nodoc:
+ value = lookup_cast_type(column.sql_type).serialize(value)
+ quote(value)
+ end
+
def quoted_true
"'t'"
end
@@ -93,17 +132,16 @@ module ActiveRecord
end
end
- value.to_s(:db)
+ result = value.to_s(:db)
+ if value.respond_to?(:usec) && value.usec > 0
+ "#{result}.#{sprintf("%06d", value.usec)}"
+ else
+ result
+ end
end
def prepare_binds_for_database(binds) # :nodoc:
- binds.map do |column, value|
- if column
- column_name = column.name
- value = column.cast_type.type_cast_for_database(value)
- end
- [column_name, value]
- end
+ binds.map(&:value_for_database)
end
private
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 db20b60d60..f754df93b6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -18,6 +18,9 @@ module ActiveRecord
"ADD #{accept(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
+
private
def visit_AlterTable(o)
@@ -28,7 +31,7 @@ module ActiveRecord
end
def visit_ColumnDefinition(o)
- o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
+ o.sql_type ||= type_to_sql(o.type, o.limit, o.precision, o.scale)
column_sql = "#{quote_column_name(o.name)} #{o.sql_type}"
add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
column_sql
@@ -70,18 +73,6 @@ module ActiveRecord
column_options
end
- def quote_column_name(name)
- @conn.quote_column_name name
- end
-
- def quote_table_name(name)
- @conn.quote_table_name name
- end
-
- def type_to_sql(type, limit, precision, scale)
- @conn.type_to_sql type.to_sym, limit, precision, scale
- end
-
def add_column_options!(sql, options)
sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
# must explicitly check for :null to allow change_column to work on migrations
@@ -97,11 +88,6 @@ module ActiveRecord
sql
end
- def quote_default_expression(value, column)
- value = type_for_column(column).type_cast_for_database(value)
- @conn.quote(value)
- end
-
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
@@ -118,10 +104,6 @@ module ActiveRecord
MSG
end
end
-
- def type_for_column(column)
- @conn.lookup_cast_type(column.sql_type)
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7eaa89c9a7..a2777fcd0a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type, :cast_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
@@ -50,6 +50,14 @@ module ActiveRecord
options[:primary_key] != default_primary_key
end
+ def defined_for?(options_or_to_table = {})
+ if options_or_to_table.is_a?(Hash)
+ options_or_to_table.all? {|key, value| options[key].to_s == value.to_s }
+ else
+ to_table == options_or_to_table.to_s
+ end
+ end
+
private
def default_primary_key
"id"
@@ -651,6 +659,10 @@ module ActiveRecord
@base.add_foreign_key(name, *args)
end
+ def foreign_key_exists?(*args) # :nodoc:
+ @base.foreign_key_exists?(name, *args)
+ end
+
private
def native
@base.native_database_types
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 42ea599a74..af7ef7cbaa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -46,9 +46,10 @@ module ActiveRecord
private
def schema_default(column)
- default = column.type_cast_from_database(column.default)
+ type = lookup_cast_type_from_column(column)
+ default = type.deserialize(column.default)
unless default.nil?
- column.type_cast_for_schema(default)
+ type.type_cast_for_schema(default)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 0f44c332ae..0438c95bd7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,4 +1,6 @@
require 'active_record/migration/join_table'
+require 'active_support/core_ext/string/access'
+require 'digest'
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -380,7 +382,7 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
- execute "DROP TABLE #{quote_table_name(table_name)}"
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
end
# Adds a new column to the named table.
@@ -660,7 +662,13 @@ module ActiveRecord
#
# remove_reference(:products, :supplier, polymorphic: true)
#
+ # ====== Remove the reference with a foreign key
+ #
+ # remove_reference(:products, :user, index: true, foreign_key: true)
+ #
def remove_reference(table_name, ref_name, options = {})
+ remove_foreign_key table_name, ref_name.to_s.pluralize if options[:foreign_key]
+
remove_column(table_name, "#{ref_name}_id")
remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
end
@@ -749,21 +757,7 @@ module ActiveRecord
def remove_foreign_key(from_table, options_or_to_table = {})
return unless supports_foreign_keys?
- if options_or_to_table.is_a?(Hash)
- options = options_or_to_table
- else
- options = { column: foreign_key_column_for(options_or_to_table) }
- end
-
- fk_name_to_delete = options.fetch(:name) do
- fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
-
- if fk_to_delete
- fk_to_delete.name
- else
- raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
- end
- end
+ fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
at = create_alter_table from_table
at.drop_foreign_key fk_name_to_delete
@@ -771,6 +765,31 @@ module ActiveRecord
execute schema_creation.accept(at)
end
+ # Checks to see if a foreign key exists on a table for a given foreign key definition.
+ #
+ # # Check a foreign key exists
+ # foreign_key_exists?(:accounts, :branches)
+ #
+ # # Check a foreign key on specified column exists
+ # foreign_key_exists?(:accounts, column: :owner_id)
+ #
+ # # Check a foreign key with a custom name exists
+ # foreign_key_exists?(:accounts, name: "special_fk_name")
+ #
+ def foreign_key_exists?(from_table, options_or_to_table = {})
+ foreign_key_for(from_table, options_or_to_table).present?
+ end
+
+ def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc:
+ return unless supports_foreign_keys?
+ foreign_keys(from_table).detect {|fk| fk.defined_for? options_or_to_table }
+ end
+
+ def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
+ foreign_key_for(from_table, options_or_to_table) or \
+ raise ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}"
+ end
+
def foreign_key_column_for(table_name) # :nodoc:
"#{table_name.to_s.singularize}_id"
end
@@ -832,6 +851,12 @@ module ActiveRecord
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end
+ elsif [:datetime, :time].include?(type) && precision ||= native[:precision]
+ if (0..6) === precision
+ column_type_sql << "(#{precision})"
+ else
+ raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
+ end
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
column_type_sql << "(#{limit})"
end
@@ -990,8 +1015,10 @@ module ActiveRecord
end
def foreign_key_name(table_name, options) # :nodoc:
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
options.fetch(:name) do
- "fk_rails_#{SecureRandom.hex(5)}"
+ "fk_rails_#{hashed_identifier}"
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 7535e9147a..11440e30d4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -55,11 +55,7 @@ module ActiveRecord
end
def add_record(record)
- if record.has_transactional_callbacks?
- records << record
- else
- record.set_transaction_state(@state)
- end
+ records << record
end
def rollback
@@ -111,7 +107,6 @@ module ActiveRecord
def rollback
connection.rollback_to_savepoint(savepoint_name)
super
- rollback_records
end
def commit
@@ -136,13 +131,11 @@ module ActiveRecord
def rollback
connection.rollback_db_transaction
super
- rollback_records
end
def commit
connection.commit_db_transaction
super
- commit_records
end
end
@@ -159,18 +152,28 @@ module ActiveRecord
else
SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
end
+
@stack.push(transaction)
transaction
end
def commit_transaction
- transaction = @stack.pop
- transaction.commit
- transaction.records.each { |r| current_transaction.add_record(r) }
+ inner_transaction = @stack.pop
+ inner_transaction.commit
+
+ if current_transaction.joinable?
+ inner_transaction.records.each do |r|
+ r.add_to_transaction
+ end
+ else
+ inner_transaction.commit_records
+ end
end
- def rollback_transaction
- @stack.pop.rollback
+ def rollback_transaction(transaction = nil)
+ transaction ||= @stack.pop
+ transaction.rollback
+ transaction.rollback_records
end
def within_new_transaction(options = {})
@@ -182,12 +185,12 @@ module ActiveRecord
ensure
unless error
if Thread.current.status == 'aborting'
- rollback_transaction
+ rollback_transaction if transaction
else
begin
commit_transaction
rescue Exception
- transaction.rollback unless transaction.state.completed?
+ rollback_transaction(transaction) unless transaction.state.completed?
raise
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c941c9f1eb..c307189980 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -4,6 +4,7 @@ require 'bigdecimal/util'
require 'active_record/type'
require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache'
+require 'active_record/connection_adapters/sql_type_metadata'
require 'active_record/connection_adapters/abstract/schema_dumper'
require 'active_record/connection_adapters/abstract/schema_creation'
require 'monitor'
@@ -114,7 +115,7 @@ module ActiveRecord
class BindCollector < Arel::Collectors::Bind
def compile(bvs, conn)
casted_binds = conn.prepare_binds_for_database(bvs)
- super(casted_binds.map { |_, value| conn.quote(value) })
+ super(casted_binds.map { |value| conn.quote(value) })
end
end
@@ -244,6 +245,11 @@ module ActiveRecord
false
end
+ # Does this adapter support datetime with precision?
+ def supports_datetime_with_precision?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -262,18 +268,6 @@ module ActiveRecord
{}
end
- # QUOTING ==================================================
-
- # Quote date/time values for use in SQL input. Includes microseconds
- # if the value is a Time responding to usec.
- def quoted_date(value) #:nodoc:
- if value.acts_like?(:time) && value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
- end
- end
-
# Returns a bind substitution value given a bind +column+
# NOTE: The column param is currently being used by the sqlserver-adapter
def substitute_at(column, _unused = 0)
@@ -366,9 +360,18 @@ module ActiveRecord
end
def case_insensitive_comparison(table, attribute, column, value)
- table[attribute].lower.eq(table.lower(value))
+ if can_perform_case_insensitive_comparison_for?(column)
+ table[attribute].lower.eq(table.lower(value))
+ else
+ case_sensitive_comparison(table, attribute, column, value)
+ end
end
+ def can_perform_case_insensitive_comparison_for?(column)
+ true
+ end
+ private :can_perform_case_insensitive_comparison_for?
+
def current_savepoint_name
current_transaction.savepoint_name
end
@@ -384,8 +387,8 @@ module ActiveRecord
end
end
- def new_column(name, default, cast_type, sql_type = nil, null = true)
- Column.new(name, default, cast_type, sql_type, null)
+ def new_column(name, default, sql_type_metadata = nil, null = true)
+ Column.new(name, default, sql_type_metadata, null)
end
def lookup_cast_type(sql_type) # :nodoc:
@@ -399,15 +402,15 @@ module ActiveRecord
protected
def initialize_type_map(m) # :nodoc:
- register_class_with_limit m, %r(boolean)i, Type::Boolean
- register_class_with_limit m, %r(char)i, Type::String
- register_class_with_limit m, %r(binary)i, Type::Binary
- register_class_with_limit m, %r(text)i, Type::Text
- register_class_with_limit m, %r(date)i, Type::Date
- register_class_with_limit m, %r(time)i, Type::Time
- register_class_with_limit m, %r(datetime)i, Type::DateTime
- register_class_with_limit m, %r(float)i, Type::Float
- register_class_with_limit m, %r(int)i, Type::Integer
+ register_class_with_limit m, %r(boolean)i, Type::Boolean
+ register_class_with_limit m, %r(char)i, Type::String
+ register_class_with_limit m, %r(binary)i, Type::Binary
+ register_class_with_limit m, %r(text)i, Type::Text
+ register_class_with_precision m, %r(date)i, Type::Date
+ register_class_with_precision m, %r(time)i, Type::Time
+ register_class_with_precision m, %r(datetime)i, Type::DateTime
+ register_class_with_limit m, %r(float)i, Type::Float
+ register_class_with_limit m, %r(int)i, Type::Integer
m.alias_type %r(blob)i, 'binary'
m.alias_type %r(clob)i, 'text'
@@ -441,6 +444,13 @@ module ActiveRecord
end
end
+ def register_class_with_precision(mapping, key, klass) # :nodoc:
+ mapping.register_type(key) do |*args|
+ precision = extract_precision(args.last)
+ klass.new(precision: precision)
+ end
+ end
+
def extract_scale(sql_type) # :nodoc:
case sql_type
when /\((\d+)\)/ then 0
@@ -453,7 +463,12 @@ module ActiveRecord
end
def extract_limit(sql_type) # :nodoc:
- $1.to_i if sql_type =~ /\((.*)\)/
+ case sql_type
+ when /^bigint/i
+ 8
+ when /\((.*)\)/
+ $1.to_i
+ end
end
def translate_exception_class(e, sql)
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 e9a3c26c32..8db4bcd7e3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -63,7 +63,7 @@ module ActiveRecord
def column_spec_for_primary_key(column)
spec = {}
- if column.extra == 'auto_increment'
+ if column.auto_increment?
return unless column.limit == 8
spec[:id] = ':bigint'
else
@@ -73,14 +73,17 @@ module ActiveRecord
spec
end
+ def prepare_column_options(column)
+ spec = super
+ spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
+ spec
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
- attr_reader :collation, :strict, :extra
+ delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
- def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
- @strict = strict
- @collation = collation
- @extra = extra
- super(name, default, cast_type, sql_type, null)
+ def initialize(*)
+ super
assert_valid_default(default)
extract_default
end
@@ -88,7 +91,7 @@ module ActiveRecord
def extract_default
if blob_or_text_column?
@default = null || strict ? nil : ''
- elsif missing_default_forged_as_empty_string?(@default)
+ elsif missing_default_forged_as_empty_string?(default)
@default = nil
end
end
@@ -106,11 +109,8 @@ module ActiveRecord
collation && !collation.match(/_ci$/)
end
- def ==(other)
- super &&
- collation == other.collation &&
- strict == other.strict &&
- extra == other.extra
+ def auto_increment?
+ extra == 'auto_increment'
end
private
@@ -131,9 +131,33 @@ module ActiveRecord
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
end
end
+ end
+
+ class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
+ attr_reader :collation, :extra, :strict
+
+ def initialize(type_metadata, collation: "", extra: "", strict: false)
+ super(type_metadata)
+ @type_metadata = type_metadata
+ @collation = collation
+ @extra = extra
+ @strict = strict
+ end
+
+ def ==(other)
+ other.is_a?(MysqlTypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
def attributes_for_hash
- super + [collation, strict, extra]
+ [self.class, @type_metadata, collation, extra, strict]
end
end
@@ -227,6 +251,10 @@ module ActiveRecord
version[0] >= 5
end
+ def supports_datetime_with_precision?
+ (version[0] == 5 && version[1] >= 6) || version[0] >= 6
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -243,8 +271,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
- Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
+ def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
+ Column.new(field, default, sql_type_metadata, null)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -467,8 +495,8 @@ module ActiveRecord
each_hash(result).map do |field|
field_name = set_field_encoding(field[:Field])
sql_type = field[:Type]
- cast_type = lookup_cast_type(sql_type)
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
+ type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
+ new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
end
end
end
@@ -502,7 +530,7 @@ module ActiveRecord
end
def drop_table(table_name, options = {})
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
def rename_index(table_name, old_name, new_name)
@@ -599,13 +627,6 @@ module ActiveRecord
when 0x1000000..0xffffffff; 'longtext'
else raise(ActiveRecordError, "No text type has character length #{limit}")
end
- when 'datetime'
- return super unless precision
-
- case precision
- when 0..6; "datetime(#{precision})"
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
- end
else
super
end
@@ -694,11 +715,6 @@ module ActiveRecord
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
- m.register_type(%r(datetime)i) do |sql_type|
- precision = extract_precision(sql_type)
- MysqlDateTime.new(precision: precision)
- end
-
m.register_type(%r(enum)i) do |sql_type|
limit = sql_type[/^enum\((.+)\)/i, 1]
.split(',').map{|enum| enum.strip.length - 2}.max
@@ -716,6 +732,18 @@ module ActiveRecord
end
end
+ def extract_precision(sql_type)
+ if /time/ === sql_type
+ super || 0
+ else
+ super
+ end
+ end
+
+ def fetch_type_metadata(sql_type, collation = "", extra = "")
+ MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
+ end
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
# to give it some prompting in the form of a subsubquery. Ugh!
def subquery_for(key, select)
@@ -790,7 +818,7 @@ module ActiveRecord
options = {
default: column.default,
null: column.null,
- auto_increment: column.extra == "auto_increment"
+ auto_increment: column.auto_increment?
}
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
@@ -894,24 +922,8 @@ module ActiveRecord
TableDefinition.new(native_database_types, name, temporary, options, as)
end
- class MysqlDateTime < Type::DateTime # :nodoc:
- def type_cast_for_database(value)
- if value.acts_like?(:time) && value.respond_to?(:usec)
- result = super.to_s(:db)
- case precision
- when 1..6
- "#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}"
- else
- result
- end
- else
- super
- end
- end
- end
-
class MysqlString < Type::String # :nodoc:
- def type_cast_for_database(value)
+ def serialize(value)
case value
when true then "1"
when false then "0"
@@ -929,6 +941,9 @@ module ActiveRecord
end
end
end
+
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
+ ActiveRecord::Type.register(:string, MysqlString, 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 e74de60a83..fa5ed07b8a 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -12,29 +12,21 @@ module ActiveRecord
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
end
- attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
+ attr_reader :name, :null, :sql_type_metadata, :default, :default_function
- delegate :type, :precision, :scale, :limit, :klass, :accessor,
- :text?, :number?, :binary?, :changed?,
- :type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
- :type_cast_for_schema,
- to: :cast_type
+ 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>.
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
- # +cast_type+ is the object used for type casting and type information.
- # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
- # <tt>company_name varchar(60)</tt>.
- # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
+ # +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
- @name = name
- @cast_type = cast_type
- @sql_type = sql_type
- @null = null
- @default = default
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil)
+ @name = name
+ @sql_type_metadata = sql_type_metadata
+ @null = null
+ @default = default
@default_function = default_function
end
@@ -50,19 +42,9 @@ module ActiveRecord
Base.human_attribute_name(@name)
end
- def with_type(type)
- dup.tap do |clone|
- clone.instance_variable_set('@cast_type', type)
- end
- end
-
def ==(other)
- other.name == name &&
- other.default == default &&
- other.cast_type == cast_type &&
- other.sql_type == sql_type &&
- other.null == null &&
- other.default_function == default_function
+ other.is_a?(Column) &&
+ attributes_for_hash == other.attributes_for_hash
end
alias :eql? :==
@@ -70,16 +52,16 @@ module ActiveRecord
attributes_for_hash.hash
end
- private
+ protected
def attributes_for_hash
- [self.class, name, default, cast_type, sql_type, null, default_function]
+ [self.class, name, default, sql_type_metadata, null, default_function]
end
end
class NullColumn < Column
def initialize(name)
- super name, nil, Type::Value.new
+ super(name, nil)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 75f244b3f3..fac6f81540 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -39,7 +39,7 @@ module ActiveRecord
MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
def initialize_schema_migrations_table
- if @config[:encoding] == 'utf8mb4'
+ if charset == 'utf8mb4'
ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4)
else
ActiveRecord::SchemaMigration.create_table
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 23d8389abb..64985ee933 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -328,8 +328,8 @@ module ActiveRecord
def initialize_type_map(m) # :nodoc:
super
- m.register_type %r(datetime)i, Fields::DateTime.new
- m.register_type %r(time)i, Fields::Time.new
+ register_class_with_precision m, %r(datetime)i, Fields::DateTime
+ register_class_with_precision m, %r(time)i, Fields::Time
end
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
@@ -395,11 +395,9 @@ module ActiveRecord
def exec_stmt(sql, name, binds)
cache = {}
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds) do
+ log(sql, name, binds) do
if binds.empty?
stmt = @connection.prepare(sql)
else
@@ -410,7 +408,7 @@ module ActiveRecord
end
begin
- stmt.execute(*type_casted_binds.map { |_, val| val })
+ stmt.execute(*type_casted_binds)
rescue Mysql::Error => e
# Older versions of MySQL leave the prepared statement in a bad
# place when an error occurs. To support older MySQL versions, we
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index acb1278499..0eb4fb468c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -2,19 +2,9 @@ module ActiveRecord
module ConnectionAdapters
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
- attr_reader :array
+ delegate :array, :oid, :fmod, to: :sql_type_metadata
alias :array? :array
- def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
- if sql_type =~ /\[\]$/
- @array = true
- sql_type = sql_type[0..sql_type.length - 3]
- else
- @array = false
- end
- super
- end
-
def serial?
default_function && default_function =~ /\Anextval\(.*\)\z/
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index d28a2b4fa0..92349e2f9b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,25 +1,19 @@
-require 'active_record/connection_adapters/postgresql/oid/infinity'
-
require 'active_record/connection_adapters/postgresql/oid/array'
require 'active_record/connection_adapters/postgresql/oid/bit'
require 'active_record/connection_adapters/postgresql/oid/bit_varying'
require 'active_record/connection_adapters/postgresql/oid/bytea'
require 'active_record/connection_adapters/postgresql/oid/cidr'
-require 'active_record/connection_adapters/postgresql/oid/date'
require 'active_record/connection_adapters/postgresql/oid/date_time'
require 'active_record/connection_adapters/postgresql/oid/decimal'
require 'active_record/connection_adapters/postgresql/oid/enum'
-require 'active_record/connection_adapters/postgresql/oid/float'
require 'active_record/connection_adapters/postgresql/oid/hstore'
require 'active_record/connection_adapters/postgresql/oid/inet'
-require 'active_record/connection_adapters/postgresql/oid/integer'
require 'active_record/connection_adapters/postgresql/oid/json'
require 'active_record/connection_adapters/postgresql/oid/jsonb'
require 'active_record/connection_adapters/postgresql/oid/money'
require 'active_record/connection_adapters/postgresql/oid/point'
require 'active_record/connection_adapters/postgresql/oid/range'
require 'active_record/connection_adapters/postgresql/oid/specialized_string'
-require 'active_record/connection_adapters/postgresql/oid/time'
require 'active_record/connection_adapters/postgresql/oid/uuid'
require 'active_record/connection_adapters/postgresql/oid/vector'
require 'active_record/connection_adapters/postgresql/oid/xml'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index e45a2f59d9..fb4e0de2a8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Array < Type::Value # :nodoc:
- include Type::Mutable
+ include Type::Helpers::Mutable
# Loads pg_array_parser if available. String parsing can be
# performed quicker by a native extension, which will not create
@@ -25,22 +25,22 @@ module ActiveRecord
@delimiter = delimiter
end
- def type_cast_from_database(value)
+ def deserialize(value)
if value.is_a?(::String)
- type_cast_array(parse_pg_array(value), :type_cast_from_database)
+ type_cast_array(parse_pg_array(value), :deserialize)
else
super
end
end
- def type_cast_from_user(value)
+ def cast(value)
if value.is_a?(::String)
value = parse_pg_array(value)
end
- type_cast_array(value, :type_cast_from_user)
+ type_cast_array(value, :cast)
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Array)
cast_value_for_database(value)
else
@@ -48,6 +48,12 @@ module ActiveRecord
end
end
+ def ==(other)
+ other.is_a?(Array) &&
+ subtype == other.subtype &&
+ delimiter == other.delimiter
+ end
+
private
def type_cast_array(value, method)
@@ -63,7 +69,7 @@ module ActiveRecord
casted_values = value.map { |item| cast_value_for_database(item) }
"{#{casted_values.join(delimiter)}}"
else
- quote_and_escape(subtype.type_cast_for_database(value))
+ quote_and_escape(subtype.serialize(value))
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
index 1dbb40ca1d..ea0fa2517f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
@@ -7,7 +7,7 @@ module ActiveRecord
:bit
end
- def type_cast(value)
+ def cast(value)
if ::String === value
case value
when /^0x/i
@@ -20,7 +20,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
Data.new(super) if value
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
index 6bd1b8ecae..8f9d6e7f9b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Bytea < Type::Binary # :nodoc:
- def type_cast_from_database(value)
+ def deserialize(value)
return if value.nil?
return value.to_s if value.is_a?(Type::Binary::Data)
PGconn.unescape_bytea(super)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
index 222f10fa8f..eeccb09bdf 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -18,7 +18,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if IPAddr === value
"#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb
deleted file mode 100644
index 1d8d264530..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Date < Type::Date # :nodoc:
- include Infinity
- end
- end
- end
- end
-end
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 b9e7894e5c..2c04c46131 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
@@ -3,8 +3,6 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class DateTime < Type::DateTime # :nodoc:
- include Infinity
-
def cast_value(value)
if value.is_a?(::String)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
index 77d5038efd..b3b610a5f6 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
@@ -7,7 +7,7 @@ module ActiveRecord
:enum
end
- def type_cast(value)
+ def cast(value)
value.to_s
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
deleted file mode 100644
index 78ef94b912..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Float < Type::Float # :nodoc:
- include Infinity
-
- def cast_value(value)
- case value
- when ::Float then value
- when 'Infinity' then ::Float::INFINITY
- when '-Infinity' then -::Float::INFINITY
- when 'NaN' then ::Float::NAN
- else value.to_f
- end
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
index be4525c94f..9270fc9f21 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
@@ -3,13 +3,13 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Hstore < Type::Value # :nodoc:
- include Type::Mutable
+ include Type::Helpers::Mutable
def type
:hstore
end
- def type_cast_from_database(value)
+ def deserialize(value)
if value.is_a?(::String)
::Hash[value.scan(HstorePair).map { |k, v|
v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
@@ -21,7 +21,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Hash)
value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ')
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb
deleted file mode 100644
index e47780399a..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- module Infinity # :nodoc:
- def infinity(options = {})
- options[:negative] ? -::Float::INFINITY : ::Float::INFINITY
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb
deleted file mode 100644
index 59abdc0009..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Integer < Type::Integer # :nodoc:
- include Infinity
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
index e12ddd9901..8e1256baad 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
@@ -3,25 +3,25 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Json < Type::Value # :nodoc:
- include Type::Mutable
+ include Type::Helpers::Mutable
def type
:json
end
- def type_cast_from_database(value)
+ def deserialize(value)
if value.is_a?(::String)
- ::ActiveSupport::JSON.decode(value)
+ ::ActiveSupport::JSON.decode(value) rescue nil
else
- super
+ value
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Array) || value.is_a?(::Hash)
::ActiveSupport::JSON.encode(value)
else
- super
+ value
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
index 380c50fc14..afc9383f91 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
@@ -13,7 +13,7 @@ module ActiveRecord
# the comparison here. Therefore, we need to parse and re-dump the
# raw value here to ensure the insignificant whitespaces are
# consistent with our encoder's output.
- raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
+ raw_old_value = serialize(deserialize(raw_old_value))
super(raw_old_value, new_value)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
index df890c2ed6..2163674019 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -3,8 +3,6 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Money < Type::Decimal # :nodoc:
- include Infinity
-
class_attribute :precision
def type
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
index bac8b01d6b..bf565bcf47 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
@@ -3,19 +3,19 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Point < Type::Value # :nodoc:
- include Type::Mutable
+ include Type::Helpers::Mutable
def type
:point
end
- def type_cast(value)
+ def cast(value)
case value
when ::String
if value[0] == '(' && value[-1] == ')'
value = value[1...-1]
end
- type_cast(value.split(','))
+ cast(value.split(','))
when ::Array
value.map { |v| Float(v) }
else
@@ -23,7 +23,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Array)
"(#{number_for_point(value[0])},#{number_for_point(value[1])})"
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index 3adfb8b9d8..fc201f8fb9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -7,7 +7,7 @@ module ActiveRecord
class Range < Type::Value # :nodoc:
attr_reader :subtype, :type
- def initialize(subtype, type)
+ def initialize(subtype, type = :range)
@subtype = subtype
@type = type
end
@@ -30,7 +30,7 @@ module ActiveRecord
::Range.new(from, to, extracted[:exclude_end])
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Range)
from = type_cast_single_for_database(value.begin)
to = type_cast_single_for_database(value.end)
@@ -40,26 +40,42 @@ module ActiveRecord
end
end
+ def ==(other)
+ other.is_a?(Range) &&
+ other.subtype == subtype &&
+ other.type == type
+ end
+
private
def type_cast_single(value)
- infinity?(value) ? value : @subtype.type_cast_from_database(value)
+ infinity?(value) ? value : @subtype.deserialize(value)
end
def type_cast_single_for_database(value)
- infinity?(value) ? '' : @subtype.type_cast_for_database(value)
+ infinity?(value) ? '' : @subtype.serialize(value)
end
def extract_bounds(value)
from, to = value[1..-2].split(',')
{
- from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
- to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
+ from: (value[1] == ',' || from == '-infinity') ? infinity(negative: true) : from,
+ to: (value[-2] == ',' || to == 'infinity') ? infinity : to,
exclude_start: (value[0] == '('),
exclude_end: (value[-1] == ')')
}
end
+ def infinity(negative: false)
+ if subtype.respond_to?(:infinity)
+ subtype.infinity(negative: negative)
+ elsif negative
+ -::Float::INFINITY
+ else
+ ::Float::INFINITY
+ end
+ end
+
def infinity?(value)
value.respond_to?(:infinite?) && value.infinite?
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
index b2a42e9ebb..2d2fede4e8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
@@ -8,10 +8,6 @@ module ActiveRecord
def initialize(type)
@type = type
end
-
- def text?
- false
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb
deleted file mode 100644
index 8f0246eddb..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Time < Type::Time # :nodoc:
- include Infinity
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
index 97b4fd3d08..5e839228e9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -5,13 +5,13 @@ module ActiveRecord
class Uuid < Type::Value # :nodoc:
ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
- alias_method :type_cast_for_database, :type_cast_from_database
+ alias_method :serialize, :deserialize
def type
:uuid
end
- def type_cast(value)
+ def cast(value)
value.to_s[ACCEPTABLE_UUID, 0]
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
index de4187b028..b26e876b54 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
@@ -16,7 +16,7 @@ module ActiveRecord
# FIXME: this should probably split on +delim+ and use +subtype+
# to cast the values. Unfortunately, the current Rails behavior
# is to just return the string.
- def type_cast(value)
+ def cast(value)
value
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
index 334af7c598..d40d837cee 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
@@ -7,7 +7,7 @@ module ActiveRecord
:xml
end
- def type_cast_for_database(value)
+ def serialize(value)
return unless value
Data.new(super)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 9de9e2c7dc..b7755c4593 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -52,15 +52,21 @@ module ActiveRecord
end
# Does not quote function default values for UUID columns
- def quote_default_value(value, column) #:nodoc:
+ def quote_default_expression(value, column) #:nodoc:
if column.type == :uuid && value =~ /\(\)/
value
- else
- value = column.cast_type.type_cast_for_database(value)
+ elsif column.respond_to?(:array?)
+ value = type_cast_from_column(column, value)
quote(value)
+ else
+ super
end
end
+ def lookup_cast_type_from_column(column) # :nodoc:
+ type_map.lookup(column.oid, column.fmod, column.sql_type)
+ end
+
private
def _quote(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index a9522e152f..b9078d4c86 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -76,15 +76,15 @@ module ActiveRecord
column(name, :point, options)
end
- def bit(name, options)
+ def bit(name, options = {})
column(name, :bit, options)
end
- def bit_varying(name, options)
+ def bit_varying(name, options = {})
column(name, :bit_varying, options)
end
- def money(name, options)
+ def money(name, options = {})
column(name, :money, options)
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 a90adcf4aa..75b9d079bd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -4,34 +4,10 @@ module ActiveRecord
class SchemaCreation < AbstractAdapter::SchemaCreation
private
- def column_options(o)
- column_options = super
- column_options[:array] = o.array
- column_options
- end
-
- def add_column_options!(sql, options)
- if options[:array]
- sql << '[]'
- end
+ def visit_ColumnDefinition(o)
+ o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
super
end
-
- def quote_default_expression(value, column)
- if column.type == :uuid && value =~ /\(\)/
- value
- else
- super
- end
- end
-
- def type_for_column(column)
- if column.array
- @conn.lookup_cast_type("#{column.sql_type}[]")
- else
- super
- end
- end
end
module SchemaStatements
@@ -112,7 +88,7 @@ module ActiveRecord
end
def drop_table(table_name, options = {})
- execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
# Returns true if schema exists.
@@ -183,15 +159,17 @@ module ActiveRecord
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
- oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
- default_value = extract_value_from_default(oid, default)
+ oid = oid.to_i
+ fmod = fmod.to_i
+ type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
+ default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
+ new_column(column_name, default_value, type_metadata, notnull == 'f', default_function)
end
end
- def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil) # :nodoc:
+ PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function)
end
# Returns the current database name.
@@ -428,12 +406,14 @@ module ActiveRecord
def change_column(table_name, column_name, type, options = {})
clear_cache!
quoted_table_name = quote_table_name(table_name)
- sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
- sql_type << "[]" if options[:array]
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
- sql << " USING #{options[:using]}" if options[:using]
- if options[:cast_as]
- sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
+ quoted_column_name = quote_column_name(column_name)
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
+ if options[:using]
+ sql << " USING #{options[:using]}"
+ elsif options[:cast_as]
+ cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
+ sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
end
execute sql
@@ -453,7 +433,7 @@ module ActiveRecord
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
execute alter_column_query % "DROP DEFAULT"
else
- execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
+ execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
end
end
@@ -461,7 +441,7 @@ module ActiveRecord
clear_cache!
unless null || default.nil?
column = column_for(table_name, column_name)
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
end
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
@@ -530,8 +510,8 @@ module ActiveRecord
end
# Maps logical Rails types to PostgreSQL-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, array = nil)
+ sql = case type.to_s
when 'binary'
# PostgreSQL doesn't support limits on binary (bytea) columns.
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
@@ -547,24 +527,18 @@ module ActiveRecord
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
end
when 'integer'
- return 'integer' unless limit
-
case limit
- when 1, 2; 'smallint'
- when 3, 4; 'integer'
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
- end
- when 'datetime'
- return super unless precision
-
- case precision
- when 0..6; "timestamp(#{precision})"
- else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
+ when 1, 2; 'smallint'
+ when nil, 3, 4; 'integer'
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
end
else
- super
+ super(type, limit, precision, scale)
end
+
+ sql << '[]' if array && type != :primary_key
+ sql
end
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
@@ -580,6 +554,18 @@ module ActiveRecord
[super, *order_columns].join(', ')
end
+
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
+ simple_type = SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
new file mode 100644
index 0000000000..58715978f7
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -0,0 +1,35 @@
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
+ attr_reader :oid, :fmod, :array
+
+ def initialize(type_metadata, oid: nil, fmod: nil)
+ super(type_metadata)
+ @type_metadata = type_metadata
+ @oid = oid
+ @fmod = fmod
+ @array = /\[\]$/ === type_metadata.sql_type
+ end
+
+ def sql_type
+ super.gsub(/\[\]$/, "")
+ end
+
+ def ==(other)
+ other.is_a?(PostgreSQLTypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
+
+ def attributes_for_hash
+ [self.class, @type_metadata, oid, fmod]
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index f4f9747359..6d25b53b21 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,14 +1,14 @@
-require 'active_record/connection_adapters/abstract_adapter'
-require 'active_record/connection_adapters/statement_pool'
-
-require 'active_record/connection_adapters/postgresql/utils'
-require 'active_record/connection_adapters/postgresql/column'
-require 'active_record/connection_adapters/postgresql/oid'
-require 'active_record/connection_adapters/postgresql/quoting'
-require 'active_record/connection_adapters/postgresql/referential_integrity'
-require 'active_record/connection_adapters/postgresql/schema_definitions'
-require 'active_record/connection_adapters/postgresql/schema_statements'
-require 'active_record/connection_adapters/postgresql/database_statements'
+require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/postgresql/column"
+require "active_record/connection_adapters/postgresql/database_statements"
+require "active_record/connection_adapters/postgresql/oid"
+require "active_record/connection_adapters/postgresql/quoting"
+require "active_record/connection_adapters/postgresql/referential_integrity"
+require "active_record/connection_adapters/postgresql/schema_definitions"
+require "active_record/connection_adapters/postgresql/schema_statements"
+require "active_record/connection_adapters/postgresql/type_metadata"
+require "active_record/connection_adapters/postgresql/utils"
+require "active_record/connection_adapters/statement_pool"
require 'arel/visitors/bind_visitor'
@@ -180,6 +180,10 @@ module ActiveRecord
true
end
+ def supports_datetime_with_precision?
+ true
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -255,6 +259,8 @@ module ActiveRecord
@table_alias_length = nil
connect
+ add_pg_decoders
+
@statements = StatementPool.new @connection,
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
@@ -459,11 +465,11 @@ module ActiveRecord
end
def initialize_type_map(m) # :nodoc:
- register_class_with_limit m, 'int2', OID::Integer
- register_class_with_limit m, 'int4', OID::Integer
- register_class_with_limit m, 'int8', OID::Integer
+ register_class_with_limit m, 'int2', Type::Integer
+ register_class_with_limit m, 'int4', Type::Integer
+ register_class_with_limit m, 'int8', Type::Integer
m.alias_type 'oid', 'int2'
- m.register_type 'float4', OID::Float.new
+ m.register_type 'float4', Type::Float.new
m.alias_type 'float8', 'float4'
m.register_type 'text', Type::Text.new
register_class_with_limit m, 'varchar', Type::String
@@ -474,8 +480,7 @@ module ActiveRecord
register_class_with_limit m, 'bit', OID::Bit
register_class_with_limit m, 'varbit', OID::BitVarying
m.alias_type 'timestamptz', 'timestamp'
- m.register_type 'date', OID::Date.new
- m.register_type 'time', OID::Time.new
+ m.register_type 'date', Type::Date.new
m.register_type 'money', OID::Money.new
m.register_type 'bytea', OID::Bytea.new
@@ -501,10 +506,8 @@ module ActiveRecord
m.alias_type 'lseg', 'varchar'
m.alias_type 'box', 'varchar'
- m.register_type 'timestamp' do |_, _, sql_type|
- precision = extract_precision(sql_type)
- OID::DateTime.new(precision: precision)
- end
+ register_class_with_precision m, 'time', Type::Time
+ register_class_with_precision m, 'timestamp', OID::DateTime
m.register_type 'numeric' do |_, fmod, sql_type|
precision = extract_precision(sql_type)
@@ -541,7 +544,7 @@ module ActiveRecord
end
# Extracts the value from a PostgreSQL column default definition.
- def extract_value_from_default(oid, default) # :nodoc:
+ def extract_value_from_default(default) # :nodoc:
case default
# Quoted types
when /\A[\(B]?'(.*)'::/m
@@ -609,12 +612,10 @@ module ActiveRecord
def exec_cache(sql, name, binds)
stmt_key = prepare_statement(sql)
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds, stmt_key) do
- @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
+ log(sql, name, binds, stmt_key) do
+ @connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
pgerror = e.original_exception
@@ -764,6 +765,73 @@ module ActiveRecord
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
end
+
+ def can_perform_case_insensitive_comparison_for?(column)
+ @case_insensitive_cache ||= {}
+ @case_insensitive_cache[column.sql_type] ||= begin
+ sql = <<-end_sql
+ SELECT exists(
+ SELECT * FROM pg_proc
+ INNER JOIN pg_cast
+ ON casttarget::text::oidvector = proargtypes
+ WHERE proname = 'lower'
+ AND castsource = '#{column.sql_type}'::regtype::oid
+ )
+ end_sql
+ execute_and_clear(sql, "SCHEMA", []) do |result|
+ result.getvalue(0, 0) == 't'
+ end
+ end
+ end
+
+ def add_pg_decoders
+ coders_by_name = {
+ 'int2' => PG::TextDecoder::Integer,
+ 'int4' => PG::TextDecoder::Integer,
+ 'int8' => PG::TextDecoder::Integer,
+ 'oid' => PG::TextDecoder::Integer,
+ 'float4' => PG::TextDecoder::Float,
+ 'float8' => PG::TextDecoder::Float,
+ 'bool' => PG::TextDecoder::Boolean,
+ }
+ query = <<-SQL
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
+ FROM pg_type as t
+ SQL
+ coders = execute_and_clear(query, "SCHEMA", []) do |result|
+ result
+ .map { |row| construct_coder(row, coders_by_name['typname']) }
+ .compact
+ end
+
+ map = PG::TypeMapByOid.new
+ coders.each { |coder| map.add_coder(coder) }
+ @connection.type_map_for_results = map
+ end
+
+ def construct_coder(row, coder_class)
+ return unless coder_class
+ coder_class.new(oid: row['oid'], name: row['typname'])
+ end
+
+ ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
+ ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
+ ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
+ ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
+ ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
+ ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
+ ActiveRecord::Type.register(:date_time, OID::DateTime, adapter: :postgresql)
+ ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
+ ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
+ ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
+ ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
+ ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql)
+ ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
+ ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
+ ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
+ ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
+ ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
+ ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb
new file mode 100644
index 0000000000..ccb7e154ee
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ # :stopdoc:
+ module ConnectionAdapters
+ class SqlTypeMetadata
+ attr_reader :sql_type, :type, :limit, :precision, :scale
+
+ def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
+ @sql_type = sql_type
+ @type = type
+ @limit = limit
+ @precision = precision
+ @scale = scale
+ end
+
+ def ==(other)
+ other.is_a?(SqlTypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
+
+ def attributes_for_hash
+ [self.class, sql_type, type, limit, precision, scale]
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 03dfd29a0a..400b586c95 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -41,25 +41,6 @@ module ActiveRecord
end
module ConnectionAdapters #:nodoc:
- class SQLite3Binary < Type::Binary # :nodoc:
- def cast_value(value)
- if value.encoding != Encoding::ASCII_8BIT
- value = value.force_encoding(Encoding::ASCII_8BIT)
- end
- value
- end
- end
-
- class SQLite3String < Type::String # :nodoc:
- def type_cast_for_database(value)
- if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT
- value.encode(Encoding::UTF_8)
- else
- super
- end
- end
- end
-
# The SQLite3 adapter works SQLite 3.6.16 or newer
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
#
@@ -239,6 +220,12 @@ module ActiveRecord
case value
when BigDecimal
value.to_f
+ when String
+ if value.encoding == Encoding::ASCII_8BIT
+ super(value.encode(Encoding::UTF_8))
+ else
+ super
+ end
else
super
end
@@ -280,11 +267,9 @@ module ActiveRecord
end
def exec_query(sql, name = nil, binds = [])
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds) do
+ log(sql, name, binds) do
# Don't cache statements if they are not prepared
if without_prepared_statement?(binds)
stmt = @connection.prepare(sql)
@@ -302,7 +287,7 @@ module ActiveRecord
stmt = cache[:stmt]
cols = cache[:cols] ||= stmt.columns
stmt.reset!
- stmt.bind_params type_casted_binds.map { |_, val| val }
+ stmt.bind_params type_casted_binds
end
ActiveRecord::Result.new(cols, stmt.to_a)
@@ -387,8 +372,8 @@ module ActiveRecord
end
sql_type = field['type']
- cast_type = lookup_cast_type(sql_type)
- new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
+ type_metadata = fetch_type_metadata(sql_type)
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0)
end
end
@@ -495,12 +480,6 @@ module ActiveRecord
protected
- def initialize_type_map(m)
- super
- m.register_type(/binary/i, SQLite3Binary.new)
- register_class_with_limit m, %r(char)i, SQLite3String
- end
-
def table_structure(table_name)
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index a7aff9f724..1244bd6195 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -88,7 +88,6 @@ module ActiveRecord
mattr_accessor :maintain_test_schema, instance_accessor: false
class_attribute :default_connection_handler, instance_writer: false
- class_attribute :find_by_statement_cache
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
@@ -107,20 +106,19 @@ module ActiveRecord
super
end
- def initialize_find_by_cache
- self.find_by_statement_cache = {}.extend(Mutex_m)
+ def initialize_find_by_cache # :nodoc:
+ @find_by_statement_cache = {}.extend(Mutex_m)
end
- def inherited(child_class)
+ def inherited(child_class) # :nodoc:
+ # initialize cache at class definition for thread safety
child_class.initialize_find_by_cache
super
end
- def find(*ids)
+ def find(*ids) # :nodoc:
# We don't have cache keys for this stuff yet
return super unless ids.length == 1
- # Allow symbols to super to maintain compatibility for deprecated finders until Rails 5
- return super if ids.first.kind_of?(Symbol)
return super if block_given? ||
primary_key.nil? ||
default_scopes.any? ||
@@ -136,14 +134,13 @@ module ActiveRecord
Please pass the id of the object by calling `.id`
MSG
end
+
key = primary_key
- s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
- find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
- where(key => params.bind).limit(1)
- }
+ statement = cached_find_by_statement(key) { |params|
+ where(key => params.bind).limit(1)
}
- record = s.execute([id], self, connection).first
+ record = statement.execute([id], self, connection).first
unless record
raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
end
@@ -152,7 +149,7 @@ module ActiveRecord
raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
end
- def find_by(*args)
+ def find_by(*args) # :nodoc:
return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
return super if default_scopes.any?
@@ -165,19 +162,16 @@ module ActiveRecord
# We can't cache Post.find_by(author: david) ...yet
return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
- key = hash.keys
+ keys = hash.keys
- klass = self
- s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
- find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
- wheres = key.each_with_object({}) { |param,o|
- o[param] = params.bind
- }
- klass.where(wheres).limit(1)
+ statement = cached_find_by_statement(keys) { |params|
+ wheres = keys.each_with_object({}) { |param, o|
+ o[param] = params.bind
}
+ where(wheres).limit(1)
}
begin
- s.execute(hash.values, self, connection).first
+ statement.execute(hash.values, self, connection).first
rescue TypeError => e
raise ActiveRecord::StatementInvalid.new(e.message, e)
rescue RangeError
@@ -185,11 +179,11 @@ module ActiveRecord
end
end
- def find_by!(*args)
+ def find_by!(*args) # :nodoc:
find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}")
end
- def initialize_generated_modules
+ def initialize_generated_modules # :nodoc:
generated_association_methods
end
@@ -210,7 +204,7 @@ module ActiveRecord
elsif !connected?
"#{super} (call '#{super}.connection' to establish a connection)"
elsif table_exists?
- attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
+ attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ', '
"#{super}(#{attr_list})"
else
"#{super}(Table doesn't exist)"
@@ -251,6 +245,12 @@ module ActiveRecord
private
+ def cached_find_by_statement(key, &block) # :nodoc:
+ @find_by_statement_cache[key] || @find_by_statement_cache.synchronize {
+ @find_by_statement_cache[key] ||= StatementCache.create(connection, &block)
+ }
+ end
+
def relation # :nodoc:
relation = Relation.create(self, arel_table, predicate_builder)
@@ -345,9 +345,6 @@ module ActiveRecord
_run_initialize_callbacks
- @aggregation_cache = {}
- @association_cache = {}
-
@new_record = true
@destroyed = false
@@ -456,6 +453,7 @@ module ActiveRecord
# Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
# when pp is required.
def pretty_print(pp)
+ return super if custom_inspect_method_defined?
pp.object_address_group(self) do
if defined?(@attributes) && @attributes
column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
@@ -481,16 +479,16 @@ module ActiveRecord
Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
end
+ private
+
def set_transaction_state(state) # :nodoc:
@transaction_state = state
end
def has_transactional_callbacks? # :nodoc:
- !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_create_callbacks.empty?
+ !_rollback_callbacks.empty? || !_commit_callbacks.empty?
end
- private
-
# Updates the attributes on this particular ActiveRecord object so that
# if it is associated with a transaction, then the state of the AR object
# will be updated to reflect the current state of the transaction
@@ -513,6 +511,8 @@ module ActiveRecord
end
def update_attributes_from_transaction_state(transaction_state, depth)
+ @reflects_state = [false] if depth == 0
+
if transaction_state && transaction_state.finalized? && !has_transactional_callbacks?
unless @reflects_state[depth]
restore_transaction_record_state if transaction_state.rolledback?
@@ -539,8 +539,6 @@ module ActiveRecord
end
def init_internals
- @aggregation_cache = {}
- @association_cache = {}
@readonly = false
@destroyed = false
@marked_for_destruction = false
@@ -549,7 +547,6 @@ module ActiveRecord
@txn = nil
@_start_transaction_state = {}
@transaction_state = nil
- @reflects_state = [false]
end
def initialize_internals_callback
@@ -560,5 +557,9 @@ module ActiveRecord
@attributes = @attributes.dup
end
end
+
+ def custom_inspect_method_defined?
+ self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
+ end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 7d8e0a2063..82596b63df 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -156,7 +156,7 @@ module ActiveRecord
def each_counter_cached_associations
_reflections.each do |name, reflection|
- yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
+ yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
end
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index f053372cfb..ea88983917 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -32,6 +32,12 @@ module ActiveRecord
# Conversation.active
# Conversation.archived
#
+ # Of course, you can also query them directly if the scopes doesn't fit your
+ # needs:
+ #
+ # Conversation.where(status: [:active, :archived])
+ # Conversation.where.not(status: :active)
+ #
# You can set the default value from the database declaration, like:
#
# create_table :conversations do |t|
@@ -59,15 +65,17 @@ module ActiveRecord
#
# In rare circumstances you might need to access the mapping directly.
# The mappings are exposed through a class method with the pluralized attribute
- # name:
+ # name, which return the mapping in a +HashWithIndifferentAccess+:
#
- # Conversation.statuses # => { "active" => 0, "archived" => 1 }
+ # Conversation.statuses[:active] # => 0
+ # Conversation.statuses["archived"] # => 1
#
- # Use that class method when you need to know the ordinal value of an enum:
+ # Use that class method when you need to know the ordinal value of an enum.
+ # For example, you can use that when manually building SQL strings:
#
# Conversation.where("status <> ?", Conversation.statuses[:archived])
#
- # Where conditions on an enum attribute must use the ordinal value of an enum.
+
module Enum
def self.extended(base) # :nodoc:
base.class_attribute(:defined_enums)
@@ -79,6 +87,38 @@ module ActiveRecord
super
end
+ class EnumType < Type::Value
+ def initialize(name, mapping)
+ @name = name
+ @mapping = mapping
+ end
+
+ def cast(value)
+ return if value.blank?
+
+ if mapping.has_key?(value)
+ value.to_s
+ elsif mapping.has_value?(value)
+ mapping.key(value)
+ else
+ raise ArgumentError, "'#{value}' is not a valid #{name}"
+ end
+ end
+
+ def deserialize(value)
+ return if value.nil?
+ mapping.key(value.to_i)
+ end
+
+ def serialize(value)
+ mapping.fetch(value, value)
+ end
+
+ protected
+
+ attr_reader :name, :mapping
+ end
+
def enum(definitions)
klass = self
definitions.each do |name, values|
@@ -90,37 +130,19 @@ module ActiveRecord
detect_enum_conflict!(name, name.to_s.pluralize, true)
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
- _enum_methods_module.module_eval do
- # def status=(value) self[:status] = statuses[value] end
- klass.send(:detect_enum_conflict!, name, "#{name}=")
- define_method("#{name}=") { |value|
- if enum_values.has_key?(value) || value.blank?
- self[name] = enum_values[value]
- elsif enum_values.has_value?(value)
- # Assigning a value directly is not a end-user feature, hence it's not documented.
- # This is used internally to make building objects from the generated scopes work
- # as expected, i.e. +Conversation.archived.build.archived?+ should be true.
- self[name] = value
- else
- raise ArgumentError, "'#{value}' is not a valid #{name}"
- end
- }
-
- # def status() statuses.key self[:status] end
- klass.send(:detect_enum_conflict!, name, name)
- define_method(name) { enum_values.key self[name] }
+ detect_enum_conflict!(name, name)
+ detect_enum_conflict!(name, "#{name}=")
- # def status_before_type_cast() statuses.key self[:status] end
- klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast")
- define_method("#{name}_before_type_cast") { enum_values.key self[name] }
+ attribute name, EnumType.new(name, enum_values)
+ _enum_methods_module.module_eval do
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
pairs.each do |value, i|
enum_values[value] = i
# def active?() status == 0 end
klass.send(:detect_enum_conflict!, name, "#{value}?")
- define_method("#{value}?") { self[name] == i }
+ define_method("#{value}?") { self[name] == value.to_s }
# def active!() update! status: :active end
klass.send(:detect_enum_conflict!, name, "#{value}!")
@@ -128,7 +150,7 @@ module ActiveRecord
# scope :active, -> { where status: 0 }
klass.send(:detect_enum_conflict!, name, value, true)
- klass.scope value, -> { klass.where name => i }
+ klass.scope value, -> { klass.where name => value }
end
end
defined_enums[name.to_s] = enum_values
@@ -138,25 +160,7 @@ module ActiveRecord
private
def _enum_methods_module
@_enum_methods_module ||= begin
- mod = Module.new do
- private
- def save_changed_attribute(attr_name, old)
- if (mapping = self.class.defined_enums[attr_name.to_s])
- value = _read_attribute(attr_name)
- if attribute_changed?(attr_name)
- if mapping[old] == value
- clear_attribute_changes([attr_name])
- end
- else
- if old != value
- set_attribute_was(attr_name, mapping.key(old))
- end
- end
- else
- super
- end
- end
- end
+ mod = Module.new
include mod
mod
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index fc28ab585f..d710d96a9a 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -178,18 +178,10 @@ module ActiveRecord
class DangerousAttributeError < ActiveRecordError
end
- # Raised when unknown attributes are supplied via mass assignment.
- class UnknownAttributeError < NoMethodError
-
- attr_reader :record, :attribute
-
- def initialize(record, attribute)
- @record = record
- @attribute = attribute.to_s
- super("unknown attribute '#{attribute}' for #{@record.class}.")
- end
-
- end
+ UnknownAttributeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( # :nodoc:
+ 'ActiveRecord::UnknownAttributeError',
+ 'ActiveModel::AttributeAssignment::UnknownAttributeError'
+ )
# Raised when an error occurred while doing a mass assignment to an attribute through the
# +attributes=+ method. The exception has an +attribute+ property that is the name of the
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 10e9be20b5..739be524da 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'yaml'
require 'zlib'
+require 'set'
require 'active_support/dependencies'
require 'active_support/core_ext/digest/uuid'
require 'active_record/fixture_set/file'
@@ -131,7 +132,7 @@ module ActiveRecord
# Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
# end
# end
- # ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+ # ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
#
# - use the helper method in a fixture
# photo:
@@ -521,12 +522,16 @@ module ActiveRecord
update_all_loaded_fixtures fixtures_map
connection.transaction(:requires_new => true) do
+ deleted_tables = Set.new
fixture_sets.each do |fs|
conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
table_rows = fs.table_rows
table_rows.each_key do |table|
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ unless deleted_tables.include? table
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
+ deleted_tables << table
end
table_rows.each do |fixture_set_name, rows|
@@ -661,7 +666,7 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- fk_type = association.active_record.columns_hash[fk_name].type
+ fk_type = association.active_record.type_for_attribute(fk_name).type
row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
end
when :has_many
@@ -691,7 +696,7 @@ module ActiveRecord
end
def primary_key_type
- @association.klass.column_types[@association.klass.primary_key].type
+ @association.klass.type_for_attribute(@association.klass.primary_key).type
end
end
@@ -711,7 +716,7 @@ module ActiveRecord
end
def primary_key_type
- @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type
+ @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type
end
def add_join_records(rows, row, association)
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index fd1e22349b..24098f72dc 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -192,7 +192,7 @@ module ActiveRecord
# If this is a StrongParameters hash, and access to inheritance_column is not permitted,
# this will ignore the inheritance column and return nil
def subclass_from_attributes?(attrs)
- columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
+ attribute_names.include?(inheritance_column) && attrs.is_a?(Hash)
end
def subclass_from_attributes(attrs)
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index b1fbd38622..8a3c27e6da 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -7,6 +7,7 @@ en:
# Default error messages
errors:
messages:
+ required: "must exist"
taken: "has already been taken"
# Active Record models configuration
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 6f2b65c137..a58d8355aa 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -144,7 +144,7 @@ module ActiveRecord
# Set the column to use for optimistic locking. Defaults to +lock_version+.
def locking_column=(value)
- clear_caches_calculated_from_columns
+ reload_schema_from_cache
@locking_column = value.to_s
end
@@ -184,8 +184,8 @@ module ActiveRecord
end
end
- class LockingType < SimpleDelegator # :nodoc:
- def type_cast_from_database(value)
+ class LockingType < DelegateClass(Type::Value) # :nodoc:
+ def deserialize(value)
# `nil` *should* be changed to 0
super.to_i
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index a5c7279db9..6b26d7be78 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -20,18 +20,14 @@ module ActiveRecord
@odd = false
end
- def render_bind(column, value)
- if column
- if column.binary? && value
- # This specifically deals with the PG adapter that casts bytea columns into a Hash.
- value = value[:value] if value.is_a?(Hash)
- value = "<#{value.bytesize} bytes of binary data>"
- end
-
- [column.name, value]
+ def render_bind(attribute)
+ value = if attribute.type.binary? && attribute.value
+ "<#{attribute.value.bytesize} bytes of binary data>"
else
- [nil, value]
+ attribute.value_for_database
end
+
+ [attribute.name, value]
end
def sql(event)
@@ -47,9 +43,7 @@ module ActiveRecord
binds = nil
unless (payload[:binds] || []).empty?
- binds = " " + payload[:binds].map { |col,v|
- render_bind(col, v)
- }.inspect
+ binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
end
if odd?
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 46f4794010..4a8e6bdd23 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -168,7 +168,7 @@ module ActiveRecord
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
# class AddFieldnameToTablename < ActiveRecord::Migration
# def change
- # add_column :tablenames, :field, :string
+ # add_column :tablenames, :fieldname, :string
# end
# end
#
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 641512d323..75adcccce6 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -111,17 +111,6 @@ module ActiveRecord
# class Mouse < ActiveRecord::Base
# self.table_name = "mice"
# end
- #
- # Alternatively, you can override the table_name method to define your
- # own computation. (Possibly using <tt>super</tt> to manipulate the default
- # table name.) Example:
- #
- # class Post < ActiveRecord::Base
- # def self.table_name
- # "special_" + super
- # end
- # end
- # Post.table_name # => "special_posts"
def table_name
reset_table_name unless defined?(@table_name)
@table_name
@@ -132,9 +121,6 @@ module ActiveRecord
# class Project < ActiveRecord::Base
# self.table_name = "project"
# end
- #
- # You can also just define your own <tt>self.table_name</tt> method; see
- # the documentation for ActiveRecord::Base#table_name.
def table_name=(value)
value = value && value.to_s
@@ -231,28 +217,37 @@ module ActiveRecord
end
def attributes_builder # :nodoc:
- @attributes_builder ||= AttributeSet::Builder.new(column_types, primary_key)
+ @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key)
end
- def column_types # :nodoc:
- @column_types ||= columns_hash.transform_values(&:cast_type).tap do |h|
- h.default = Type::Value.new
- end
+ def columns_hash # :nodoc:
+ load_schema
+ @columns_hash
+ end
+
+ def columns
+ load_schema
+ @columns ||= columns_hash.values
+ end
+
+ def attribute_types # :nodoc:
+ load_schema
+ @attribute_types ||= Hash.new(Type::Value.new)
end
def type_for_attribute(attr_name) # :nodoc:
- column_types[attr_name]
+ attribute_types[attr_name]
end
# Returns a hash where the keys are column names and the values are
# default values when instantiating the AR object for this table.
def column_defaults
+ load_schema
_default_attributes.to_hash
end
def _default_attributes # :nodoc:
- @default_attributes ||= attributes_builder.build_from_database(
- raw_default_values)
+ @default_attributes ||= AttributeSet.new({})
end
# Returns an array of column names as strings.
@@ -295,19 +290,53 @@ module ActiveRecord
def reset_column_information
connection.clear_cache!
undefine_attribute_methods
- connection.schema_cache.clear_table_cache!(table_name) if table_exists?
+ connection.schema_cache.clear_table_cache!(table_name)
- @arel_engine = nil
- @arel_table = nil
- @column_names = nil
- @column_types = nil
- @content_columns = nil
- @default_attributes = nil
- @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
+ reload_schema_from_cache
end
private
+ def schema_loaded?
+ defined?(@columns_hash) && @columns_hash
+ end
+
+ def load_schema
+ unless schema_loaded?
+ load_schema!
+ end
+ end
+
+ def load_schema!
+ @columns_hash = connection.schema_cache.columns_hash(table_name)
+ @columns_hash.each do |name, column|
+ define_attribute(
+ name,
+ connection.lookup_cast_type_from_column(column),
+ default: column.default,
+ user_provided_default: false
+ )
+ end
+ end
+
+ def reload_schema_from_cache
+ @arel_engine = nil
+ @arel_table = nil
+ @column_names = nil
+ @attribute_types = nil
+ @content_columns = nil
+ @default_attributes = nil
+ @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
+ @attributes_builder = nil
+ @column_names = nil
+ @attribute_types = nil
+ @columns = nil
+ @columns_hash = nil
+ @content_columns = nil
+ @default_attributes = nil
+ @attribute_names = nil
+ end
+
# Guesses the table name, but does not decorate it with prefix and suffix information.
def undecorated_table_name(class_name = base_class.name)
table_name = class_name.to_s.demodulize.underscore
@@ -331,10 +360,6 @@ module ActiveRecord
base.table_name
end
end
-
- def raw_default_values
- columns_hash.transform_values(&:default)
- end
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 846e1162a9..084ef397a8 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -312,7 +312,7 @@ module ActiveRecord
attr_names.each do |association_name|
if reflection = _reflect_on_association(association_name)
reflection.autosave = true
- add_autosave_association_callbacks(reflection)
+ define_autosave_validation_callbacks(reflection)
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
@@ -521,7 +521,7 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- Type::Boolean.new.type_cast_from_user(hash['_destroy'])
+ Type::Boolean.new.cast(hash['_destroy'])
end
# Determines if a new record should be rejected by checking
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index b406da14dc..fa6eaa7e64 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -30,10 +30,18 @@ module ActiveRecord
true
end
+ def none?
+ true
+ end
+
def any?
false
end
+ def one?
+ false
+ end
+
def many?
false
end
@@ -75,5 +83,9 @@ module ActiveRecord
def exists?(_id = false)
false
end
+
+ def or(other)
+ other.spawn
+ end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 714d36e7c0..7c076864a3 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -178,6 +178,7 @@ module ActiveRecord
def destroy
raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
destroy_associations
+ self.class.connection.add_transaction_record(self)
destroy_row if persisted?
@destroyed = true
freeze
@@ -207,7 +208,8 @@ module ActiveRecord
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes)
+ changed_attributes = @changed_attributes if defined?(@changed_attributes)
+ became.instance_variable_set("@changed_attributes", changed_attributes || {})
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.instance_variable_set("@errors", errors)
@@ -245,7 +247,7 @@ module ActiveRecord
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
- send("#{name}=", value)
+ public_send("#{name}=", value)
save(validate: false) if changed?
end
@@ -352,7 +354,7 @@ module ActiveRecord
# method toggles directly the underlying value without calling any setter.
# Returns +self+.
def toggle(attribute)
- self[attribute] = !send("#{attribute}?")
+ self[attribute] = !public_send("#{attribute}?")
self
end
@@ -413,9 +415,6 @@ module ActiveRecord
# end
#
def reload(options = nil)
- clear_aggregation_cache
- clear_association_cache
-
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
@@ -428,14 +427,17 @@ module ActiveRecord
self
end
- # Saves the record with the updated_at/on attributes set to the current time.
+ # Saves the record with the updated_at/on attributes set to the current time
+ # or the time specified.
# Please note that no validation is performed and only the +after_touch+,
# +after_commit+ and +after_rollback+ callbacks are executed.
#
+ # This method can be passed attribute names and an optional time argument.
# If attribute names are passed, they are updated along with updated_at/on
- # attributes.
+ # attributes. If no time argument is passed, the current time is used as default.
#
- # product.touch # updates updated_at/on
+ # product.touch # updates updated_at/on with current time
+ # product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time
# product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
# product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
#
@@ -459,19 +461,18 @@ module ActiveRecord
# ball = Ball.new
# ball.touch(:updated_at) # => raises ActiveRecordError
#
- def touch(*names)
+ def touch(*names, time: current_time_from_proper_timezone)
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
attributes = timestamp_attributes_for_update_in_model
attributes.concat(names)
unless attributes.empty?
- current_time = current_time_from_proper_timezone
changes = {}
attributes.each do |column|
column = column.to_s
- changes[column] = write_attribute(column, current_time)
+ changes[column] = write_attribute(column, time)
end
changes[self.class.locking_column] = increment_lock if locking_enabled?
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 91c9a0db99..4e597590e9 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -7,7 +7,7 @@ module ActiveRecord
delegate :find_by, :find_by!, to: :all
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
delegate :find_each, :find_in_batches, to: :all
- delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 04c2be045d..2591e7492d 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -269,9 +269,9 @@ db_namespace = namespace :db do
end
namespace :structure do
- desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
+ desc 'Dump the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql'
task :dump => [:environment, :load_config] do
- filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
+ filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
@@ -286,8 +286,8 @@ db_namespace = namespace :db do
end
desc "Recreate the databases from the structure.sql file"
- task :load => [:environment, :load_config] do
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['DB_STRUCTURE'])
+ task :load => [:load_config] do
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
task :load_if_sql => ['db:create', :environment] do
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 9c4db8a05e..6bbec7c0c0 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,18 +1,19 @@
# -*- coding: utf-8 -*-
-require 'arel/collectors/bind'
+require "arel/collectors/bind"
module ActiveRecord
# = Active Record Relation
class Relation
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
- :order, :joins, :where, :having, :bind, :references,
+ :order, :joins, :references,
:extending, :unscope]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
:reverse_order, :distinct, :create_with, :uniq]
+ CLAUSE_METHODS = [:where, :having, :from]
INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
- VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
+ VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
@@ -80,7 +81,7 @@ module ActiveRecord
end
relation = scope.where(@klass.primary_key => (id_was || id))
- bvs = binds + relation.bind_values
+ bvs = binds + relation.bound_attributes
um = relation
.arel
.compile_update(substitutes, @klass.primary_key)
@@ -94,11 +95,11 @@ module ActiveRecord
def substitute_values(values) # :nodoc:
binds = values.map do |arel_attr, value|
- [@klass.columns_hash[arel_attr.name], value]
+ QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
end
- substitutes = values.each_with_index.map do |(arel_attr, _), i|
- [arel_attr, @klass.connection.substitute_at(binds[i][0])]
+ substitutes = values.map do |(arel_attr, _)|
+ [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])]
end
[substitutes, binds]
@@ -270,6 +271,15 @@ module ActiveRecord
end
end
+ # Returns true if there are no records.
+ def none?
+ if block_given?
+ to_a.none? { |*block_args| yield(*block_args) }
+ else
+ empty?
+ end
+ end
+
# Returns true if there are any records.
def any?
if block_given?
@@ -279,6 +289,15 @@ module ActiveRecord
end
end
+ # Returns true if there is exactly one record.
+ def one?
+ if block_given?
+ to_a.one? { |*block_args| yield(*block_args) }
+ else
+ limit_value ? to_a.one? : size == 1
+ end
+ end
+
# Returns true if there is more than one record.
def many?
if block_given?
@@ -341,8 +360,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- bvs = arel.bind_values + bind_values
- @klass.connection.update stmt, 'SQL', bvs
+ @klass.connection.update stmt, 'SQL', bound_attributes
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -466,8 +484,10 @@ module ActiveRecord
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
if MULTI_VALUE_METHODS.include?(method)
send("#{method}_values").any?
- else
+ elsif SINGLE_VALUE_METHODS.include?(method)
send("#{method}_value")
+ elsif CLAUSE_METHODS.include?(method)
+ send("#{method}_clause").any?
end
}
if invalid_methods.any?
@@ -486,7 +506,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- affected = @klass.connection.delete(stmt, 'SQL', bind_values)
+ affected = @klass.connection.delete(stmt, 'SQL', bound_attributes)
reset
affected
@@ -556,11 +576,10 @@ module ActiveRecord
find_with_associations { |rel| relation = rel }
end
- arel = relation.arel
- binds = arel.bind_values + relation.bind_values
+ binds = relation.bound_attributes
binds = connection.prepare_binds_for_database(binds)
- binds.map! { |_, value| connection.quote(value) }
- collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
+ binds.map! { |value| connection.quote(value) }
+ collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new)
collect.substitute_binds(binds).join
end
end
@@ -570,22 +589,7 @@ module ActiveRecord
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
def where_values_hash(relation_table_name = table_name)
- equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
- node.left.relation.name == relation_table_name
- }
-
- binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
-
- Hash[equalities.map { |where|
- name = where.left.name
- [name, binds.fetch(name.to_s) {
- case where.right
- when Array then where.right.map(&:val)
- when Arel::Nodes::Casted, Arel::Nodes::Quoted
- where.right.val
- end
- }]
- }]
+ where_clause.to_h(relation_table_name)
end
def scope_for_create
@@ -648,7 +652,7 @@ module ActiveRecord
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values)
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
preload = preload_values
preload += includes_values unless eager_loading?
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 4f0502ae75..e07580a563 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -27,14 +27,15 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the primary key value to start from.
+ # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:start+ option on that worker).
+ # (by setting the +:begin_at+ and +:end_at+ option on each worker).
#
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
- # Person.find_each(start: 2000, batch_size: 2000) do |person|
+ # Person.find_each(begin_at: 2000, batch_size: 2000) do |person|
# person.party_all_night!
# end
#
@@ -45,19 +46,27 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(options = {})
+ def find_each(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
+ if start
+ begin_at = start
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing `start` value to find_each is deprecated, and will be removed in Rails 5.1.
+ Please pass `begin_at` instead.
+ MSG
+ end
if block_given?
- find_in_batches(options) do |records|
+ find_in_batches(begin_at: begin_at, end_at: end_at, batch_size: batch_size) do |records|
records.each { |record| yield record }
end
else
- enum_for :find_each, options do
- options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
+ enum_for(:find_each, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
+ relation = self
+ apply_limits(relation, begin_at, end_at).size
end
end
end
- # Yields each batch of records that was found by the find +options+ as
+ # Yields each batch of records that was found by the find options as
# an array.
#
# Person.where("age > 21").find_in_batches do |group|
@@ -77,14 +86,15 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the primary key value to start from.
+ # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:start+ option on that worker).
+ # (by setting the +:begin_at+ and +:end_at+ option on each worker).
#
# # Let's process the next 2000 records
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
+ # Person.find_in_batches(begin_at: 2000, batch_size: 2000) do |group|
# group.each { |person| person.party_all_night! }
# end
#
@@ -95,16 +105,19 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(options = {})
- options.assert_valid_keys(:start, :batch_size)
+ def find_in_batches(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
+ if start
+ begin_at = start
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing `start` value to find_in_batches is deprecated, and will be removed in Rails 5.1.
+ Please pass `begin_at` instead.
+ MSG
+ end
relation = self
- start = options[:start]
- batch_size = options[:batch_size] || 1000
-
unless block_given?
- return to_enum(:find_in_batches, options) do
- total = start ? where(table[primary_key].gteq(start)).size : size
+ return to_enum(:find_in_batches, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
+ total = apply_limits(relation, begin_at, end_at).size
(total - 1).div(batch_size) + 1
end
end
@@ -114,7 +127,8 @@ module ActiveRecord
end
relation = relation.reorder(batch_order).limit(batch_size)
- records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
+ relation = apply_limits(relation, begin_at, end_at)
+ records = relation.to_a
while records.any?
records_size = records.size
@@ -131,6 +145,12 @@ module ActiveRecord
private
+ def apply_limits(relation, begin_at, end_at)
+ relation = relation.where(table[primary_key].gteq(begin_at)) if begin_at
+ relation = relation.where(table[primary_key].lteq(end_at)) if end_at
+ relation
+ end
+
def batch_order
"#{quoted_table_name}.#{quoted_primary_key} ASC"
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 1d4cb1a83b..4a4de86d48 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -166,8 +166,8 @@ module ActiveRecord
relation.select_values = column_names.map { |cn|
columns_hash.key?(cn) ? arel_table[cn] : cn
}
- result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
- result.cast_values(klass.column_types)
+ result = klass.connection.select_all(relation.arel, nil, bound_attributes)
+ result.cast_values(klass.attribute_types)
end
end
@@ -227,14 +227,11 @@ module ActiveRecord
column_alias = column_name
- bind_values = nil
-
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
return 0 if relation.limit_value == 0
query_builder = build_count_subquery(relation, column_name, distinct)
- bind_values = query_builder.bind_values + relation.bind_values
else
column = aggregate_column(column_name)
@@ -245,10 +242,9 @@ module ActiveRecord
relation.select_values = [select_value]
query_builder = relation.arel
- bind_values = query_builder.bind_values + relation.bind_values
end
- result = @klass.connection.select_all(query_builder, nil, bind_values)
+ result = @klass.connection.select_all(query_builder, nil, bound_attributes)
row = result.first
value = row && row.values.first
column = result.column_types.fetch(column_alias) do
@@ -290,7 +286,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
- select_values += select_values unless having_values.empty?
+ select_values += select_values unless having_clause.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
if field.respond_to?(:as)
@@ -304,11 +300,11 @@ module ActiveRecord
relation.group_values = group
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
+ calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
- key_records = association.klass.base_class.find(key_ids)
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
key_records = Hash[key_records.map { |r| [r.id, r] }]
end
@@ -357,9 +353,9 @@ module ActiveRecord
def type_cast_calculated_value(value, type, operation = nil)
case operation
when 'count' then value.to_i
- when 'sum' then type.type_cast_from_database(value || 0)
+ when 'sum' then type.deserialize(value || 0)
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
- else type.type_cast_from_database(value)
+ else type.deserialize(value)
end
end
@@ -378,11 +374,9 @@ module ActiveRecord
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
relation.select_values = [aliased_column]
- arel = relation.arel
- subquery = arel.as(subquery_alias)
+ subquery = relation.arel.as(subquery_alias)
sm = Arel::SelectManager.new relation.engine
- sm.bind_values = arel.bind_values
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
sm.project(select_value).from(subquery)
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 088bc203b7..fb47d915ff 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -307,11 +307,11 @@ module ActiveRecord
relation = relation.where(conditions)
else
unless conditions == :none
- relation = where(primary_key => conditions)
+ relation = relation.where(primary_key => conditions)
end
end
- connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
+ connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
end
# This method is called whenever no records are found with either a single
@@ -365,7 +365,7 @@ module ActiveRecord
[]
else
arel = relation.arel
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
join_dependency.instantiate(rows, aliases)
end
end
@@ -410,7 +410,7 @@ module ActiveRecord
relation = relation.except(:select).select(values).distinct!
arel = relation.arel
- id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
id_rows.map {|row| row[primary_key]}
end
diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb
new file mode 100644
index 0000000000..a93952fa30
--- /dev/null
+++ b/activerecord/lib/active_record/relation/from_clause.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ class Relation
+ class FromClause
+ attr_reader :value, :name
+
+ def initialize(value, name)
+ @value = value
+ @name = name
+ end
+
+ def binds
+ if value.is_a?(Relation)
+ value.bound_attributes
+ else
+ []
+ end
+ end
+
+ def merge(other)
+ self
+ end
+
+ def empty?
+ value.nil?
+ end
+
+ def self.empty
+ new(nil, nil)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index afb0b208c3..65b607ff1c 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -49,9 +49,9 @@ module ActiveRecord
@other = other
end
- NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
- Relation::MULTI_VALUE_METHODS -
- [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
+ NORMAL_VALUES = Relation::VALUE_METHODS -
+ Relation::CLAUSE_METHODS -
+ [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -75,6 +75,7 @@ module ActiveRecord
merge_multi_values
merge_single_values
+ merge_clauses
merge_joins
relation
@@ -107,20 +108,6 @@ module ActiveRecord
end
def merge_multi_values
- lhs_wheres = relation.where_values
- rhs_wheres = other.where_values
-
- lhs_binds = relation.bind_values
- rhs_binds = other.bind_values
-
- removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
-
- where_values = kept + rhs_wheres
- bind_values = filter_binds(lhs_binds, removed) + rhs_binds
-
- relation.where_values = where_values
- relation.bind_values = bind_values
-
if other.reordering_value
# override any order specified in the original relation
relation.reorder! other.order_values
@@ -133,36 +120,18 @@ module ActiveRecord
end
def merge_single_values
- relation.from_value = other.from_value unless relation.from_value
- relation.lock_value = other.lock_value unless relation.lock_value
+ relation.lock_value ||= other.lock_value
unless other.create_with_value.blank?
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
end
end
- def filter_binds(lhs_binds, removed_wheres)
- return lhs_binds if removed_wheres.empty?
-
- set = Set.new removed_wheres.map { |x| x.left.name.to_s }
- lhs_binds.dup.delete_if { |col,_| set.include? col.name }
- end
-
- # Remove equalities from the existing relation with a LHS which is
- # present in the relation being merged in.
- # returns [things_to_remove, things_to_keep]
- def partition_overwrites(lhs_wheres, rhs_wheres)
- if lhs_wheres.empty? || rhs_wheres.empty?
- return [[], lhs_wheres]
- end
-
- nodes = rhs_wheres.find_all do |w|
- w.respond_to?(:operator) && w.operator == :==
- end
- seen = Set.new(nodes) { |node| node.left }
-
- lhs_wheres.partition do |w|
- w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
+ def merge_clauses
+ CLAUSE_METHODS.each do |name|
+ clause = relation.send("#{name}_clause")
+ other_clause = other.send("#{name}_clause")
+ relation.send("#{name}_clause=", clause.merge(other_clause))
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 2860a30f99..43e9afe853 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -29,24 +29,8 @@ module ActiveRecord
end
def create_binds(attributes)
- result = attributes.dup
- binds = []
-
- attributes.each do |column_name, value|
- case value
- when String, Integer, ActiveRecord::StatementCache::Substitute
- result[column_name] = Arel::Nodes::BindParam.new
- binds.push([table.column(column_name), value])
- when Hash
- attrs, bvs = associated_predicate_builder(column_name).create_binds(value)
- result[column_name] = attrs
- binds += bvs
- when Relation
- binds += value.arel.bind_values + value.bind_values
- end
- end
-
- [result, binds]
+ attributes = convert_dot_notation_to_hash(attributes.stringify_keys)
+ create_binds_for_hash(attributes)
end
def expand(column, value)
@@ -108,6 +92,30 @@ module ActiveRecord
end
end
+
+ def create_binds_for_hash(attributes)
+ result = attributes.dup
+ binds = []
+
+ attributes.each do |column_name, value|
+ case value
+ when Hash
+ attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
+ result[column_name] = attrs
+ binds += bvs
+ when Relation
+ binds += value.bound_attributes
+ else
+ if can_be_bound?(column_name, value)
+ result[column_name] = Arel::Nodes::BindParam.new
+ binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+ end
+ end
+ end
+
+ [result, binds]
+ end
+
private
def associated_predicate_builder(association_name)
@@ -131,5 +139,11 @@ module ActiveRecord
def handler_for(object)
@handlers.detect { |klass, _| klass === object }.last
end
+
+ def can_be_bound?(column_name, value)
+ !value.nil? &&
+ handler_for(value).is_a?(BasicObjectHandler) &&
+ !table.associated_with?(column_name)
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
index aabcf20c1d..159889d3b8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -31,7 +31,14 @@ module ActiveRecord
end
def ids
- value
+ case value
+ when Relation
+ value.select(primary_key)
+ when Array
+ value.map { |v| convert_to_id(v) }
+ else
+ convert_to_id(value)
+ end
end
def base_class
@@ -42,6 +49,10 @@ module ActiveRecord
private
+ def primary_key
+ associated_table.association_primary_key(base_class)
+ end
+
def polymorphic_base_class_from_value
case value
when Relation
@@ -53,6 +64,15 @@ module ActiveRecord
value.class.base_class
end
end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
new file mode 100644
index 0000000000..e69319b4de
--- /dev/null
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -0,0 +1,19 @@
+require 'active_record/attribute'
+
+module ActiveRecord
+ class Relation
+ class QueryAttribute < Attribute
+ def type_cast(value)
+ value
+ end
+
+ def value_for_database
+ @value_for_database ||= super
+ end
+
+ def with_cast_value(value)
+ QueryAttribute.new(name, value, type)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index c34e4bfb9b..7514401072 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,6 +1,9 @@
-require 'active_support/core_ext/array/wrap'
-require 'active_support/core_ext/string/filters'
+require "active_record/relation/from_clause"
+require "active_record/relation/query_attribute"
+require "active_record/relation/where_clause"
+require "active_record/relation/where_clause_factory"
require 'active_model/forbidden_attributes_protection'
+require 'active_support/core_ext/string/filters'
module ActiveRecord
module QueryMethods
@@ -39,23 +42,10 @@ module ActiveRecord
# User.where.not(name: "Jon", role: "admin")
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
def not(opts, *rest)
- where_value = @scope.send(:build_where, opts, rest).map do |rel|
- case rel
- when NilClass
- raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
- when Arel::Nodes::In
- Arel::Nodes::NotIn.new(rel.left, rel.right)
- when Arel::Nodes::Equality
- Arel::Nodes::NotEqual.new(rel.left, rel.right)
- when String
- Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
- else
- Arel::Nodes::Not.new(rel)
- end
- end
+ where_clause = @scope.send(:where_clause_factory).build(opts, rest)
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
- @scope.where_values += where_value
+ @scope.where_clause += where_clause.invert
@scope
end
end
@@ -90,6 +80,23 @@ module ActiveRecord
CODE
end
+ Relation::CLAUSE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_clause # def where_clause
+ @values[:#{name}] || new_#{name}_clause # @values[:where] || new_where_clause
+ end # end
+ #
+ def #{name}_clause=(value) # def where_clause=(value)
+ assert_mutability! # assert_mutability!
+ @values[:#{name}] = value # @values[:where] = value
+ end # end
+ CODE
+ end
+
+ def bound_attributes
+ from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
+ end
+
def create_with_value # :nodoc:
@values[:create_with] || {}
end
@@ -392,9 +399,8 @@ module ActiveRecord
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
end
- Array(target_value).each do |val|
- where_unscoping(val)
- end
+ target_values = Array(target_value).map(&:to_s)
+ self.where_clause = where_clause.except(*target_values)
end
else
raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
@@ -425,15 +431,6 @@ module ActiveRecord
self
end
- def bind(value) # :nodoc:
- spawn.bind!(value)
- end
-
- def bind!(value) # :nodoc:
- self.bind_values += [value]
- self
- end
-
# Returns a new relation, which is the result of filtering the current relation
# according to the conditions in the arguments.
#
@@ -569,7 +566,7 @@ module ActiveRecord
references!(PredicateBuilder.references(opts))
end
- self.where_values += build_where(opts, rest)
+ self.where_clause += where_clause_factory.build(opts, rest)
self
end
@@ -585,6 +582,37 @@ module ActiveRecord
unscope(where: conditions.keys).where(conditions)
end
+ # Returns a new relation, which is the logical union of this relation and the one passed as an
+ # argument.
+ #
+ # The two relations must be structurally compatible: they must be scoping the same model, and
+ # they must differ only by +where+ (if no +group+ has been defined) or +having+ (if a +group+ is
+ # present). Neither relation may have a +limit+, +offset+, or +uniq+ set.
+ #
+ # Post.where("id = 1").or(Post.where("id = 2"))
+ # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
+ #
+ def or(other)
+ spawn.or!(other)
+ end
+
+ def or!(other) # :nodoc:
+ unless structurally_compatible_for_or?(other)
+ raise ArgumentError, 'Relation passed to #or must be structurally compatible'
+ end
+
+ self.where_clause = self.where_clause.or(other.where_clause)
+ self.having_clause = self.having_clause.or(other.having_clause)
+
+ self
+ end
+
+ private def structurally_compatible_for_or?(other) # :nodoc:
+ Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } &&
+ (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } &&
+ (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") != other.send("#{m}_clause") }
+ end
+
# Allows to specify a HAVING clause. Note that you can't use HAVING
# without also specifying a GROUP clause.
#
@@ -596,7 +624,7 @@ module ActiveRecord
def having!(opts, *rest) # :nodoc:
references!(PredicateBuilder.references(opts)) if Hash === opts
- self.having_values += build_where(opts, rest)
+ self.having_clause += having_clause_factory.build(opts, rest)
self
end
@@ -744,10 +772,7 @@ module ActiveRecord
end
def from!(value, subquery_name = nil) # :nodoc:
- self.from_value = [value, subquery_name]
- if value.is_a? Relation
- self.bind_values = value.arel.bind_values + value.bind_values + bind_values
- end
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
self
end
@@ -858,21 +883,18 @@ module ActiveRecord
build_joins(arel, joins_values.flatten) unless joins_values.empty?
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
-
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
-
+ arel.where(where_clause.ast) unless where_clause.empty?
+ arel.having(having_clause.ast) unless having_clause.empty?
arel.take(connection.sanitize_limit(limit_value)) if limit_value
arel.skip(offset_value.to_i) if offset_value
-
- arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
+ arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
build_order(arel)
- build_select(arel, select_values.uniq)
+ build_select(arel)
arel.distinct(distinct_value)
- arel.from(build_from) if from_value
+ arel.from(build_from) unless from_clause.empty?
arel.lock(lock_value) if lock_value
arel
@@ -883,79 +905,24 @@ module ActiveRecord
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
end
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
+ clause_method = Relation::CLAUSE_METHODS.include?(scope)
+ multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope)
+ if clause_method
+ unscope_code = "#{scope}_clause="
+ else
+ unscope_code = "#{scope}_value#{'s' if multi_val_method}="
+ end
case scope
when :order
result = []
- when :where
- self.bind_values = []
else
- result = [] unless single_val_method
+ result = [] if multi_val_method
end
self.send(unscope_code, result)
end
- def where_unscoping(target_value)
- target_value = target_value.to_s
-
- where_values.reject! do |rel|
- case rel
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
- subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
- subrelation.name.to_s == target_value
- end
- end
-
- self.bind_values = bind_values.reject { |col,_| col.name == target_value }
- end
-
- def custom_join_ast(table, joins)
- joins = joins.reject(&:blank?)
-
- return [] if joins.empty?
-
- joins.map! do |join|
- case join
- when Array
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
- when String
- join = Arel.sql(join)
- end
- table.create_string_join(join)
- end
- end
-
- def collapse_wheres(arel, wheres)
- predicates = wheres.map do |where|
- next where if ::Arel::Nodes::Equality === where
- where = Arel.sql(where) if String === where
- Arel::Nodes::Grouping.new(where)
- end
-
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
- end
-
- def build_where(opts, other = [])
- case opts
- when String, Array
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
- when Hash
- opts = predicate_builder.resolve_column_aliases(opts)
-
- tmp_opts, bind_values = predicate_builder.create_binds(opts)
- self.bind_values += bind_values
-
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
-
- predicate_builder.build_from_hash(attributes)
- else
- [opts]
- end
- end
-
def association_for_table(table_name)
table_name = table_name.to_s
@klass._reflect_on_association(table_name) ||
@@ -963,7 +930,8 @@ module ActiveRecord
end
def build_from
- opts, name = from_value
+ opts = from_clause.value
+ name = from_clause.name
case opts
when Relation
name ||= 'subquery'
@@ -988,13 +956,14 @@ module ActiveRecord
raise 'unknown class: %s' % join.class.name
end
end
+ buckets.default = []
- association_joins = buckets[:association_join] || []
- stashed_association_joins = buckets[:stashed_join] || []
- join_nodes = (buckets[:join_node] || []).uniq
- string_joins = (buckets[:string_join] || []).map(&:strip).uniq
+ association_joins = buckets[:association_join]
+ stashed_association_joins = buckets[:stashed_join]
+ join_nodes = buckets[:join_node].uniq
+ string_joins = buckets[:string_join].map(&:strip).uniq
- join_list = join_nodes + custom_join_ast(manager, string_joins)
+ join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@klass,
@@ -1014,22 +983,31 @@ module ActiveRecord
manager
end
- def build_select(arel, selects)
- if !selects.empty?
- expanded_select = selects.map do |field|
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
- arel_table[field]
- else
- field
- end
- end
+ def convert_join_strings_to_ast(table, joins)
+ joins
+ .flatten
+ .reject(&:blank?)
+ .map { |join| table.create_string_join(Arel.sql(join)) }
+ end
- arel.project(*expanded_select)
+ def build_select(arel)
+ if select_values.any?
+ arel.project(*arel_columns(select_values.uniq))
else
arel.project(@klass.arel_table[Arel.star])
end
end
+ def arel_columns(columns)
+ columns.map do |field|
+ if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
+ arel_table[field]
+ else
+ field
+ end
+ end
+ end
+
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
@@ -1048,10 +1026,6 @@ module ActiveRecord
end
end
- def array_of_strings?(o)
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
- end
-
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)
@@ -1118,5 +1092,19 @@ module ActiveRecord
raise ArgumentError, "The method .#{method_name}() must contain arguments."
end
end
+
+ def new_where_clause
+ Relation::WhereClause.empty
+ end
+ alias new_having_clause new_where_clause
+
+ def where_clause_factory
+ @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
+ end
+ alias having_clause_factory where_clause_factory
+
+ def new_from_clause
+ Relation::FromClause.empty
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 01bddea6c9..70da37fa84 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -32,7 +32,7 @@ module ActiveRecord
elsif other
spawn.merge!(other)
else
- self
+ raise ArgumentError, "invalid argument: #{other.inspect}."
end
end
@@ -58,9 +58,6 @@ module ActiveRecord
# Post.order('id asc').only(:where) # discards the order condition
# Post.order('id asc').only(:where, :order) # uses the specified order
def only(*onlies)
- if onlies.any? { |o| o == :where }
- onlies << :bind
- end
relation_with values.slice(*onlies)
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
new file mode 100644
index 0000000000..f9b9e640ec
--- /dev/null
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -0,0 +1,173 @@
+module ActiveRecord
+ class Relation
+ class WhereClause # :nodoc:
+ attr_reader :binds
+
+ delegate :any?, :empty?, to: :predicates
+
+ def initialize(predicates, binds)
+ @predicates = predicates
+ @binds = binds
+ end
+
+ def +(other)
+ WhereClause.new(
+ predicates + other.predicates,
+ binds + other.binds,
+ )
+ end
+
+ def merge(other)
+ WhereClause.new(
+ predicates_unreferenced_by(other) + other.predicates,
+ non_conflicting_binds(other) + other.binds,
+ )
+ end
+
+ def except(*columns)
+ WhereClause.new(
+ predicates_except(columns),
+ binds_except(columns),
+ )
+ end
+
+ def or(other)
+ if empty?
+ self
+ elsif other.empty?
+ other
+ else
+ WhereClause.new(
+ [ast.or(other.ast)],
+ binds + other.binds
+ )
+ end
+ end
+
+ def to_h(table_name = nil)
+ equalities = predicates.grep(Arel::Nodes::Equality)
+ if table_name
+ equalities = equalities.select do |node|
+ node.left.relation.name == table_name
+ end
+ end
+
+ binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
+
+ equalities.map { |node|
+ name = node.left.name
+ [name, binds.fetch(name.to_s) {
+ case node.right
+ when Array then node.right.map(&:val)
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
+ node.right.val
+ end
+ }]
+ }.to_h
+ end
+
+ def ast
+ Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
+ end
+
+ def ==(other)
+ other.is_a?(WhereClause) &&
+ predicates == other.predicates &&
+ binds == other.binds
+ end
+
+ def invert
+ WhereClause.new(inverted_predicates, binds)
+ end
+
+ def self.empty
+ new([], [])
+ end
+
+ protected
+
+ attr_reader :predicates
+
+ def referenced_columns
+ @referenced_columns ||= begin
+ equality_nodes = predicates.select { |n| equality_node?(n) }
+ Set.new(equality_nodes, &:left)
+ end
+ end
+
+ private
+
+ def predicates_unreferenced_by(other)
+ predicates.reject do |n|
+ equality_node?(n) && other.referenced_columns.include?(n.left)
+ end
+ end
+
+ def equality_node?(node)
+ node.respond_to?(:operator) && node.operator == :==
+ end
+
+ def non_conflicting_binds(other)
+ conflicts = referenced_columns & other.referenced_columns
+ conflicts.map! { |node| node.name.to_s }
+ binds.reject { |attr| conflicts.include?(attr.name) }
+ end
+
+ def inverted_predicates
+ predicates.map { |node| invert_predicate(node) }
+ end
+
+ def invert_predicate(node)
+ case node
+ when NilClass
+ raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
+ when Arel::Nodes::In
+ Arel::Nodes::NotIn.new(node.left, node.right)
+ when Arel::Nodes::Equality
+ Arel::Nodes::NotEqual.new(node.left, node.right)
+ when String
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
+ else
+ Arel::Nodes::Not.new(node)
+ end
+ end
+
+ def predicates_except(columns)
+ predicates.reject do |node|
+ case node
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
+ columns.include?(subrelation.name.to_s)
+ end
+ end
+ end
+
+ def binds_except(columns)
+ binds.reject do |attr|
+ columns.include?(attr.name)
+ end
+ end
+
+ def predicates_with_wrapped_sql_literals
+ non_empty_predicates.map do |node|
+ if Arel::Nodes::Equality === node
+ node
+ else
+ wrap_sql_literal(node)
+ end
+ end
+ end
+
+ def non_empty_predicates
+ predicates - ['']
+ end
+
+ def wrap_sql_literal(node)
+ if ::String === node
+ node = Arel.sql(node)
+ end
+ Arel::Nodes::Grouping.new(node)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
new file mode 100644
index 0000000000..0430922be3
--- /dev/null
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ class Relation
+ class WhereClauseFactory
+ def initialize(klass, predicate_builder)
+ @klass = klass
+ @predicate_builder = predicate_builder
+ end
+
+ def build(opts, other)
+ binds = []
+
+ case opts
+ when String, Array
+ parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
+ when Hash
+ attributes = predicate_builder.resolve_column_aliases(opts)
+ attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
+
+ attributes, binds = predicate_builder.create_binds(attributes)
+
+ parts = predicate_builder.build_from_hash(attributes)
+ else
+ parts = [opts]
+ end
+
+ WhereClause.new(parts, binds)
+ end
+
+ protected
+
+ attr_reader :klass, :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 3a3e65ef32..500c478e65 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -81,7 +81,7 @@ module ActiveRecord
def cast_values(type_overrides = {}) # :nodoc:
types = columns.map { |name| column_type(name, type_overrides) }
result = rows.map do |values|
- types.zip(values).map { |type, value| type.type_cast_from_database(value) }
+ types.zip(values).map { |type, value| type.deserialize(value) }
end
columns.one? ? result.map!(&:first) : result
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 313e767dcb..c7f55ebaa1 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -75,7 +75,7 @@ module ActiveRecord
def sanitize_sql_hash_for_assignment(attrs, table)
c = connection
attrs.map do |attr, value|
- value = type_for_attribute(attr.to_s).type_cast_for_database(value)
+ value = type_for_attribute(attr.to_s).serialize(value)
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
end.join(', ')
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 3e43591672..fca4f1c9d3 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -11,11 +11,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- ScopeRegistry.value_for(:current_scope, base_class.to_s)
+ ScopeRegistry.value_for(:current_scope, self.to_s)
end
def current_scope=(scope) #:nodoc:
- ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
+ ScopeRegistry.set_value_for(:current_scope, self.to_s, scope)
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 35420e6551..43c7b1c574 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -30,7 +30,13 @@ module ActiveRecord
end
def default_scoped # :nodoc:
- relation.merge(build_default_scope)
+ scope = build_default_scope
+
+ if scope
+ relation.spawn.merge!(scope)
+ else
+ relation
+ end
end
# Collects attributes from scopes that should be applied when creating
@@ -151,11 +157,20 @@ module ActiveRecord
extension = Module.new(&block) if block
- singleton_class.send(:define_method, name) do |*args|
- scope = all.scoping { body.call(*args) }
- scope = scope.extending(extension) if extension
+ if body.respond_to?(:to_proc)
+ singleton_class.send(:define_method, name) do |*args|
+ scope = all.scoping { instance_exec(*args, &body) }
+ scope = scope.extending(extension) if extension
+
+ scope || all
+ end
+ else
+ singleton_class.send(:define_method, name) do |*args|
+ scope = all.scoping { body.call(*args) }
+ scope = scope.extending(extension) if extension
- scope || all
+ scope || all
+ end
end
end
end
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index 07031b6371..0990f815a7 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# Load securerandom only when has_secure_token is used.
require 'active_support/core_ext/securerandom'
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
- before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) }
+ before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")}
end
def generate_unique_secure_token
@@ -36,4 +36,3 @@ module ActiveRecord
end
end
end
-
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index c2484d02ed..89b7e0be82 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -180,9 +180,9 @@ module ActiveRecord #:nodoc:
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
- column = klass.columns_hash[name] || Type::Value.new
+ cast_type = klass.type_for_attribute(name)
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type
{ :text => :string,
:time => :datetime }[type] || type
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index 3047a81ec4..95986c820c 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -48,7 +48,7 @@ module ActiveRecord
def sql_for(binds, connection)
val = @values.dup
binds = connection.prepare_binds_for_database(binds)
- @indexes.each { |i| val[i] = connection.quote(binds.shift.last) }
+ @indexes.each { |i| val[i] = connection.quote(binds.shift) }
val.join
end
end
@@ -67,21 +67,21 @@ module ActiveRecord
end
class BindMap # :nodoc:
- def initialize(bind_values)
+ def initialize(bound_attributes)
@indexes = []
- @bind_values = bind_values
+ @bound_attributes = bound_attributes
- bind_values.each_with_index do |(_, value), i|
- if Substitute === value
+ bound_attributes.each_with_index do |attr, i|
+ if Substitute === attr.value
@indexes << i
end
end
end
def bind(values)
- bvs = @bind_values.map(&:dup)
- @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
- bvs
+ bas = @bound_attributes.dup
+ @indexes.each_with_index { |offset,i| bas[offset] = bas[offset].with_cast_value(values[i]) }
+ bas
end
end
@@ -89,7 +89,7 @@ module ActiveRecord
def self.create(connection, block = Proc.new)
relation = block.call Params.new
- bind_map = BindMap.new relation.bind_values
+ bind_map = BindMap.new relation.bound_attributes
query_builder = connection.cacheable_query relation.arel
new query_builder, bind_map
end
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
new file mode 100644
index 0000000000..b0b86865fd
--- /dev/null
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -0,0 +1,55 @@
+module ActiveRecord
+ # ActiveRecord::Suppressor prevents the receiver from being saved during
+ # a given block.
+ #
+ # For example, here's a pattern of creating notifications when new comments
+ # are posted. (The notification may in turn trigger an email, a push
+ # notification, or just appear in the UI somewhere):
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commentable, polymorphic: true
+ # after_create -> { Notification.create! comment: self,
+ # recipients: commentable.recipients }
+ # end
+ #
+ # That's what you want the bulk of the time. New comment creates a new
+ # Notification. But there may well be off cases, like copying a commentable
+ # and its comments, where you don't want that. So you'd have a concern
+ # something like this:
+ #
+ # module Copyable
+ # def copy_to(destination)
+ # Notification.suppress do
+ # # Copy logic that creates new comments that we do not want
+ # # triggering notifications.
+ # end
+ # end
+ # end
+ module Suppressor
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def suppress(&block)
+ SuppressorRegistry.suppressed[name] = true
+ yield
+ ensure
+ SuppressorRegistry.suppressed[name] = false
+ end
+ end
+
+ # Ignore saving events if we're in suppression mode.
+ def save!(*args) # :nodoc:
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
+ end
+ end
+
+ class SuppressorRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_reader :suppressed
+
+ def initialize
+ @suppressed = {}
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index 31a40adb67..3dd6321a97 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -1,6 +1,7 @@
module ActiveRecord
class TableMetadata # :nodoc:
delegate :foreign_type, :foreign_key, to: :association, prefix: true
+ delegate :association_primary_key, to: :association
def initialize(klass, arel_table, association = nil)
@klass = klass
@@ -22,9 +23,11 @@ module ActiveRecord
arel_table[column_name]
end
- def column(column_name)
+ def type(column_name)
if klass
- klass.columns_hash[column_name.to_s]
+ klass.type_for_attribute(column_name.to_s)
+ else
+ Type::Value.new
end
end
@@ -40,7 +43,7 @@ module ActiveRecord
association_klass = association.klass
arel_table = association_klass.arel_table
else
- type_caster = TypeCaster::Connection.new(klass.connection, table_name)
+ type_caster = TypeCaster::Connection.new(klass, table_name)
association_klass = nil
arel_table = Arel::Table.new(table_name, type_caster: type_caster)
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 0fd2862b2c..dd405c7796 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -318,9 +318,12 @@ module ActiveRecord
# Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks
# can be called.
def add_to_transaction
- if self.class.connection.add_transaction_record(self)
- remember_transaction_record_state
+ if has_transactional_callbacks?
+ self.class.connection.add_transaction_record(self)
+ else
+ set_transaction_state(self.class.connection.transaction_state)
end
+ remember_transaction_record_state
end
# Executes +method+ within a transaction and captures its return value as a
@@ -378,7 +381,10 @@ module ActiveRecord
thaw unless restore_state[:frozen?]
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
- write_attribute(self.class.primary_key, restore_state[:id]) if self.class.primary_key
+ pk = self.class.primary_key
+ if pk && read_attribute(pk) != restore_state[:id]
+ write_attribute(pk, restore_state[:id])
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 250e8d5b23..2c0cda69d0 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,7 +1,4 @@
-require 'active_record/type/decorator'
-require 'active_record/type/mutable'
-require 'active_record/type/numeric'
-require 'active_record/type/time_value'
+require 'active_record/type/helpers'
require 'active_record/type/value'
require 'active_record/type/big_integer'
@@ -19,5 +16,51 @@ require 'active_record/type/text'
require 'active_record/type/time'
require 'active_record/type/unsigned_integer'
+require 'active_record/type/adapter_specific_registry'
require 'active_record/type/type_map'
require 'active_record/type/hash_lookup_type_map'
+
+module ActiveRecord
+ module Type
+ @registry = AdapterSpecificRegistry.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 ActiveRecord::Attributes::ClassMethods#attribute. If your
+ # type is only meant to be used with a specific database adapter, you can
+ # do so by passing +adapter: :postgresql+. If your type has the same
+ # name as a native type for the current adapter, an exception will be
+ # raised unless you specify an +:override+ option. +override: true+ will
+ # cause your type to be used instead of the native type. +override:
+ # false+ will cause the native type to be used over yours if one exists.
+ def register(type_name, klass = nil, **options, &block)
+ registry.register(type_name, klass, **options, &block)
+ end
+
+ def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc:
+ registry.lookup(*args, adapter: adapter, **kwargs)
+ end
+
+ private
+
+ def current_adapter_name
+ ActiveRecord::Base.connection.adapter_name.downcase.to_sym
+ end
+ end
+
+ register(:big_integer, Type::BigInteger, override: false)
+ register(:binary, Type::Binary, override: false)
+ register(:boolean, Type::Boolean, override: false)
+ register(:date, Type::Date, override: false)
+ register(:date_time, Type::DateTime, override: false)
+ register(:decimal, Type::Decimal, override: false)
+ register(:float, Type::Float, override: false)
+ register(:integer, Type::Integer, override: false)
+ register(:string, Type::String, override: false)
+ register(:text, Type::Text, override: false)
+ register(:time, Type::Time, override: false)
+ end
+end
diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb
new file mode 100644
index 0000000000..5f71b3cb94
--- /dev/null
+++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb
@@ -0,0 +1,142 @@
+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)
+ end
+
+ def lookup(symbol, *args)
+ registration = registrations
+ .select { |r| r.matches?(symbol, *args) }
+ .max
+
+ if registration
+ registration.call(self, symbol, *args)
+ else
+ raise ArgumentError, "Unknown type #{symbol.inspect}"
+ end
+ end
+
+ def add_modifier(options, klass, **args)
+ registrations << DecorationRegistration.new(options, klass, **args)
+ end
+
+ protected
+
+ attr_reader :registrations
+ end
+
+ class Registration
+ def initialize(name, block, adapter: nil, override: nil)
+ @name = name
+ @block = block
+ @adapter = adapter
+ @override = override
+ end
+
+ def call(_registry, *args, adapter: nil, **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 && matches_adapter?(**kwargs)
+ end
+
+ def <=>(other)
+ if conflicts_with?(other)
+ raise TypeConflictError.new("Type #{name} was registered for all
+ adapters, but shadows a native type with
+ the same name for #{other.adapter}".squish)
+ end
+ priority <=> other.priority
+ end
+
+ protected
+
+ attr_reader :name, :block, :adapter, :override
+
+ def priority
+ result = 0
+ if adapter
+ result |= 1
+ end
+ if override
+ result |= 2
+ end
+ result
+ end
+
+ def priority_except_adapter
+ priority & 0b111111100
+ end
+
+ private
+
+ def matches_adapter?(adapter: nil, **)
+ (self.adapter.nil? || adapter == self.adapter)
+ end
+
+ def conflicts_with?(other)
+ same_priority_except_adapter?(other) &&
+ has_adapter_conflict?(other)
+ end
+
+ def same_priority_except_adapter?(other)
+ priority_except_adapter == other.priority_except_adapter
+ end
+
+ def has_adapter_conflict?(other)
+ (override.nil? && other.adapter) ||
+ (adapter && other.override.nil?)
+ end
+ end
+
+ class DecorationRegistration < Registration
+ def initialize(options, klass, adapter: nil)
+ @options = options
+ @klass = klass
+ @adapter = adapter
+ end
+
+ def call(registry, *args, **kwargs)
+ subtype = registry.lookup(*args, **kwargs.except(*options.keys))
+ klass.new(subtype)
+ end
+
+ def matches?(*args, **kwargs)
+ matches_adapter?(**kwargs) && matches_options?(**kwargs)
+ end
+
+ def priority
+ super | 4
+ end
+
+ protected
+
+ attr_reader :options, :klass
+
+ private
+
+ def matches_options?(**kwargs)
+ options.all? do |key, value|
+ kwargs[key] == value
+ end
+ end
+ end
+ end
+
+ class TypeConflictError < StandardError
+ end
+
+ # :startdoc:
+end
diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb
index 005a48ef0d..0baf8c63ad 100644
--- a/activerecord/lib/active_record/type/binary.rb
+++ b/activerecord/lib/active_record/type/binary.rb
@@ -9,7 +9,7 @@ module ActiveRecord
true
end
- def type_cast(value)
+ def cast(value)
if value.is_a?(Data)
value.to_s
else
@@ -17,13 +17,13 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
return if value.nil?
Data.new(super)
end
def changed_in_place?(raw_old_value, value)
- old_value = type_cast_from_database(raw_old_value)
+ old_value = deserialize(raw_old_value)
old_value != value
end
diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb
index d90a6069b7..3ceab59ebb 100644
--- a/activerecord/lib/active_record/type/date.rb
+++ b/activerecord/lib/active_record/type/date.rb
@@ -1,14 +1,12 @@
module ActiveRecord
module Type
class Date < Value # :nodoc:
+ include Helpers::AcceptsMultiparameterTime.new
+
def type
:date
end
- def klass
- ::Date
- end
-
def type_cast_for_schema(value)
"'#{value.to_s(:db)}'"
end
@@ -41,6 +39,11 @@ module ActiveRecord
::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/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb
index 0a737815bc..a5199959b9 100644
--- a/activerecord/lib/active_record/type/date_time.rb
+++ b/activerecord/lib/active_record/type/date_time.rb
@@ -1,26 +1,15 @@
module ActiveRecord
module Type
class DateTime < Value # :nodoc:
- include TimeValue
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 4 => 0, 5 => 0 }
+ )
def type
:datetime
end
- def type_cast_for_database(value)
- zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
-
- if value.acts_like?(:time)
- if value.respond_to?(zone_conversion_method)
- value.send(zone_conversion_method)
- else
- value
- end
- else
- super
- end
- end
-
private
def cast_value(string)
@@ -42,6 +31,14 @@ module ActiveRecord
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/activerecord/lib/active_record/type/decimal.rb
index 7b2bee2c42..867b5f75c7 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Type
class Decimal < Value # :nodoc:
- include Numeric
+ include Helpers::Numeric
def type
:decimal
diff --git a/activerecord/lib/active_record/type/decorator.rb b/activerecord/lib/active_record/type/decorator.rb
deleted file mode 100644
index 9fce38ea44..0000000000
--- a/activerecord/lib/active_record/type/decorator.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-module ActiveRecord
- module Type
- module Decorator # :nodoc:
- def init_with(coder)
- @subtype = coder['subtype']
- __setobj__(@subtype)
- end
-
- def encode_with(coder)
- coder['subtype'] = __getobj__
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/float.rb b/activerecord/lib/active_record/type/float.rb
index 42eb44b9a9..d88482b85d 100644
--- a/activerecord/lib/active_record/type/float.rb
+++ b/activerecord/lib/active_record/type/float.rb
@@ -1,18 +1,24 @@
module ActiveRecord
module Type
class Float < Value # :nodoc:
- include Numeric
+ include Helpers::Numeric
def type
:float
end
- alias type_cast_for_database type_cast
+ alias serialize cast
private
def cast_value(value)
- value.to_f
+ case value
+ when ::Float then value
+ when "Infinity" then ::Float::INFINITY
+ when "-Infinity" then -::Float::INFINITY
+ when "NaN" then ::Float::NAN
+ else value.to_f
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/helpers.rb b/activerecord/lib/active_record/type/helpers.rb
new file mode 100644
index 0000000000..634d417d13
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers.rb
@@ -0,0 +1,4 @@
+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/helpers/accepts_multiparameter_time.rb b/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb
new file mode 100644
index 0000000000..be571fc1c7
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb
@@ -0,0 +1,30 @@
+module ActiveRecord
+ module Type
+ module Helpers
+ class AcceptsMultiparameterTime < Module # :nodoc:
+ def initialize(defaults: {})
+ define_method(:cast) 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
+ )
+ end
+ private :value_from_multiparameter_assignment
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/helpers/mutable.rb b/activerecord/lib/active_record/type/helpers/mutable.rb
new file mode 100644
index 0000000000..88a9099277
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/mutable.rb
@@ -0,0 +1,18 @@
+module ActiveRecord
+ module Type
+ module Helpers
+ module Mutable # :nodoc:
+ def cast(value)
+ deserialize(serialize(value))
+ end
+
+ # +raw_old_value+ will be the `_before_type_cast` version of the
+ # value (likely a string). +new_value+ will be the current, type
+ # cast value.
+ def changed_in_place?(raw_old_value, new_value)
+ raw_old_value != serialize(new_value)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/helpers/numeric.rb b/activerecord/lib/active_record/type/helpers/numeric.rb
new file mode 100644
index 0000000000..a755a02a59
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/numeric.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module Type
+ module Helpers
+ module Numeric # :nodoc:
+ def cast(value)
+ value = case value
+ when true then 1
+ when false then 0
+ when ::String then value.presence
+ else value
+ end
+ super(value)
+ end
+
+ def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
+ super || number_to_non_number?(old_value, new_value_before_type_cast)
+ end
+
+ private
+
+ def number_to_non_number?(old_value, new_value_before_type_cast)
+ old_value != nil && non_numeric_string?(new_value_before_type_cast)
+ end
+
+ def non_numeric_string?(value)
+ # 'wibble'.to_i will give zero, we want to make sure
+ # that we aren't marking int zero to string zero as
+ # changed.
+ value.to_s !~ /\A-?\d+\.?\d*\z/
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/helpers/time_value.rb b/activerecord/lib/active_record/type/helpers/time_value.rb
new file mode 100644
index 0000000000..7eb41557cb
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/time_value.rb
@@ -0,0 +1,58 @@
+module ActiveRecord
+ 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
+
+ if value.acts_like?(:time)
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
+
+ if value.respond_to?(zone_conversion_method)
+ value = value.send(zone_conversion_method)
+ end
+ end
+
+ value
+ end
+
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ end
+
+ def user_input_in_time_zone(value)
+ value.in_time_zone
+ end
+
+ private
+
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
+ # Treat 0000-00-00 00:00:00 as nil.
+ return if year.nil? || (year == 0 && mon == 0 && mday == 0)
+
+ if offset
+ time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
+ return unless time
+
+ time -= offset
+ Base.default_timezone == :utc ? time : time.getlocal
+ else
+ ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ end
+ end
+
+ # Doesn't handle time zones.
+ def fast_string_to_time(string)
+ if string =~ ConnectionAdapters::Column::Format::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
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb
index 394fe008f9..2a1b04ac7f 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activerecord/lib/active_record/type/integer.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Type
class Integer < Value # :nodoc:
- include Numeric
+ include Helpers::Numeric
# Column storage size in bytes.
# 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc.
@@ -16,13 +16,19 @@ module ActiveRecord
:integer
end
- alias type_cast_for_database type_cast
-
- def type_cast_from_database(value)
+ def deserialize(value)
return if value.nil?
value.to_i
end
+ def serialize(value)
+ result = cast(value)
+ if result
+ ensure_in_range(result)
+ end
+ result
+ end
+
protected
attr_reader :range
@@ -34,9 +40,7 @@ module ActiveRecord
when true then 1
when false then 0
else
- result = value.to_i rescue nil
- ensure_in_range(result) if result
- result
+ value.to_i rescue nil
end
end
diff --git a/activerecord/lib/active_record/type/mutable.rb b/activerecord/lib/active_record/type/mutable.rb
deleted file mode 100644
index 066617ea59..0000000000
--- a/activerecord/lib/active_record/type/mutable.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module ActiveRecord
- module Type
- module Mutable # :nodoc:
- def type_cast_from_user(value)
- type_cast_from_database(type_cast_for_database(value))
- end
-
- # +raw_old_value+ will be the `_before_type_cast` version of the
- # value (likely a string). +new_value+ will be the current, type
- # cast value.
- def changed_in_place?(raw_old_value, new_value)
- raw_old_value != type_cast_for_database(new_value)
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
deleted file mode 100644
index 674f996f38..0000000000
--- a/activerecord/lib/active_record/type/numeric.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveRecord
- module Type
- module Numeric # :nodoc:
- def number?
- true
- end
-
- def type_cast(value)
- value = case value
- when true then 1
- when false then 0
- when ::String then value.presence
- else value
- end
- super(value)
- end
-
- def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
- super || number_to_non_number?(old_value, new_value_before_type_cast)
- end
-
- private
-
- def number_to_non_number?(old_value, new_value_before_type_cast)
- old_value != nil && non_numeric_string?(new_value_before_type_cast)
- end
-
- def non_numeric_string?(value)
- # 'wibble'.to_i will give zero, we want to make sure
- # that we aren't marking int zero to string zero as
- # changed.
- value.to_s !~ /\A-?\d+\.?\d*\z/
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 3cac03464e..732029c723 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,8 +1,7 @@
module ActiveRecord
module Type
class Serialized < DelegateClass(Type::Value) # :nodoc:
- include Mutable
- include Decorator
+ include Helpers::Mutable
attr_reader :subtype, :coder
@@ -12,7 +11,7 @@ module ActiveRecord
super(subtype)
end
- def type_cast_from_database(value)
+ def deserialize(value)
if default_value?(value)
value
else
@@ -20,7 +19,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
return if value.nil?
unless default_value?(value)
super coder.dump(value)
@@ -29,23 +28,13 @@ module ActiveRecord
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- subtype.changed_in_place?(raw_old_value, type_cast_for_database(value))
+ subtype.changed_in_place?(raw_old_value, serialize(value))
end
def accessor
ActiveRecord::Store::IndifferentHashAccessor
end
- def init_with(coder)
- @coder = coder['coder']
- super
- end
-
- def encode_with(coder)
- coder['coder'] = @coder
- super
- end
-
private
def default_value?(value)
diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb
index cf95e25be0..2662b7e874 100644
--- a/activerecord/lib/active_record/type/string.rb
+++ b/activerecord/lib/active_record/type/string.rb
@@ -11,7 +11,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
case value
when ::Numeric, ActiveSupport::Duration then value.to_s
when ::String then ::String.new(value)
@@ -21,10 +21,6 @@ module ActiveRecord
end
end
- def text?
- true
- end
-
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index cab1c7bf1e..19a10021bc 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -1,7 +1,10 @@
module ActiveRecord
module Type
class Time < Value # :nodoc:
- include TimeValue
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
+ )
def type
:time
diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb
deleted file mode 100644
index 8d9ac25643..0000000000
--- a/activerecord/lib/active_record/type/time_value.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module ActiveRecord
- module Type
- module TimeValue # :nodoc:
- def klass
- ::Time
- end
-
- def type_cast_for_schema(value)
- "'#{value.to_s(:db)}'"
- end
-
- def user_input_in_time_zone(value)
- value.in_time_zone
- end
-
- private
-
- def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
- # Treat 0000-00-00 00:00:00 as nil.
- return if year.nil? || (year == 0 && mon == 0 && mday == 0)
-
- if offset
- time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
- return unless time
-
- time -= offset
- Base.default_timezone == :utc ? time : time.getlocal
- else
- ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
- end
- end
-
- # Doesn't handle time zones.
- def fast_string_to_time(string)
- if string =~ ConnectionAdapters::Column::Format::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
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 859b51ca90..fc3ef5e83b 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -1,43 +1,46 @@
module ActiveRecord
module Type
- class Value # :nodoc:
+ class Value
attr_reader :precision, :scale, :limit
- # Valid options are +precision+, +scale+, and +limit+.
- def initialize(options = {})
- options.assert_valid_keys(:precision, :scale, :limit)
- @precision = options[:precision]
- @scale = options[:scale]
- @limit = options[:limit]
+ def initialize(precision: nil, limit: nil, scale: nil)
+ @precision = precision
+ @scale = scale
+ @limit = limit
end
- # The simplified type that this object represents. Returns a symbol such
- # as +:string+ or +:integer+
- def type; end
+ def type # :nodoc:
+ end
- # Type casts a string from the database into the appropriate ruby type.
- # Classes which do not need separate type casting behavior for database
- # and user provided values should override +cast_value+ instead.
- def type_cast_from_database(value)
- type_cast(value)
+ # Convert a value from database input to the appropriate ruby type. The
+ # return value of this method will be returned from
+ # ActiveRecord::AttributeMethods::Read#read_attribute. The default
+ # implementation just calls Value#cast.
+ #
+ # +value+ The raw input, as provided from the database.
+ def deserialize(value)
+ cast(value)
end
# Type casts a value from user input (e.g. from a setter). This value may
- # be a string from the form builder, or an already type cast value
- # provided manually to a setter.
+ # be a string from the form builder, or a ruby object passed to a setter.
+ # There is currently no way to differentiate between which source it came
+ # from.
+ #
+ # The return value of this method will be returned from
+ # ActiveRecord::AttributeMethods::Read#read_attribute. See also:
+ # Value#cast_value.
#
- # Classes which do not need separate type casting behavior for database
- # and user provided values should override +type_cast+ or +cast_value+
- # instead.
- def type_cast_from_user(value)
- type_cast(value)
+ # +value+ The raw input, as provided to the attribute setter.
+ def cast(value)
+ cast_value(value) unless value.nil?
end
# Cast a value from the ruby type to a type that the database knows how
# to understand. The returned value from this method should be a
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
- # +nil+
- def type_cast_for_database(value)
+ # +nil+.
+ def serialize(value)
value
end
@@ -49,21 +52,10 @@ module ActiveRecord
# These predicates are not documented, as I need to look further into
# their use, and see if they can be removed entirely.
- def text? # :nodoc:
- false
- end
-
- def number? # :nodoc:
- false
- end
-
def binary? # :nodoc:
false
end
- def klass # :nodoc:
- end
-
# Determines whether a value has changed for dirty checking. +old_value+
# and +new_value+ will always be type-cast. Types should not need to
# override this method.
@@ -72,10 +64,23 @@ module ActiveRecord
end
# Determines whether the mutable value has been modified since it was
- # read. Returns +false+ by default. This method should not be overridden
- # directly. Types which return a mutable value should include
- # +Type::Mutable+, which will define this method.
- def changed_in_place?(*)
+ # read. Returns +false+ by default. If your type returns an object
+ # which could be mutated, you should override this method. You will need
+ # to either:
+ #
+ # - pass +new_value+ to Value#serialize and compare it to
+ # +raw_old_value+
+ #
+ # or
+ #
+ # - pass +raw_old_value+ to Value#deserialize and compare it to
+ # +new_value+
+ #
+ # +raw_old_value+ The original value, before being passed to
+ # +deserialize+.
+ #
+ # +new_value+ The current value, after type casting.
+ def changed_in_place?(raw_old_value, new_value)
false
end
@@ -88,14 +93,9 @@ module ActiveRecord
private
- def type_cast(value)
- cast_value(value) unless value.nil?
- end
-
# Convenience method for types which do not need separate type casting
- # behavior for user and database inputs. Called by
- # +type_cast_from_database+ and +type_cast_from_user+ for all values
- # except +nil+.
+ # behavior for user and database inputs. Called by Value#cast for
+ # values except +nil+.
def cast_value(value) # :doc:
value
end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 9e4a130b40..3878270770 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -1,34 +1,29 @@
module ActiveRecord
module TypeCaster
class Connection
- def initialize(connection, table_name)
- @connection = connection
+ def initialize(klass, table_name)
+ @klass = klass
@table_name = table_name
end
def type_cast_for_database(attribute_name, value)
return value if value.is_a?(Arel::Nodes::BindParam)
- type = type_for(attribute_name)
- type.type_cast_for_database(value)
+ column = column_for(attribute_name)
+ connection.type_cast_from_column(column, value)
end
protected
- attr_reader :connection, :table_name
+ attr_reader :table_name
+ delegate :connection, to: :@klass
private
- def type_for(attribute_name)
+ def column_for(attribute_name)
if connection.schema_cache.table_exists?(table_name)
- column_for(attribute_name).cast_type
- else
- Type::Value.new
+ connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
end
end
-
- def column_for(attribute_name)
- connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
- end
end
end
end
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
index 03c9e8ff83..4b1941351c 100644
--- a/activerecord/lib/active_record/type_caster/map.rb
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -8,7 +8,7 @@ module ActiveRecord
def type_cast_for_database(attr_name, value)
return value if value.is_a?(Arel::Nodes::BindParam)
type = types.type_for_attribute(attr_name.to_s)
- type.type_cast_for_database(value)
+ type.serialize(value)
end
protected
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index f27adc9c40..e227212827 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -40,7 +40,7 @@ module ActiveRecord
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
# exception instead of returning +false+ if the record is not valid.
def save!(options={})
- perform_validations(options) ? super : raise_record_invalid
+ perform_validations(options) ? super : raise_validation_error
end
# Runs all the validations within the specified context. Returns +true+ if
@@ -61,21 +61,9 @@ module ActiveRecord
alias_method :validate, :valid?
- # Runs all the validations within the specified context. Returns +true+ if
- # no errors are found, raises +RecordInvalid+ otherwise.
- #
- # If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
- # <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
- #
- # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
- # some <tt>:on</tt> option will only run in the specified context.
- def validate!(context = nil)
- valid?(context) || raise_record_invalid
- end
-
protected
- def raise_record_invalid
+ def raise_validation_error
raise(RecordInvalid.new(self))
end
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
index ef5a6cbbe7..5991fbad8e 100644
--- a/activerecord/lib/active_record/validations/length.rb
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -2,11 +2,23 @@ module ActiveRecord
module Validations
class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
+ return unless should_validate?(record) || associations_are_dirty?(record)
if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
end
super
end
+
+ def associations_are_dirty?(record)
+ attributes.any? do |attribute|
+ value = record.read_attribute_for_validation(attribute)
+ if value.respond_to?(:loaded?) && value.loaded?
+ value.target.any?(&:marked_for_destruction?)
+ else
+ false
+ end
+ end
+ end
end
module ClassMethods
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 61b30749d9..75d5bd5a35 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,6 +2,7 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate(record)
+ return unless should_validate?(record)
super
attributes.each do |attribute|
next unless record.class._reflect_on_association(attribute)
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index f52f91e89c..9be4b10a55 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -11,6 +11,7 @@ module ActiveRecord
end
def validate_each(record, attribute, value)
+ return unless should_validate?(record)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
@@ -59,7 +60,8 @@ module ActiveRecord
end
column = klass.columns_hash[attribute_name]
- value = klass.type_for_attribute(attribute_name).type_cast_for_database(value)
+ cast_type = klass.type_for_attribute(attribute_name)
+ value = cast_type.serialize(value)
value = klass.connection.type_cast(value)
if value.is_a?(String) && column.limit
value = value.to_s[0, column.limit]
@@ -67,7 +69,7 @@ module ActiveRecord
value = Arel::Nodes::Quoted.new(value)
- comparison = if !options[:case_sensitive] && value && column.text?
+ comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 64cde143a1..49a68fb94c 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -22,15 +22,14 @@ module ActiveRecord
end
def primary_key(table)
- @primary_keys[table]
+ @primary_keys[table] || "id"
end
def merge_column(table_name, name, sql_type = nil, options = {})
@columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
options[:default],
- lookup_cast_type(sql_type.to_s),
- sql_type.to_s,
+ fetch_type_metadata(sql_type),
options[:null])
end
@@ -38,6 +37,10 @@ module ActiveRecord
@columns[table_name]
end
+ def table_exists?(*)
+ true
+ end
+
def active?
true
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 99e3d7021d..ecf1368a91 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -193,7 +193,7 @@ module ActiveRecord
author = Author.create!(name: 'john')
Post.create!(author: author, title: 'foo', body: 'bar')
query = author.posts.where(title: 'foo').select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({"title" => "foo"}, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
@@ -203,7 +203,7 @@ module ActiveRecord
def test_select_methods_passing_a_relation
Post.create!(title: 'foo', body: 'bar')
query = Post.where(title: 'foo').select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({"title" => "foo"}, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index ce01b16362..4762ef43b5 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -94,7 +94,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
with_example_table do
@connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::Value.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -106,10 +106,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_exec_typecasts_bind_vals
with_example_table do
@connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [bind])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
diff --git a/activerecord/test/cases/adapters/mysql/datetime_test.rb b/activerecord/test/cases/adapters/mysql/datetime_test.rb
deleted file mode 100644
index ae00f4e131..0000000000
--- a/activerecord/test/cases/adapters/mysql/datetime_test.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'cases/helper'
-
-if mysql_56?
- class DateTimeTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
-
- class Foo < ActiveRecord::Base; end
-
- def test_default_datetime_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
- assert_nil activerecord_column_option('foos', 'created_at', 'precision')
- end
-
- def test_datetime_data_type_with_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
- assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_timestamps_helper_with_custom_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 4
- end
- assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_passing_precision_to_datetime_does_not_set_limit
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 4
- end
- assert_nil activerecord_column_option('foos', 'created_at', 'limit')
- assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
- end
-
- def test_invalid_datetime_precision_raises_error
- assert_raises ActiveRecord::ActiveRecordError do
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 7
- end
- end
- end
-
- def test_mysql_agrees_with_activerecord_about_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 4
- end
- assert_equal 4, mysql_datetime_precision('foos', 'created_at')
- assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
- end
-
- def test_formatting_datetime_according_to_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.datetime :created_at, precision: 0
- t.datetime :updated_at, precision: 4
- end
- date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
- Foo.create!(created_at: date, updated_at: date)
- assert foo = Foo.find_by(created_at: date)
- assert_equal date.to_s, foo.created_at.to_s
- assert_equal date.to_s, foo.updated_at.to_s
- assert_equal 000000, foo.created_at.usec
- assert_equal 999900, foo.updated_at.usec
- end
-
- private
-
- def mysql_datetime_precision(table_name, column_name)
- results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
- result = results.find do |result_hash|
- result_hash["column_name"] == column_name
- end
- result && result["datetime_precision"]
- end
-
- def activerecord_column_option(tablename, column_name, option)
- result = ActiveRecord::Base.connection.columns(tablename).find do |column|
- column.name == column_name
- end
- result && result.send(option)
- end
- 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 85db8f4614..48ceef365e 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 @@
-# encoding: utf-8
require "cases/helper"
require 'support/ddl_helper'
@@ -16,7 +15,7 @@ module ActiveRecord
assert_raise ActiveRecord::NoDatabaseError do
configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
connection = ActiveRecord::Base.mysql_connection(configuration)
- connection.exec_query('drop table if exists ex')
+ connection.drop_table 'ex', if_exists: true
end
end
@@ -111,7 +110,7 @@ module ActiveRecord
result = @conn.exec_query('SELECT status FROM ex')
- assert_equal 2, result.column_types['status'].type_cast_from_database(result.last['status'])
+ assert_equal 2, result.column_types['status'].deserialize(result.last['status'])
end
end
@@ -129,10 +128,10 @@ module ActiveRecord
private
def insert(ctx, data, table='ex')
- binds = data.map { |name, value|
- [ctx.columns(table).find { |x| x.name == name }, value]
+ binds = data.map { |name, value|
+ Relation::QueryAttribute.new(name, value, Type::Value.new)
}
- columns = binds.map(&:first).map(&:name)
+ columns = binds.map(&:name)
sql = "INSERT INTO #{table} (#{columns.join(", ")})
VALUES (#{(['?'] * columns.length).join(', ')})"
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 403f7cbc74..2f9c070255 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -139,7 +139,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
def drop_tables_directly(table_names, connection = @connection)
table_names.each do |name|
- connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ connection.drop_table name, if_exists: true
end
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index ab547747df..b7f9c2ce84 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
teardown do
- @connection.execute "drop table if exists mysql_doubles"
+ @connection.drop_table "mysql_doubles", if_exists: true
end
class MysqlDouble < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index d261e2db55..a8b39b21d4 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -22,7 +22,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert_raise ActiveRecord::NoDatabaseError do
configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
connection = ActiveRecord::Base.mysql2_connection(configuration)
- connection.exec_query('drop table if exists ex')
+ connection.drop_table 'ex', if_exists: true
end
end
@@ -122,11 +122,4 @@ class MysqlConnectionTest < ActiveRecord::TestCase
ensure
@connection.execute "DROP TABLE `bar_baz`"
end
-
- if mysql_56?
- def test_quote_time_usec
- assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0))
- assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime)
- end
- end
end
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_test.rb
deleted file mode 100644
index ae00f4e131..0000000000
--- a/activerecord/test/cases/adapters/mysql2/datetime_test.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'cases/helper'
-
-if mysql_56?
- class DateTimeTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
-
- class Foo < ActiveRecord::Base; end
-
- def test_default_datetime_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
- assert_nil activerecord_column_option('foos', 'created_at', 'precision')
- end
-
- def test_datetime_data_type_with_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
- assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_timestamps_helper_with_custom_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 4
- end
- assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_passing_precision_to_datetime_does_not_set_limit
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 4
- end
- assert_nil activerecord_column_option('foos', 'created_at', 'limit')
- assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
- end
-
- def test_invalid_datetime_precision_raises_error
- assert_raises ActiveRecord::ActiveRecordError do
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 7
- end
- end
- end
-
- def test_mysql_agrees_with_activerecord_about_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, precision: 4
- end
- assert_equal 4, mysql_datetime_precision('foos', 'created_at')
- assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
- end
-
- def test_formatting_datetime_according_to_precision
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.datetime :created_at, precision: 0
- t.datetime :updated_at, precision: 4
- end
- date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
- Foo.create!(created_at: date, updated_at: date)
- assert foo = Foo.find_by(created_at: date)
- assert_equal date.to_s, foo.created_at.to_s
- assert_equal date.to_s, foo.updated_at.to_s
- assert_equal 000000, foo.created_at.usec
- assert_equal 999900, foo.updated_at.usec
- end
-
- private
-
- def mysql_datetime_precision(table_name, column_name)
- results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
- result = results.find do |result_hash|
- result_hash["column_name"] == column_name
- end
- result && result["datetime_precision"]
- end
-
- def activerecord_column_option(tablename, column_name, option)
- result = ActiveRecord::Base.connection.columns(tablename).find do |column|
- column.name == column_name
- end
- result && result.send(option)
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 7f97b454bb..beb829fc46 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
def drop_tables_directly(table_names, connection = @connection)
table_names.each do |name|
- connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ connection.drop_table name, if_exists: true
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 9c49599d34..271b570eb5 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -16,23 +16,31 @@ module ActiveRecord
def test_initializes_schema_migrations_for_encoding_utf8mb4
smtn = ActiveRecord::Migrator.schema_migrations_table_name
- connection.drop_table(smtn) if connection.table_exists?(smtn)
+ connection.drop_table smtn, if_exists: true
- config = connection.instance_variable_get(:@config)
- original_encoding = config[:encoding]
+ database_name = connection.current_database
+ database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'")
+
+ original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"]
+ original_collation = database_info["DEFAULT_COLLATION_NAME"]
+
+ execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4")
- config[:encoding] = 'utf8mb4'
connection.initialize_schema_migrations_table
assert connection.column_exists?(smtn, :version, :string, limit: Mysql2Adapter::MAX_INDEX_LENGTH_FOR_UTF8MB4)
ensure
- config[:encoding] = original_encoding
+ execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}")
end
private
def connection
@connection ||= ActiveRecord::Base.connection
end
+
+ def execute(sql)
+ connection.execute(sql)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 331f33455e..2163e35e70 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -25,10 +24,11 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
end
end
@column = PgArray.columns_hash['tags']
+ @type = PgArray.type_for_attribute("tags")
end
teardown do
- @connection.execute 'drop table if exists pg_arrays'
+ @connection.drop_table 'pg_arrays', if_exists: true
disable_extension!('hstore', @connection)
end
@@ -36,13 +36,11 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal :string, @column.type
assert_equal "character varying", @column.sql_type
assert @column.array?
- assert_not @column.number?
- assert_not @column.binary?
+ assert_not @type.binary?
ratings_column = PgArray.columns_hash['ratings']
assert_equal :integer, ratings_column.type
assert ratings_column.array?
- assert_not ratings_column.number?
end
def test_default
@@ -94,9 +92,9 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
end
def test_type_cast_array
- assert_equal(['1', '2', '3'], @column.type_cast_from_database('{1,2,3}'))
- assert_equal([], @column.type_cast_from_database('{}'))
- assert_equal([nil], @column.type_cast_from_database('{NULL}'))
+ assert_equal(['1', '2', '3'], @type.deserialize('{1,2,3}'))
+ assert_equal([], @type.deserialize('{}'))
+ assert_equal([nil], @type.deserialize('{NULL}'))
end
def test_type_cast_integers
@@ -208,7 +206,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
x = PgArray.create!(tags: tags)
x.reload
- assert_equal x.tags_before_type_cast, PgArray.columns_hash['tags'].type_cast_for_database(tags)
+ assert_equal x.tags_before_type_cast, PgArray.type_for_attribute('tags').serialize(tags)
end
def test_quoting_non_standard_delimiters
@@ -216,8 +214,8 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
comma_delim = OID::Array.new(ActiveRecord::Type::String.new, ',')
semicolon_delim = OID::Array.new(ActiveRecord::Type::String.new, ';')
- assert_equal %({"hello,",world;}), comma_delim.type_cast_for_database(strings)
- assert_equal %({hello,;"world;"}), semicolon_delim.type_cast_for_database(strings)
+ assert_equal %({"hello,",world;}), comma_delim.serialize(strings)
+ assert_equal %({hello,;"world;"}), semicolon_delim.serialize(strings)
end
def test_mutate_array
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index f154ba4cdc..1a5ff4316c 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
require 'support/schema_dumping_helper'
@@ -14,30 +13,34 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase
@connection.create_table('postgresql_bit_strings', :force => true) do |t|
t.bit :a_bit, default: "00000011", limit: 8
t.bit_varying :a_bit_varying, default: "0011", limit: 4
+ t.bit :another_bit
+ t.bit_varying :another_bit_varying
end
end
def teardown
return unless @connection
- @connection.execute 'DROP TABLE IF EXISTS postgresql_bit_strings'
+ @connection.drop_table 'postgresql_bit_strings', if_exists: true
end
def test_bit_string_column
column = PostgresqlBitString.columns_hash["a_bit"]
assert_equal :bit, column.type
assert_equal "bit(8)", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlBitString.type_for_attribute("a_bit")
+ assert_not type.binary?
end
def test_bit_string_varying_column
column = PostgresqlBitString.columns_hash["a_bit_varying"]
assert_equal :bit_varying, column.type
assert_equal "bit varying(4)", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlBitString.type_for_attribute("a_bit_varying")
+ assert_not type.binary?
end
def test_default
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index aeebec034d..16db5ab83d 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
class PostgresqlByteaTest < ActiveRecord::TestCase
@@ -17,10 +16,11 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
end
end
@column = ByteaDataType.columns_hash['payload']
+ @type = ByteaDataType.type_for_attribute("payload")
end
teardown do
- @connection.execute 'drop table if exists bytea_data_type'
+ @connection.drop_table 'bytea_data_type', if_exists: true
end
def test_column
@@ -40,16 +40,16 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
data = "\u001F\x8B"
assert_equal('UTF-8', data.encoding.name)
- assert_equal('ASCII-8BIT', @column.type_cast_from_database(data).encoding.name)
+ assert_equal('ASCII-8BIT', @type.deserialize(data).encoding.name)
end
def test_type_cast_binary_value
data = "\u001F\x8B".force_encoding("BINARY")
- assert_equal(data, @column.type_cast_from_database(data))
+ assert_equal(data, @type.deserialize(data))
end
def test_type_case_nil
- assert_equal(nil, @column.type_cast_from_database(nil))
+ assert_equal(nil, @type.deserialize(nil))
end
def test_read_value
diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
index 6c1b29f7fe..5a9796887c 100644
--- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
@@ -26,6 +26,13 @@ module ActiveRecord
connection.change_column :strings, :somedate, :timestamp, cast_as: :timestamp
assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type
end
+
+ def test_change_type_with_array
+ connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp
+ column = connection.columns(:strings).find { |c| c.name == 'somedate' }
+ assert_equal :datetime, column.type
+ assert column.array?
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
index 54b679d3ab..6cb11d17b4 100644
--- a/activerecord/test/cases/adapters/postgresql/cidr_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
@@ -10,14 +10,14 @@ module ActiveRecord
ip = IPAddr.new("255.0.0.0/8")
ip2 = IPAddr.new("127.0.0.1")
- assert_equal "255.0.0.0/8", type.type_cast_for_database(ip)
- assert_equal "127.0.0.1/32", type.type_cast_for_database(ip2)
+ assert_equal "255.0.0.0/8", type.serialize(ip)
+ assert_equal "127.0.0.1/32", type.serialize(ip2)
end
test "casting does nothing with non-IPAddr objects" do
type = OID::Cidr.new
- assert_equal "foo", type.type_cast_for_database("foo")
+ assert_equal "foo", type.serialize("foo")
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index cfbe842948..f706847890 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'support/schema_dumping_helper'
@@ -20,7 +19,7 @@ if ActiveRecord::Base.connection.supports_extensions?
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS citexts;'
+ @connection.drop_table 'citexts', if_exists: true
disable_extension!('citext', @connection)
end
@@ -32,9 +31,10 @@ if ActiveRecord::Base.connection.supports_extensions?
column = Citext.columns_hash['cival']
assert_equal :citext, column.type
assert_equal 'citext', column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = Citext.type_for_attribute('cival')
+ assert_not type.binary?
end
def test_change_table_supports_json
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index 24c1969dee..16e3f90a47 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
@@ -30,7 +29,7 @@ module PostgresqlCompositeBehavior
def teardown
super
- @connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
+ @connection.drop_table 'postgresql_composites', if_exists: true
@connection.execute 'DROP TYPE IF EXISTS full_address'
reset_connection
PostgresqlComposite.reset_column_information
@@ -50,9 +49,10 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
column = PostgresqlComposite.columns_hash["address"]
assert_nil column.type
assert_equal "full_address", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlComposite.type_for_attribute("address")
+ assert_not type.binary?
end
def test_composite_mapping
@@ -83,17 +83,17 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
class FullAddressType < ActiveRecord::Type::Value
def type; :full_address end
- def type_cast_from_database(value)
+ def deserialize(value)
if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/
FullAddress.new($1, $2)
end
end
- def type_cast_from_user(value)
+ def cast(value)
value
end
- def type_cast_for_database(value)
+ def serialize(value)
return if value.nil?
"(#{value.city},#{value.street})"
end
@@ -111,9 +111,10 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
column = PostgresqlComposite.columns_hash["address"]
assert_equal :full_address, column.type
assert_equal "full_address", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlComposite.type_for_attribute("address")
+ assert_not type.binary?
end
def test_composite_mapping
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index ab7fd3c6d5..55ad76c8c0 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -126,12 +126,12 @@ module ActiveRecord
end
def test_statement_key_is_logged
- bindval = 1
- @connection.exec_query('SELECT $1::integer', 'SQL', [[nil, bindval]])
+ bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
+ @connection.exec_query('SELECT $1::integer', 'SQL', [bind])
name = @subscriber.payloads.last[:statement_name]
assert name
- res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})")
- plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first
+ res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
+ plan = res.column_types['QUERY PLAN'].deserialize res.rows.first.first
assert_operator plan.length, :>, 0
end
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index ebb04814bb..26e064c937 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
@@ -20,7 +19,7 @@ class PostgresqlDomainTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_domains'
+ @connection.drop_table 'postgresql_domains', if_exists: true
@connection.execute 'DROP DOMAIN IF EXISTS custom_money'
reset_connection
end
@@ -29,9 +28,10 @@ class PostgresqlDomainTest < ActiveRecord::TestCase
column = PostgresqlDomain.columns_hash["price"]
assert_equal :decimal, column.type
assert_equal "custom_money", column.sql_type
- assert column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlDomain.type_for_attribute("price")
+ assert_not type.binary?
end
def test_domain_acts_like_basetype
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 88b3b2cc0e..7458de23d8 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
@@ -22,7 +21,7 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_enums'
+ @connection.drop_table 'postgresql_enums', if_exists: true
@connection.execute 'DROP TYPE IF EXISTS mood'
reset_connection
end
@@ -31,9 +30,10 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
column = PostgresqlEnum.columns_hash["current_mood"]
assert_equal :enum, column.type
assert_equal "mood", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlEnum.type_for_attribute("current_mood")
+ assert_not type.binary?
end
def test_enum_defaults
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index e7e5d8e32a..b83063c94e 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -14,16 +13,17 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS tsvectors;'
+ @connection.drop_table 'tsvectors', if_exists: true
end
def test_tsvector_column
column = Tsvector.columns_hash["text_vector"]
assert_equal :tsvector, column.type
assert_equal "tsvector", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = Tsvector.type_for_attribute("text_vector")
+ assert_not type.binary?
end
def test_update_tsvector
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index ed2bf554bb..41e9572907 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
require 'support/schema_dumping_helper'
@@ -19,16 +18,17 @@ class PostgresqlPointTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_points'
+ @connection.drop_table 'postgresql_points', if_exists: true
end
def test_column
column = PostgresqlPoint.columns_hash["x"]
assert_equal :point, column.type
assert_equal "point", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlPoint.type_for_attribute("x")
+ assert_not type.binary?
end
def test_default
@@ -84,7 +84,7 @@ class PostgresqlGeometricTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_geometrics'
+ @connection.drop_table 'postgresql_geometrics', if_exists: true
end
def test_geometric_types
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 2ca123409a..e6835031c3 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -29,10 +28,11 @@ if ActiveRecord::Base.connection.supports_extensions?
end
end
@column = Hstore.columns_hash['tags']
+ @type = Hstore.type_for_attribute("tags")
end
teardown do
- @connection.execute 'drop table if exists hstores'
+ @connection.drop_table 'hstores', if_exists: true
end
def test_hstore_included_in_extensions
@@ -54,9 +54,9 @@ if ActiveRecord::Base.connection.supports_extensions?
def test_column
assert_equal :hstore, @column.type
assert_equal "hstore", @column.sql_type
- assert_not @column.number?
- assert_not @column.binary?
assert_not @column.array?
+
+ assert_not @type.binary?
end
def test_default
@@ -110,10 +110,10 @@ if ActiveRecord::Base.connection.supports_extensions?
end
def test_type_cast_hstore
- assert_equal({'1' => '2'}, @column.type_cast_from_database("\"1\"=>\"2\""))
- assert_equal({}, @column.type_cast_from_database(""))
- assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b")))
+ assert_equal({'1' => '2'}, @type.deserialize("\"1\"=>\"2\""))
+ assert_equal({}, @type.deserialize(""))
+ assert_equal({'key'=>nil}, @type.deserialize('key => NULL'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b")))
end
def test_with_store_accessors
@@ -165,47 +165,47 @@ if ActiveRecord::Base.connection.supports_extensions?
end
def test_gen1
- assert_equal(%q(" "=>""), @column.cast_type.type_cast_for_database({' '=>''}))
+ assert_equal(%q(" "=>""), @type.serialize({' '=>''}))
end
def test_gen2
- assert_equal(%q(","=>""), @column.cast_type.type_cast_for_database({','=>''}))
+ assert_equal(%q(","=>""), @type.serialize({','=>''}))
end
def test_gen3
- assert_equal(%q("="=>""), @column.cast_type.type_cast_for_database({'='=>''}))
+ assert_equal(%q("="=>""), @type.serialize({'='=>''}))
end
def test_gen4
- assert_equal(%q(">"=>""), @column.cast_type.type_cast_for_database({'>'=>''}))
+ assert_equal(%q(">"=>""), @type.serialize({'>'=>''}))
end
def test_parse1
- assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
end
def test_parse2
- assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ "))
+ assert_equal({" " => " "}, @type.deserialize("\\ =>\\ "))
end
def test_parse3
- assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>"))
+ assert_equal({"=" => ">"}, @type.deserialize("==>>"))
end
def test_parse4
- assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w'))
+ assert_equal({"=a"=>"q=w"}, @type.deserialize('\=a=>q=w'))
end
def test_parse5
- assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w'))
+ assert_equal({"=a"=>"q=w"}, @type.deserialize('"=a"=>q\=w'))
end
def test_parse6
- assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w'))
+ assert_equal({"\"a"=>"q>w"}, @type.deserialize('"\"a"=>q>w'))
end
def test_parse7
- assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w'))
+ assert_equal({"\"a"=>"q\"w"}, @type.deserialize('\"a=>q"w'))
end
def test_rewrite
diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index 74163ac712..24199c69b8 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -15,7 +15,7 @@ class PostgresqlInfinityTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute("DROP TABLE IF EXISTS postgresql_infinities")
+ @connection.drop_table 'postgresql_infinities', if_exists: true
end
test "type casting infinity on a float column" do
diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb
index 7f8751281e..679a0fc7b3 100644
--- a/activerecord/test/cases/adapters/postgresql/integer_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb
@@ -16,7 +16,7 @@ class PostgresqlIntegerTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute "drop table if exists pg_integers"
+ @connection.drop_table "pg_integers", if_exists: true
end
test "schema properly respects bigint ranges" do
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 5f6cda1986..d8fded16b4 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -1,4 +1,4 @@
-# encoding: utf-8
+# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -14,29 +14,28 @@ module PostgresqlJSONSharedTestCases
def setup
@connection = ActiveRecord::Base.connection
begin
- @connection.transaction do
- @connection.create_table('json_data_type') do |t|
- t.public_send column_type, 'payload', default: {} # t.json 'payload', default: {}
- t.public_send column_type, 'settings' # t.json 'settings'
- end
+ @connection.create_table('json_data_type') do |t|
+ t.public_send column_type, 'payload', default: {} # t.json 'payload', default: {}
+ t.public_send column_type, 'settings' # t.json 'settings'
end
rescue ActiveRecord::StatementInvalid
- skip "do not test on PG without json"
+ skip "do not test on PostgreSQL without #{column_type} type."
end
@column = JsonDataType.columns_hash['payload']
end
def teardown
- @connection.execute 'drop table if exists json_data_type'
+ @connection.drop_table :json_data_type, if_exists: true
end
def test_column
column = JsonDataType.columns_hash["payload"]
assert_equal column_type, column.type
assert_equal column_type.to_s, column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = JsonDataType.type_for_attribute("payload")
+ assert_not type.binary?
end
def test_default
@@ -78,16 +77,16 @@ module PostgresqlJSONSharedTestCases
end
def test_type_cast_json
- column = JsonDataType.columns_hash["payload"]
+ type = JsonDataType.type_for_attribute("payload")
data = "{\"a_key\":\"a_value\"}"
- hash = column.type_cast_from_database(data)
+ hash = type.deserialize(data)
assert_equal({'a_key' => 'a_value'}, hash)
- assert_equal({'a_key' => 'a_value'}, column.type_cast_from_database(data))
+ assert_equal({'a_key' => 'a_value'}, type.deserialize(data))
- assert_equal({}, column.type_cast_from_database("{}"))
- assert_equal({'key'=>nil}, column.type_cast_from_database('{"key": null}'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"})))
+ assert_equal({}, type.deserialize("{}"))
+ assert_equal({'key'=>nil}, type.deserialize('{"key": null}'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
end
def test_rewrite
@@ -179,6 +178,14 @@ module PostgresqlJSONSharedTestCases
assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload)
assert_not json.changed?
end
+
+ def test_assigning_invalid_json
+ json = JsonDataType.new
+
+ json.payload = 'foo'
+
+ assert_nil json.payload
+ end
end
class PostgresqlJSONTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index c5371348ea..ce0ad16557 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -23,16 +22,17 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'drop table if exists ltrees'
+ @connection.drop_table 'ltrees', if_exists: true
end
def test_column
column = Ltree.columns_hash['path']
assert_equal :ltree, column.type
assert_equal "ltree", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = Ltree.type_for_attribute('path')
+ assert_not type.binary?
end
def test_write
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 78afd49b0b..cedd399380 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -11,13 +10,13 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
@connection = ActiveRecord::Base.connection
@connection.execute("set lc_monetary = 'C'")
@connection.create_table('postgresql_moneys', force: true) do |t|
- t.column "wealth", "money"
- t.column "depth", "money", default: "150.55"
+ t.money "wealth"
+ t.money "depth", default: "150.55"
end
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_moneys'
+ @connection.drop_table 'postgresql_moneys', if_exists: true
end
def test_column
@@ -25,9 +24,10 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
assert_equal :money, column.type
assert_equal "money", column.sql_type
assert_equal 2, column.scale
- assert column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlMoney.type_for_attribute("wealth")
+ assert_not type.binary?
end
def test_default
@@ -46,11 +46,11 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
end
def test_money_type_cast
- column = PostgresqlMoney.columns_hash['wealth']
- assert_equal(12345678.12, column.type_cast_from_user("$12,345,678.12"))
- assert_equal(12345678.12, column.type_cast_from_user("$12.345.678,12"))
- assert_equal(-1.15, column.type_cast_from_user("-$1.15"))
- assert_equal(-2.25, column.type_cast_from_user("($2.25)"))
+ type = PostgresqlMoney.type_for_attribute('wealth')
+ assert_equal(12345678.12, type.cast("$12,345,678.12"))
+ assert_equal(12345678.12, type.cast("$12.345.678,12"))
+ assert_equal(-1.15, type.cast("-$1.15"))
+ assert_equal(-2.25, type.cast("($2.25)"))
end
def test_schema_dumping
diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
index 741876d3ca..033695518e 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -16,34 +15,37 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_network_addresses'
+ @connection.drop_table 'postgresql_network_addresses', if_exists: true
end
def test_cidr_column
column = PostgresqlNetworkAddress.columns_hash["cidr_address"]
assert_equal :cidr, column.type
assert_equal "cidr", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlNetworkAddress.type_for_attribute("cidr_address")
+ assert_not type.binary?
end
def test_inet_column
column = PostgresqlNetworkAddress.columns_hash["inet_address"]
assert_equal :inet, column.type
assert_equal "inet", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlNetworkAddress.type_for_attribute("inet_address")
+ assert_not type.binary?
end
def test_macaddr_column
column = PostgresqlNetworkAddress.columns_hash["mac_address"]
assert_equal :macaddr, column.type
assert_equal "macaddr", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = PostgresqlNetworkAddress.type_for_attribute("mac_address")
+ assert_not type.binary?
end
def test_network_types
diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
index 70aa898439..093b81fe8d 100644
--- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
@@ -12,7 +12,7 @@ class PostgresqlNumberTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_numbers'
+ @connection.drop_table 'postgresql_numbers', if_exists: true
end
def test_data_type
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 6bb2b26cd5..a934180a43 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/ddl_helper'
require 'support/connection_helper'
@@ -228,8 +227,8 @@ module ActiveRecord
"DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
)
ensure
- @connection.exec_query('DROP TABLE IF EXISTS ex')
- @connection.exec_query('DROP TABLE IF EXISTS ex2')
+ @connection.drop_table 'ex', if_exists: true
+ @connection.drop_table 'ex2', if_exists: true
end
def test_exec_insert_number
@@ -284,7 +283,7 @@ module ActiveRecord
string = @connection.quote('foo')
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -298,9 +297,9 @@ module ActiveRecord
string = @connection.quote('foo')
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [bind])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -437,10 +436,10 @@ module ActiveRecord
private
def insert(ctx, data)
- binds = data.map { |name, value|
- [ctx.columns('ex').find { |x| x.name == name }, value]
+ binds = data.map { |name, value|
+ bind_param(value, name)
}
- columns = binds.map(&:first).map(&:name)
+ columns = binds.map(&:name)
bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
@@ -457,6 +456,10 @@ module ActiveRecord
def connection_without_insert_returning
ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
end
+
+ def bind_param(value, name = nil)
+ ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 894cf1ffa2..e4420d9d13 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -27,21 +27,16 @@ module ActiveRecord
assert_equal "'Infinity'", @conn.quote(infinity)
end
- def test_quote_time_usec
- assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0))
- assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0).to_datetime)
- end
-
def test_quote_range
range = "1,2]'; SELECT * FROM users; --".."a"
type = OID::Range.new(Type::Integer.new, :int8range)
- assert_equal "'[1,0]'", @conn.quote(type.type_cast_for_database(range))
+ assert_equal "'[1,0]'", @conn.quote(type.serialize(range))
end
def test_quote_bit_string
value = "'); SELECT * FROM users; /*\n01\n*/--"
type = OID::Bit.new
- assert_equal nil, @conn.quote(type.type_cast_for_database(value))
+ assert_equal nil, @conn.quote(type.serialize(value))
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 70cf21100a..b6b451ca5c 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -91,7 +91,7 @@ _SQL
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
+ @connection.drop_table 'postgresql_ranges', if_exists: true
@connection.execute 'DROP TYPE IF EXISTS floatrange'
reset_connection
end
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
index 056a035622..f507328868 100644
--- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -7,8 +7,8 @@ class PostgresqlRenameTableTest < ActiveRecord::TestCase
end
def teardown
- @connection.execute 'DROP TABLE IF EXISTS "before_rename"'
- @connection.execute 'DROP TABLE IF EXISTS "after_rename"'
+ @connection.drop_table "before_rename", if_exists: true
+ @connection.drop_table "after_rename", if_exists: true
end
test "renaming a table also renames the primary key index" do
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index 99c26c4bf7..6937145439 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -55,7 +55,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
set_session_auth
USERS.each do |u|
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
set_session_auth
end
end
@@ -67,7 +67,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
USERS.each do |u|
@connection.clear_cache!
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
set_session_auth
end
end
@@ -111,4 +111,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
@connection.session_auth = auth || 'default'
end
+ def bind_param(value)
+ ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 642b63357f..77ff6d01bc 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -151,10 +151,10 @@ class SchemaTest < ActiveRecord::TestCase
def test_schema_change_with_prepared_stmt
altered = false
- @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
@connection.exec_query "alter table developers add column zomg int", 'sql', []
altered = true
- @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
ensure
# We are not using DROP COLUMN IF EXISTS because that syntax is only
# supported by pg 9.X
@@ -435,6 +435,10 @@ class SchemaTest < ActiveRecord::TestCase
assert_equal this_index_column, this_index.columns[0]
assert_equal this_index_name, this_index.name
end
+
+ def bind_param(value)
+ ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
+ end
end
class SchemaForeignKeyTest < ActiveRecord::TestCase
@@ -456,8 +460,8 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase
output = dump_table_schema "wagons"
assert_match %r{\s+add_foreign_key "wagons", "my_schema\.trains", column: "train_id"$}, output
ensure
- @connection.execute "DROP TABLE IF EXISTS wagons"
- @connection.execute "DROP TABLE IF EXISTS my_schema.trains"
+ @connection.drop_table "wagons", if_exists: true
+ @connection.drop_table "my_schema.trains", if_exists: true
@connection.execute "DROP SCHEMA IF EXISTS my_schema"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index eb32c4d2c2..da14063e20 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -70,53 +70,6 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal(-1.0 / 0.0, d.updated_at)
end
- def test_default_datetime_precision
- ActiveRecord::Base.connection.create_table(:foos)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
- assert_nil activerecord_column_option('foos', 'created_at', 'precision')
- end
-
- def test_timestamp_data_type_with_precision
- ActiveRecord::Base.connection.create_table(:foos)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, :precision => 0
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, :precision => 5
- assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_timestamps_helper_with_custom_precision
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 4
- end
- assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_passing_precision_to_timestamp_does_not_set_limit
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 4
- end
- assert_nil activerecord_column_option("foos", "created_at", "limit")
- assert_nil activerecord_column_option("foos", "updated_at", "limit")
- end
-
- def test_invalid_timestamp_precision_raises_error
- assert_raises ActiveRecord::ActiveRecordError do
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 7
- end
- end
- end
-
- def test_postgres_agrees_with_activerecord_about_precision
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 4
- end
- assert_equal '4', pg_datetime_precision('foos', 'created_at')
- assert_equal '4', pg_datetime_precision('foos', 'updated_at')
- end
-
def test_bc_timestamp
date = Date.new(0) - 1.week
Developer.create!(:name => "aaron", :updated_at => date)
@@ -134,21 +87,4 @@ class TimestampTest < ActiveRecord::TestCase
Developer.create!(:name => "yahagi", :updated_at => date)
assert_equal date, Developer.find_by_name("yahagi").updated_at
end
-
- private
-
- def pg_datetime_precision(table_name, column_name)
- results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'")
- result = results.find do |result_hash|
- result_hash["column_name"] == column_name
- end
- result && result["datetime_precision"]
- end
-
- def activerecord_column_option(tablename, column_name, option)
- result = ActiveRecord::Base.connection.columns(tablename).find do |column|
- column.name == column_name
- end
- result && result.send(option)
- end
end
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index c88259d274..c0907b8f21 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -18,8 +18,8 @@ class PostgresqlTypeLookupTest < ActiveRecord::TestCase
bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]")
big_array = [123456789123456789]
- assert_raises(RangeError) { int_array.type_cast_from_user(big_array) }
- assert_equal big_array, bigint_array.type_cast_from_user(big_array)
+ assert_raises(RangeError) { int_array.serialize(big_array) }
+ assert_equal "{123456789123456789}", bigint_array.serialize(big_array)
end
test "range types correctly respect registration of subtypes" do
@@ -27,7 +27,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::TestCase
bigint_range = @connection.type_map.lookup(3926, -1, "int8range")
big_range = 0..123456789123456789
- assert_raises(RangeError) { int_range.type_cast_for_database(big_range) }
- assert_equal "[0,123456789123456789]", bigint_range.type_cast_for_database(big_range)
+ assert_raises(RangeError) { int_range.serialize(big_range) }
+ assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index d328f17a3f..1219e197ab 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -8,7 +7,7 @@ module PostgresqlUUIDHelper
end
def drop_table(name)
- connection.execute "drop table if exists #{name}"
+ connection.drop_table name, if_exists: true
end
end
@@ -49,9 +48,10 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
assert_equal "uuid", column.sql_type
- assert_not column.number?
- assert_not column.binary?
assert_not column.array?
+
+ type = UUIDType.type_for_attribute("guid")
+ assert_not type.binary?
end
def test_treat_blank_uuid_as_nil
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index ac8464a65d..b097deb2f4 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'support/schema_dumping_helper'
@@ -23,7 +22,7 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'drop table if exists xml_data_type'
+ @connection.drop_table 'xml_data_type', if_exists: true
end
def test_column
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 274e358e4a..243f65df98 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -85,9 +85,9 @@ module ActiveRecord
def test_quoting_binary_strings
value = "hello".encode('ascii-8bit')
- type = SQLite3String.new
+ type = Type::String.new
- assert_equal "'hello'", @conn.quote(type.type_cast_for_database(value))
+ assert_equal "'hello'", @conn.quote(type.serialize(value))
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 029663e7f4..5ca3c92027 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/owner'
require 'tempfile'
@@ -23,7 +22,7 @@ module ActiveRecord
def test_bad_connection
assert_raise ActiveRecord::NoDatabaseError do
connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db")
- connection.exec_query('drop table if exists ex')
+ connection.drop_table 'ex', if_exists: true
end
end
@@ -83,8 +82,7 @@ module ActiveRecord
def test_exec_insert
with_example_table do
- column = @conn.columns('ex').find { |col| col.name == 'number' }
- vals = [[column, 10]]
+ vals = [Relation::QueryAttribute.new("number", 10, Type::Value.new)]
@conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals)
result = @conn.exec_query(
@@ -157,7 +155,7 @@ module ActiveRecord
with_example_table 'id int, data string' do
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new(nil, 1, Type::Value.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -169,10 +167,9 @@ module ActiveRecord
def test_exec_query_typecasts_bind_vals
with_example_table 'id int, data string' do
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @conn.columns('ex').find { |col| col.name == 'id' }
result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -194,7 +191,7 @@ module ActiveRecord
binary.save!
assert_equal str, binary.data
ensure
- DualEncoding.connection.execute('DROP TABLE IF EXISTS dual_encodings')
+ DualEncoding.connection.drop_table 'dual_encodings', if_exists: true
end
def test_type_cast_should_not_mutate_encoding
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
index f545fc2011..deedf67c8e 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/owner'
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index 3e0032ec73..472e270f8c 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -8,12 +8,7 @@ module ActiveRecord
test 'does not duplicate conditions' do
scope = AssociationScope.scope(Author.new.association(:welcome_posts),
Author.connection)
- wheres = scope.where_values.map(&:right)
- binds = scope.bind_values.map(&:last)
- wheres = scope.where_values.map(&:right).reject { |node|
- Arel::Nodes::BindParam === node
- }
- assert_equal wheres.uniq, wheres
+ binds = scope.where_clause.binds.map(&:value)
assert_equal binds.uniq, binds
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 17394cb6f7..a425b3ed88 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -122,14 +122,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first
- assert citibank_result.association_cache.key?(:firm_with_primary_key)
+ assert citibank_result.association(:firm_with_primary_key).loaded?
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first
- assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols)
+ assert citibank_result.association(:firm_with_primary_key_symbols).loaded?
end
def test_creating_the_belonging_object
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 371635d20a..7d8b933992 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -77,9 +77,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_has_many_through_with_order
authors = Author.includes(:favorite_authors).to_a
+ assert authors.count > 0
assert_no_queries { authors.map(&:favorite_authors) }
end
+ def test_eager_loaded_has_one_association_with_references_does_not_run_additional_queries
+ Post.update_all(author_id: nil)
+ authors = Author.includes(:post).references(:post).to_a
+ assert authors.count > 0
+ assert_no_queries { authors.map(&:post) }
+ end
+
def test_with_two_tables_in_from_without_getting_double_quoted
posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a
assert_equal 2, posts.first.comments.size
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 21a45042fa..eebeaad7cf 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -33,6 +33,8 @@ require 'models/ship'
require 'models/tyre'
require 'models/subscriber'
require 'models/subscription'
+require 'models/zine'
+require 'models/interest'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -97,7 +99,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
:posts, :readers, :taggings, :cars, :jobs, :tags,
- :categorizations
+ :categorizations, :zines, :interests
def setup
Client.destroyed_client_ids.clear
@@ -316,16 +318,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
car = cars(:honda)
- scoped_count = car.foo_bulbs.where_values.count
+ scope = car.foo_bulbs.where_values_hash
bulb = car.foo_bulbs.build
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = car.foo_bulbs.create
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = car.foo_bulbs.create!
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
end
def test_no_sql_should_be_fired_if_association_already_loaded
@@ -435,6 +437,32 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
end
+ def test_taking
+ posts(:other_by_bob).destroy
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take!
+ authors(:bob).posts.to_a
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take!
+ end
+
+ def test_taking_not_found
+ authors(:bob).posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { authors(:bob).posts.take! }
+ authors(:bob).posts.to_a
+ assert_raise(ActiveRecord::RecordNotFound) { authors(:bob).posts.take! }
+ end
+
+ def test_taking_with_inverse_of
+ interests(:woodsmanship).destroy
+ interests(:survival).destroy
+
+ zine = zines(:going_out)
+ interest = zine.interests.take
+ assert_equal interests(:hunting), interest
+ assert_same zine, interest.zine
+ end
+
def test_cant_save_has_many_readonly_association
authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
authors(:david).readonly_comments.each { |c| assert c.readonly? }
@@ -1307,7 +1335,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised { topic.destroy }
end
- uses_transaction :test_dependence_with_transaction_support_on_failure
def test_dependence_with_transaction_support_on_failure
firm = companies(:first_firm)
clients = firm.clients
@@ -1679,6 +1706,82 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, firm.clients.size
end
+ def test_calling_none_should_count_instead_of_loading_association
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.none? # use count query
+ end
+ assert !firm.clients.loaded?
+ end
+
+ def test_calling_none_on_loaded_association_should_not_use_query
+ firm = companies(:first_firm)
+ firm.clients.collect # force load
+ assert_no_queries { assert ! firm.clients.none? }
+ end
+
+ def test_calling_none_should_defer_to_collection_if_using_a_block
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.expects(:size).never
+ firm.clients.none? { true }
+ end
+ assert firm.clients.loaded?
+ end
+
+ def test_calling_none_should_return_true_if_none
+ firm = companies(:another_firm)
+ assert firm.clients_like_ms.none?
+ assert_equal 0, firm.clients_like_ms.size
+ end
+
+ def test_calling_none_should_return_false_if_any
+ firm = companies(:first_firm)
+ assert !firm.limited_clients.none?
+ assert_equal 1, firm.limited_clients.size
+ end
+
+ def test_calling_one_should_count_instead_of_loading_association
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.one? # use count query
+ end
+ assert !firm.clients.loaded?
+ end
+
+ def test_calling_one_on_loaded_association_should_not_use_query
+ firm = companies(:first_firm)
+ firm.clients.collect # force load
+ assert_no_queries { assert ! firm.clients.one? }
+ end
+
+ def test_calling_one_should_defer_to_collection_if_using_a_block
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.expects(:size).never
+ firm.clients.one? { true }
+ end
+ assert firm.clients.loaded?
+ end
+
+ def test_calling_one_should_return_false_if_zero
+ firm = companies(:another_firm)
+ assert ! firm.clients_like_ms.one?
+ assert_equal 0, firm.clients_like_ms.size
+ end
+
+ def test_calling_one_should_return_true_if_one
+ firm = companies(:first_firm)
+ assert firm.limited_clients.one?
+ assert_equal 1, firm.limited_clients.size
+ end
+
+ def test_calling_one_should_return_false_if_more_than_one
+ firm = companies(:first_firm)
+ assert ! firm.clients.one?
+ assert_equal 3, firm.clients.size
+ end
+
def test_joins_with_namespaced_model_should_use_correct_type
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 9b6757e256..3b6a4038cd 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -237,16 +237,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
- scoped_count = pirate.association(:foo_bulb).scope.where_values.count
+ scope = pirate.association(:foo_bulb).scope.where_values_hash
bulb = pirate.build_foo_bulb
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = pirate.create_foo_bulb
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = pirate.create_foo_bulb!
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
end
def test_create_association
@@ -276,7 +276,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_create_with_inexistent_foreign_key_failing
firm = Firm.create(name: 'GlobalMegaCorp')
- assert_raises(ActiveRecord::UnknownAttributeError) do
+ assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do
firm.create_account_with_inexistent_foreign_key
end
end
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index 321fb6c8dd..8b765a2e0c 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -18,8 +18,8 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
teardown do
- @connection.drop_table 'parents' if @connection.table_exists? 'parents'
- @connection.drop_table 'children' if @connection.table_exists? 'children'
+ @connection.drop_table 'parents', if_exists: true
+ @connection.drop_table 'children', if_exists: true
end
test "belongs_to associations are not required by default" do
@@ -40,7 +40,7 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
record = model.new
assert_not record.save
- assert_equal ["Parent can't be blank"], record.errors.full_messages
+ assert_equal ["Parent must exist"], record.errors.full_messages
record.parent = Parent.new
assert record.save
@@ -64,12 +64,32 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
record = model.new
assert_not record.save
- assert_equal ["Child can't be blank"], record.errors.full_messages
+ assert_equal ["Child must exist"], record.errors.full_messages
record.child = Child.new
assert record.save
end
+ test "required has_one associations have a correct error message" do
+ model = subclass_of(Parent) do
+ has_one :child, required: true, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Child"
+ end
+
+ record = model.create
+ assert_equal ["Child must exist"], record.errors.full_messages
+ end
+
+ test "required belongs_to associations have a correct error message" do
+ model = subclass_of(Child) do
+ belongs_to :parent, required: true, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
+ end
+
+ record = model.create
+ assert_equal ["Parent must exist"], record.errors.full_messages
+ end
+
private
def subclass_of(klass, &block)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 72963fd56c..3d202a5527 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -43,28 +43,6 @@ class AssociationsTest < ActiveRecord::TestCase
assert_equal favs, fav2
end
- def test_clear_association_cache_stored
- firm = Firm.find(1)
- assert_kind_of Firm, firm
-
- firm.clear_association_cache
- assert_equal Firm.find(1).clients.collect(&:name).sort, firm.clients.collect(&:name).sort
- end
-
- def test_clear_association_cache_new_record
- firm = Firm.new
- client_stored = Client.find(3)
- client_new = Client.new
- client_new.name = "The Joneses"
- clients = [ client_stored, client_new ]
-
- firm.clients << clients
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
-
- firm.clear_association_cache
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
- end
-
def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
@@ -141,7 +119,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_association_with_references
firm = companies(:first_firm)
- assert_equal ['foo'], firm.association_with_references.references_values
+ assert_includes firm.association_with_references.references_values, 'foo'
end
end
@@ -238,7 +216,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
def test_scoped_allows_conditions
- assert developers(:david).projects.merge!(where: 'foo').where_values.include?('foo')
+ assert developers(:david).projects.merge(where: 'foo').to_sql.include?('foo')
end
test "getting a scope from an association" do
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
index 53bd58e22e..2aeb2601c2 100644
--- a/activerecord/test/cases/attribute_decorators_test.rb
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -12,11 +12,11 @@ module ActiveRecord
super(delegate)
end
- def type_cast_from_user(value)
+ def cast(value)
"#{super} #{@decoration}"
end
- alias type_cast_from_database type_cast_from_user
+ alias deserialize cast
end
setup do
@@ -28,7 +28,7 @@ module ActiveRecord
teardown do
return unless @connection
- @connection.drop_table 'attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model'
+ @connection.drop_table 'attribute_decorators_model', if_exists: true
Model.attribute_type_decorations.clear
Model.reset_column_information
end
@@ -51,7 +51,7 @@ module ActiveRecord
end
test "undecorated columns are not touched" do
- Model.attribute :another_string, Type::String.new, default: 'something or other'
+ Model.attribute :another_string, :string, default: 'something or other'
Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
assert_equal 'something or other', Model.new.another_string
@@ -86,7 +86,7 @@ module ActiveRecord
end
test "decorating attributes does not modify parent classes" do
- Model.attribute :another_string, Type::String.new, default: 'whatever'
+ Model.attribute :another_string, :string, default: 'whatever'
Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
child_class = Class.new(Model)
child_class.decorate_attribute_type(:another_string, :test) { |t| StringDecorator.new(t) }
@@ -102,15 +102,15 @@ module ActiveRecord
end
class Multiplier < SimpleDelegator
- def type_cast_from_user(value)
+ def cast(value)
return if value.nil?
value * 2
end
- alias type_cast_from_database type_cast_from_user
+ alias deserialize cast
end
test "decorating with a proc" do
- Model.attribute :an_int, Type::Integer.new
+ Model.attribute :an_int, :integer
type_is_integer = proc { |_, type| type.type == :integer }
Model.decorate_matching_attribute_types type_is_integer, :multiplier do |type|
Multiplier.new(type)
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index e38b32d7fc..74e556211b 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -17,7 +17,7 @@ module ActiveRecord
include ActiveRecord::AttributeMethods
- def self.column_names
+ def self.attribute_names
%w{ one two three }
end
@@ -25,11 +25,11 @@ module ActiveRecord
end
def self.columns
- column_names.map { FakeColumn.new(name) }
+ attribute_names.map { FakeColumn.new(name) }
end
def self.columns_hash
- Hash[column_names.map { |name|
+ Hash[attribute_names.map { |name|
[name, FakeColumn.new(name)]
}]
end
@@ -39,13 +39,13 @@ module ActiveRecord
def test_define_attribute_methods
instance = @klass.new
- @klass.column_names.each do |name|
+ @klass.attribute_names.each do |name|
assert !instance.methods.map(&:to_s).include?(name)
end
@klass.define_attribute_methods
- @klass.column_names.each do |name|
+ @klass.attribute_names.each do |name|
assert instance.methods.map(&:to_s).include?(name), "#{name} is not defined"
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 4036294121..243c90e945 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -758,12 +758,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_bulk_update_respects_access_control
privatize("title=(value)")
- assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") }
- assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
+ assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new(:title => "Rants about pants") }
+ assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
end
def test_bulk_update_raise_unknown_attribute_error
- error = assert_raises(ActiveRecord::UnknownAttributeError) {
+ error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) {
Topic.new(hello: "world")
}
assert_instance_of Topic, error.record
@@ -937,6 +937,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert model.id_came_from_user?
end
+ def test_accessed_fields
+ model = @target.first
+
+ assert_equal [], model.accessed_fields
+
+ model.title
+
+ assert_equal ["title"], model.accessed_fields
+ end
+
private
def new_topic_like_ar_class(&block)
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index ba53612d30..9d927481ec 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -65,6 +65,16 @@ module ActiveRecord
assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h)
end
+ test "to_hash maintains order" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: '2.2', bar: '3.3')
+
+ attributes[:bar]
+ hash = attributes.to_h
+
+ assert_equal [[:foo, 2], [:bar, 3.3]], hash.to_a
+ end
+
test "values_before_type_cast" do
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
attributes = builder.build_from_database(foo: '1.1', bar: '2.2')
@@ -141,12 +151,12 @@ module ActiveRecord
end
class MyType
- def type_cast_from_user(value)
+ def cast(value)
return if value.nil?
value + " from user"
end
- def type_cast_from_database(value)
+ def deserialize(value)
return if value.nil?
value + " from database"
end
@@ -186,5 +196,16 @@ module ActiveRecord
attributes.freeze
assert_equal({ foo: "1" }, attributes.to_hash)
end
+
+ test "#accessed_attributes returns only attributes which have been read" do
+ builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new)
+ attributes = builder.build_from_database(foo: "1", bar: "2")
+
+ assert_equal [], attributes.accessed
+
+ attributes.fetch_value(:foo)
+
+ assert_equal [:foo], attributes.accessed
+ end
end
end
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 39a976fcc8..aa419c7a67 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
test "from_database + read type casts from database" do
- @type.expect(:type_cast_from_database, 'type cast from database', ['a value'])
+ @type.expect(:deserialize, 'type cast from database', ['a value'])
attribute = Attribute.from_database(nil, 'a value', @type)
type_cast_value = attribute.value
@@ -22,7 +22,7 @@ module ActiveRecord
end
test "from_user + read type casts from user" do
- @type.expect(:type_cast_from_user, 'type cast from user', ['a value'])
+ @type.expect(:cast, 'type cast from user', ['a value'])
attribute = Attribute.from_user(nil, 'a value', @type)
type_cast_value = attribute.value
@@ -31,7 +31,7 @@ module ActiveRecord
end
test "reading memoizes the value" do
- @type.expect(:type_cast_from_database, 'from the database', ['whatever'])
+ @type.expect(:deserialize, 'from the database', ['whatever'])
attribute = Attribute.from_database(nil, 'whatever', @type)
type_cast_value = attribute.value
@@ -42,7 +42,7 @@ module ActiveRecord
end
test "reading memoizes falsy values" do
- @type.expect(:type_cast_from_database, false, ['whatever'])
+ @type.expect(:deserialize, false, ['whatever'])
attribute = Attribute.from_database(nil, 'whatever', @type)
attribute.value
@@ -58,27 +58,27 @@ module ActiveRecord
end
test "from_database + read_for_database type casts to and from database" do
- @type.expect(:type_cast_from_database, 'read from database', ['whatever'])
- @type.expect(:type_cast_for_database, 'ready for database', ['read from database'])
+ @type.expect(:deserialize, 'read from database', ['whatever'])
+ @type.expect(:serialize, 'ready for database', ['read from database'])
attribute = Attribute.from_database(nil, 'whatever', @type)
- type_cast_for_database = attribute.value_for_database
+ serialize = attribute.value_for_database
- assert_equal 'ready for database', type_cast_for_database
+ assert_equal 'ready for database', serialize
end
test "from_user + read_for_database type casts from the user to the database" do
- @type.expect(:type_cast_from_user, 'read from user', ['whatever'])
- @type.expect(:type_cast_for_database, 'ready for database', ['read from user'])
+ @type.expect(:cast, 'read from user', ['whatever'])
+ @type.expect(:serialize, 'ready for database', ['read from user'])
attribute = Attribute.from_user(nil, 'whatever', @type)
- type_cast_for_database = attribute.value_for_database
+ serialize = attribute.value_for_database
- assert_equal 'ready for database', type_cast_for_database
+ assert_equal 'ready for database', serialize
end
test "duping dups the value" do
- @type.expect(:type_cast_from_database, 'type cast', ['a value'])
+ @type.expect(:deserialize, 'type cast', ['a value'])
attribute = Attribute.from_database(nil, 'a value', @type)
value_from_orig = attribute.value
@@ -90,7 +90,7 @@ module ActiveRecord
end
test "duping does not dup the value if it is not dupable" do
- @type.expect(:type_cast_from_database, false, ['a value'])
+ @type.expect(:deserialize, false, ['a value'])
attribute = Attribute.from_database(nil, 'a value', @type)
assert_same attribute.value, attribute.dup.value
@@ -102,11 +102,11 @@ module ActiveRecord
end
class MyType
- def type_cast_from_user(value)
+ def cast(value)
value + " from user"
end
- def type_cast_from_database(value)
+ def deserialize(value)
value + " from database"
end
end
@@ -169,5 +169,24 @@ module ActiveRecord
second = Attribute.from_user(:foo, 1, Type::Integer.new)
assert_not_equal first, second
end
+
+ test "an attribute has not been read by default" do
+ attribute = Attribute.from_database(:foo, 1, Type::Value.new)
+ assert_not attribute.has_been_read?
+ end
+
+ test "an attribute has been read when its value is calculated" do
+ attribute = Attribute.from_database(:foo, 1, Type::Value.new)
+ attribute.value
+ assert attribute.has_been_read?
+ end
+
+ test "an attribute can not be mutated if it has not been read,
+ and skips expensive calculations" do
+ type_which_raises_from_all_methods = Object.new
+ attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods)
+
+ assert_not attribute.changed_in_place_from?("bar")
+ end
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index dbe1eb48db..e7b76b1cf9 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -1,17 +1,17 @@
require 'cases/helper'
class OverloadedType < ActiveRecord::Base
- attribute :overloaded_float, Type::Integer.new
- attribute :overloaded_string_with_limit, Type::String.new(limit: 50)
- attribute :non_existent_decimal, Type::Decimal.new
- attribute :string_with_default, Type::String.new, default: 'the overloaded default'
+ attribute :overloaded_float, :integer
+ attribute :overloaded_string_with_limit, :string, limit: 50
+ attribute :non_existent_decimal, :decimal
+ attribute :string_with_default, :string, default: 'the overloaded default'
end
class ChildOfOverloadedType < OverloadedType
end
class GrandchildOfOverloadedType < ChildOfOverloadedType
- attribute :overloaded_float, Type::Float.new
+ attribute :overloaded_float, :float
end
class UnoverloadedType < ActiveRecord::Base
@@ -50,15 +50,15 @@ module ActiveRecord
end
test "overloaded properties with limit" do
- assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit
- assert_equal 255, UnoverloadedType.columns_hash['overloaded_string_with_limit'].limit
+ assert_equal 50, OverloadedType.type_for_attribute('overloaded_string_with_limit').limit
+ assert_equal 255, UnoverloadedType.type_for_attribute('overloaded_string_with_limit').limit
end
test "nonexistent attribute" do
data = OverloadedType.new(non_existent_decimal: 1)
assert_equal BigDecimal.new(1), data.non_existent_decimal
- assert_raise ActiveRecord::UnknownAttributeError do
+ assert_raise ActiveModel::AttributeAssignment::UnknownAttributeError do
UnoverloadedType.new(non_existent_decimal: 1)
end
end
@@ -87,29 +87,74 @@ module ActiveRecord
assert_equal 4.4, data.overloaded_float
end
- test "overloading properties does not change column order" do
- column_names = OverloadedType.column_names
- assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names
+ test "overloading properties does not attribute method order" do
+ attribute_names = OverloadedType.attribute_names
+ assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), attribute_names
end
test "caches are cleared" do
klass = Class.new(OverloadedType)
- assert_equal 6, klass.columns.length
- assert_not klass.columns_hash.key?('wibble')
- assert_equal 6, klass.column_types.length
+ assert_equal 6, klass.attribute_types.length
assert_equal 6, klass.column_defaults.length
- assert_not klass.column_names.include?('wibble')
- assert_equal 5, klass.content_columns.length
+ assert_not klass.attribute_types.include?('wibble')
klass.attribute :wibble, Type::Value.new
- assert_equal 7, klass.columns.length
- assert klass.columns_hash.key?('wibble')
- assert_equal 7, klass.column_types.length
+ assert_equal 7, klass.attribute_types.length
assert_equal 7, klass.column_defaults.length
- assert klass.column_names.include?('wibble')
- assert_equal 6, klass.content_columns.length
+ assert klass.attribute_types.include?('wibble')
+ end
+
+ test "the given default value is cast from user" do
+ custom_type = Class.new(Type::Value) do
+ def cast(*)
+ "from user"
+ end
+
+ def deserialize(*)
+ "from database"
+ end
+ end
+
+ klass = Class.new(OverloadedType) do
+ attribute :wibble, custom_type.new, default: "default"
+ end
+ model = klass.new
+
+ assert_equal "from user", model.wibble
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ test "arrays types can be specified" do
+ klass = Class.new(OverloadedType) do
+ attribute :my_array, :string, limit: 50, array: true
+ attribute :my_int_array, :integer, array: true
+ end
+
+ string_array = ConnectionAdapters::PostgreSQL::OID::Array.new(
+ Type::String.new(limit: 50))
+ int_array = ConnectionAdapters::PostgreSQL::OID::Array.new(
+ Type::Integer.new)
+ refute_equal string_array, int_array
+ assert_equal string_array, klass.type_for_attribute("my_array")
+ assert_equal int_array, klass.type_for_attribute("my_int_array")
+ end
+
+ test "range types can be specified" do
+ klass = Class.new(OverloadedType) do
+ attribute :my_range, :string, limit: 50, range: true
+ attribute :my_int_range, :integer, range: true
+ end
+
+ string_range = ConnectionAdapters::PostgreSQL::OID::Range.new(
+ Type::String.new(limit: 50))
+ int_range = ConnectionAdapters::PostgreSQL::OID::Range.new(
+ Type::Integer.new)
+ refute_equal string_range, int_range
+ assert_equal string_range, klass.type_for_attribute("my_range")
+ assert_equal int_range, klass.type_for_attribute("my_int_range")
+ end
end
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 52765881d0..859afc4553 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -1061,11 +1061,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_not_ignore_different_error_messages_on_the_same_attribute
+ old_validators = Ship._validators.deep_dup
+ old_callbacks = Ship._validate_callbacks.deep_dup
Ship.validates_format_of :name, :with => /\w/
@pirate.ship.name = ""
@pirate.catchphrase = nil
assert @pirate.invalid?
assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
+ ensure
+ Ship._validators = old_validators if old_validators
+ Ship._validate_callbacks = old_callbacks if old_callbacks
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 0debd30e5c..993350ebd6 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'active_support/concurrency/latch'
@@ -813,7 +812,6 @@ class BasicsTest < ActiveRecord::TestCase
def test_dup_does_not_copy_associations
author = authors(:david)
assert_not_equal [], author.posts
- author.send(:clear_association_cache)
author_dup = author.dup
assert_equal [], author_dup.posts
@@ -904,8 +902,8 @@ class BasicsTest < ActiveRecord::TestCase
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
- attribute :my_house_population, Type::Integer.new
- attribute :atoms_in_universe, Type::Integer.new
+ attribute :my_house_population, :integer
+ attribute :atoms_in_universe, :integer
end
def test_big_decimal_conditions
@@ -1428,7 +1426,7 @@ class BasicsTest < ActiveRecord::TestCase
attrs.delete 'id'
typecast = Class.new(ActiveRecord::Type::Value) {
- def type_cast value
+ def cast value
"t.lo"
end
}
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index c12fa03015..9e428098e4 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -37,9 +37,9 @@ class EachTest < ActiveRecord::TestCase
if Enumerator.method_defined? :size
def test_each_should_return_a_sized_enumerator
- assert_equal 11, Post.find_each(:batch_size => 1).size
- assert_equal 5, Post.find_each(:batch_size => 2, :start => 7).size
- assert_equal 11, Post.find_each(:batch_size => 10_000).size
+ assert_equal 11, Post.find_each(batch_size: 1).size
+ assert_equal 5, Post.find_each(batch_size: 2, begin_at: 7).size
+ assert_equal 11, Post.find_each(batch_size: 10_000).size
end
end
@@ -99,7 +99,16 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_start_from_the_start_option
assert_queries(@total) do
- Post.find_in_batches(:batch_size => 1, :start => 2) do |batch|
+ Post.find_in_batches(batch_size: 1, begin_at: 2) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
+
+ def test_find_in_batches_should_end_at_the_end_option
+ assert_queries(6) do
+ Post.find_in_batches(batch_size: 1, end_at: 5) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
end
@@ -163,7 +172,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){}
+ Post.find_in_batches({ batch_size: 42, begin_at: 1 }.freeze){}
end
end
@@ -172,7 +181,7 @@ class EachTest < ActiveRecord::TestCase
start_nick = nick_order_subscribers.second.nick
subscribers = []
- Subscriber.find_in_batches(:batch_size => 1, :start => start_nick) do |batch|
+ Subscriber.find_in_batches(batch_size: 1, begin_at: start_nick) do |batch|
subscribers.concat(batch)
end
@@ -200,11 +209,32 @@ class EachTest < ActiveRecord::TestCase
end
end
+ def test_find_in_batches_start_deprecated
+ assert_deprecated do
+ assert_queries(@total) do
+ Post.find_in_batches(batch_size: 1, start: 2) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
+ end
+
+ def test_find_each_start_deprecated
+ assert_deprecated do
+ assert_queries(@total) do
+ Post.find_each(batch_size: 1, start: 2) do |post|
+ assert_kind_of Post, post
+ end
+ end
+ end
+ end
+
if Enumerator.method_defined? :size
def test_find_in_batches_should_return_a_sized_enumerator
assert_equal 11, Post.find_in_batches(:batch_size => 1).size
assert_equal 6, Post.find_in_batches(:batch_size => 2).size
- assert_equal 4, Post.find_in_batches(:batch_size => 2, :start => 4).size
+ assert_equal 4, Post.find_in_batches(batch_size: 2, begin_at: 4).size
assert_equal 4, Post.find_in_batches(:batch_size => 3).size
assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index ccf2be369d..86dee929bf 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
# Without using prepared statements, it makes no sense to test
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 66663b3e0e..1e38b97c4a 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -22,8 +22,8 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
- @subscriber = LogListener.new
- @pk = Topic.columns_hash[Topic.primary_key]
+ @subscriber = LogListener.new
+ @pk = Topic.columns_hash[Topic.primary_key]
@subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
@@ -40,7 +40,7 @@ module ActiveRecord
def test_binds_are_logged
sub = @connection.substitute_at(@pk)
- binds = [[@pk, 1]]
+ binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
@connection.exec_query(sql, 'SQL', binds)
@@ -49,29 +49,17 @@ module ActiveRecord
assert_equal binds, message[4][:binds]
end
- def test_binds_are_logged_after_type_cast
- sub = @connection.substitute_at(@pk)
- binds = [[@pk, "3"]]
- sql = "select * from topics where id = #{sub.to_sql}"
-
- @connection.exec_query(sql, 'SQL', binds)
-
- message = @subscriber.calls.find { |args| args[4][:sql] == sql }
- assert_equal [[@pk, 3]], message[4][:binds]
- end
-
def test_find_one_uses_binds
Topic.find(1)
- binds = [[@pk, 1]]
- message = @subscriber.calls.find { |args| args[4][:binds] == binds }
+ message = @subscriber.calls.find { |args| args[4][:binds].any? { |attr| attr.value == 1 } }
assert message, 'expected a message with binds'
end
- def test_logs_bind_vars
+ def test_logs_bind_vars_after_type_cast
payload = {
:name => 'SQL',
:sql => 'select * from topics where id = ?',
- :binds => [[@pk, 10]]
+ :binds => [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
}
event = ActiveSupport::Notifications::Event.new(
'foo',
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 299217214e..f0393aa6b1 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -15,9 +15,9 @@ require 'models/treasure'
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
- attribute :world_population, Type::Integer.new
- attribute :my_house_population, Type::Integer.new
- attribute :atoms_in_universe, Type::Integer.new
+ attribute :world_population, :integer
+ attribute :my_house_population, :integer
+ attribute :atoms_in_universe, :integer
end
class CalculationsTest < ActiveRecord::TestCase
@@ -631,4 +631,9 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal({ "has trinket" => part.id }, ShipPart.joins(:trinkets).group("ship_parts.name").sum(:id))
end
+
+ def test_calculation_grouped_by_association_doesnt_error_when_no_records_have_association
+ Client.update_all(client_of: nil)
+ assert_equal({ nil => Client.count }, Client.group(:firm).count)
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 670d94dc06..3ae4a6eade 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -288,7 +288,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
- [ :after_save, :block ]
+ [ :after_save, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
@@ -357,7 +362,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
- [ :after_save, :block ]
+ [ :after_save, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
@@ -408,7 +418,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_destroy, :string ],
[ :after_destroy, :proc ],
[ :after_destroy, :object ],
- [ :after_destroy, :block ]
+ [ :after_destroy, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index bcfd66b4bf..14b95ecab1 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# Avoid column definitions in create table statements like:
# `title` varchar(255) DEFAULT NULL
def test_should_not_include_default_clause_when_default_is_null
- column = Column.new("title", nil, Type::String.new(limit: 20))
+ column = Column.new("title", nil, SqlTypeMetadata.new(limit: 20))
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -22,7 +22,7 @@ module ActiveRecord
end
def test_should_include_default_clause_when_default_is_present
- column = Column.new("title", "Hello", Type::String.new(limit: 20))
+ column = Column.new("title", "Hello", SqlTypeMetadata.new(limit: 20))
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -30,94 +30,53 @@ module ActiveRecord
end
def test_should_specify_not_null_if_null_option_is_false
- column = Column.new("title", "Hello", Type::String.new(limit: 20), "varchar(20)", false)
+ type_metadata = SqlTypeMetadata.new(limit: 20)
+ column = Column.new("title", "Hello", type_metadata, false)
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "binary(1)")
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
+ binary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
assert_equal "a", binary_column.default
- varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)")
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
+ varbinary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
assert_equal "a", varbinary_column.default
end
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- MysqlAdapter::Column.new("title", "a", Type::Binary.new, "blob")
+ AbstractMysqlAdapter::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob"))
end
+ text_type = AbstractMysqlAdapter::MysqlTypeMetadata.new(
+ SqlTypeMetadata.new(type: :text))
assert_raise ArgumentError do
- MysqlAdapter::Column.new("title", "Hello", Type::Text.new)
+ AbstractMysqlAdapter::Column.new("title", "Hello", text_type)
end
- text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)
+ text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
assert_equal nil, text_column.default
- not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new, "text", false)
+ not_null_text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type, false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
- blob_column = MysqlAdapter::Column.new("title", nil, Type::Binary.new, "blob")
+ binary_type = SqlTypeMetadata.new(sql_type: "blob")
+ blob_column = AbstractMysqlAdapter::Column.new("title", nil, binary_type)
assert !blob_column.has_default?
- text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)
+ text_type = SqlTypeMetadata.new(type: :text)
+ text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
assert !text_column.has_default?
end
end
-
- if current_adapter?(:Mysql2Adapter)
- def test_should_set_default_for_mysql_binary_data_types
- binary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "binary(1)")
- assert_equal "a", binary_column.default
-
- varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)")
- assert_equal "a", varbinary_column.default
- end
-
- def test_should_not_set_default_for_blob_and_text_data_types
- assert_raise ArgumentError do
- Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "blob")
- end
-
- assert_raise ArgumentError do
- Mysql2Adapter::Column.new("title", "Hello", Type::Text.new)
- end
-
- text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)
- assert_equal nil, text_column.default
-
- not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new, "text", false)
- assert_equal "", not_null_text_column.default
- end
-
- def test_has_default_should_return_false_for_blob_and_text_data_types
- blob_column = Mysql2Adapter::Column.new("title", nil, Type::Binary.new, "blob")
- assert !blob_column.has_default?
-
- text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)
- assert !text_column.has_default?
- end
- end
-
- if current_adapter?(:PostgreSQLAdapter)
- def test_bigint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Integer.new
- bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint")
- assert_equal :integer, bigint_column.type
- end
-
- def test_smallint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Integer.new
- smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
- assert_equal :integer, smallint_column.type
- end
- end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index d5c1dc1e5d..05c57985a1 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -79,13 +79,18 @@ module ActiveRecord
assert_lookup_type :integer, 'bigint'
end
+ def test_bigint_limit
+ cast_type = @connection.type_map.lookup("bigint")
+ assert_equal 8, cast_type.limit
+ end
+
def test_decimal_without_scale
types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)}
types.each do |type|
cast_type = @connection.type_map.lookup(type)
assert_equal :decimal, cast_type.type
- assert_equal 2, cast_type.type_cast_from_user(2.1)
+ assert_equal 2, cast_type.cast(2.1)
end
end
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 715d92af99..3cb98832c5 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -98,4 +98,15 @@ class CoreTest < ActiveRecord::TestCase
assert actual.start_with?(expected.split('XXXXXX').first)
assert actual.end_with?(expected.split('XXXXXX').last)
end
+
+ def test_pretty_print_overridden_by_inspect
+ subtopic = Class.new(Topic) do
+ def inspect
+ "inspecting topic"
+ end
+ end
+ actual = ''
+ PP.pp(subtopic.new, StringIO.new(actual))
+ assert_equal "inspecting topic\n", actual
+ end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 07a182070b..1f5055b2a2 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -180,4 +180,22 @@ class CounterCacheTest < ActiveRecord::TestCase
SpecialTopic.reset_counters(special.id, :lightweight_special_replies)
end
end
+
+ test "counters are updated both in memory and in the database on create" do
+ car = Car.new(engines_count: 0)
+ car.engines = [Engine.new, Engine.new]
+ car.save!
+
+ assert_equal 2, car.engines_count
+ assert_equal 2, car.reload.engines_count
+ end
+
+ test "counter caches are updated in memory when the default value is nil" do
+ car = Car.new(engines_count: nil)
+ car.engines = [Engine.new, Engine.new]
+ car.save!
+
+ assert_equal 2, car.engines_count
+ assert_equal 2, car.reload.engines_count
+ end
end
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
new file mode 100644
index 0000000000..6a4e64b22c
--- /dev/null
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -0,0 +1,111 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+if ActiveRecord::Base.connection.supports_datetime_with_precision?
+class DateTimePrecisionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_fixtures = false
+
+ class Foo < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ teardown do
+ @connection.drop_table :foos, if_exists: true
+ end
+
+ def test_datetime_data_type_with_precision
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
+ @connection.add_column :foos, :updated_at, :datetime, precision: 5
+ assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_timestamps_helper_with_custom_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 4
+ end
+ assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_passing_precision_to_datetime_does_not_set_limit
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 4
+ end
+ assert_nil activerecord_column_option('foos', 'created_at', 'limit')
+ assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
+ end
+
+ def test_invalid_datetime_precision_raises_error
+ assert_raises ActiveRecord::ActiveRecordError do
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 7
+ end
+ end
+ end
+
+ def test_database_agrees_with_activerecord_about_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 4
+ end
+ assert_equal 4, database_datetime_precision('foos', 'created_at')
+ assert_equal 4, database_datetime_precision('foos', 'updated_at')
+ end
+
+ def test_formatting_datetime_according_to_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.datetime :created_at, precision: 0
+ t.datetime :updated_at, precision: 4
+ end
+ date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
+ Foo.create!(created_at: date, updated_at: date)
+ assert foo = Foo.find_by(created_at: date)
+ assert_equal 1, Foo.where(updated_at: date).count
+ assert_equal date.to_s, foo.created_at.to_s
+ assert_equal date.to_s, foo.updated_at.to_s
+ assert_equal 000000, foo.created_at.usec
+ assert_equal 999900, foo.updated_at.usec
+ end
+
+ def test_schema_dump_includes_datetime_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 6
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.datetime\s+"created_at",\s+precision: 6,\s+null: false$}, output
+ assert_match %r{t\.datetime\s+"updated_at",\s+precision: 6,\s+null: false$}, output
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_datetime_precision_with_zero_should_be_dumped
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 0
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.datetime\s+"created_at",\s+precision: 0,\s+null: false$}, output
+ assert_match %r{t\.datetime\s+"updated_at",\s+precision: 0,\s+null: false$}, output
+ end
+ end
+
+ private
+
+ def database_datetime_precision(table_name, column_name)
+ results = @connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
+ result = results.find do |result_hash|
+ result_hash["column_name"] == column_name
+ end
+ result && result["datetime_precision"].to_i
+ end
+
+ def activerecord_column_option(tablename, column_name, option)
+ result = @connection.columns(tablename).find do |column|
+ column.name == column_name
+ end
+ result && result.send(option)
+ end
+end
+end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index e9bc583bf4..b9db0d0123 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -40,7 +40,7 @@ class DefaultNumbersTest < ActiveRecord::TestCase
end
teardown do
- @connection.drop_table "default_numbers" if @connection.table_exists? 'default_numbers'
+ @connection.drop_table :default_numbers, if_exists: true
end
def test_default_positive_integer
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 192ba6f7cd..3a7cc572e6 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -623,13 +623,13 @@ class DirtyTest < ActiveRecord::TestCase
end
end
- test "defaults with type that implements `type_cast_for_database`" do
+ test "defaults with type that implements `serialize`" do
type = Class.new(ActiveRecord::Type::Value) do
- def type_cast(value)
+ def cast(value)
value.to_i
end
- def type_cast_for_database(value)
+ def serialize(value)
value.to_s
end
end
@@ -711,7 +711,7 @@ class DirtyTest < ActiveRecord::TestCase
test "attribute_will_change! doesn't try to save non-persistable attributes" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'people'
- attribute :non_persisted_attribute, ActiveRecord::Type::String.new
+ attribute :non_persisted_attribute, :string
end
record = klass.new(first_name: "Sean")
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 346fcab6ea..e70d492efd 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -26,6 +26,49 @@ class EnumTest < ActiveRecord::TestCase
assert_equal @book, Book.unread.first
end
+ test "find via where with values" do
+ proposed, written = Book.statuses[:proposed], Book.statuses[:written]
+
+ assert_equal @book, Book.where(status: proposed).first
+ refute_equal @book, Book.where(status: written).first
+ assert_equal @book, Book.where(status: [proposed]).first
+ refute_equal @book, Book.where(status: [written]).first
+ refute_equal @book, Book.where("status <> ?", proposed).first
+ assert_equal @book, Book.where("status <> ?", written).first
+ end
+
+ test "find via where with symbols" do
+ assert_equal @book, Book.where(status: :proposed).first
+ refute_equal @book, Book.where(status: :written).first
+ assert_equal @book, Book.where(status: [:proposed]).first
+ refute_equal @book, Book.where(status: [:written]).first
+ refute_equal @book, Book.where.not(status: :proposed).first
+ assert_equal @book, Book.where.not(status: :written).first
+ end
+
+ test "find via where with strings" do
+ assert_equal @book, Book.where(status: "proposed").first
+ refute_equal @book, Book.where(status: "written").first
+ assert_equal @book, Book.where(status: ["proposed"]).first
+ refute_equal @book, Book.where(status: ["written"]).first
+ refute_equal @book, Book.where.not(status: "proposed").first
+ assert_equal @book, Book.where.not(status: "written").first
+ end
+
+ test "build from scope" do
+ assert Book.written.build.written?
+ refute Book.written.build.proposed?
+ end
+
+ test "build from where" do
+ assert Book.where(status: Book.statuses[:written]).build.written?
+ refute Book.where(status: Book.statuses[:written]).build.proposed?
+ assert Book.where(status: :written).build.written?
+ refute Book.where(status: :written).build.proposed?
+ assert Book.where(status: "written").build.written?
+ refute Book.where(status: "written").build.proposed?
+ end
+
test "update by declaration" do
@book.written!
assert @book.written?
@@ -129,19 +172,24 @@ class EnumTest < ActiveRecord::TestCase
assert_equal "'unknown' is not a valid status", e.message
end
+ test "NULL values from database should be casted to nil" do
+ Book.where(id: @book.id).update_all("status = NULL")
+ assert_nil @book.reload.status
+ end
+
test "assign nil value" do
@book.status = nil
- assert @book.status.nil?
+ assert_nil @book.status
end
test "assign empty string value" do
@book.status = ''
- assert @book.status.nil?
+ assert_nil @book.status
end
test "assign long empty string value" do
@book.status = ' '
- assert @book.status.nil?
+ assert_nil @book.status
end
test "constant to access the mapping" do
@@ -161,7 +209,11 @@ class EnumTest < ActiveRecord::TestCase
end
test "_before_type_cast returns the enum label (required for form fields)" do
- assert_equal "proposed", @book.status_before_type_cast
+ if @book.status_came_from_user?
+ assert_equal "proposed", @book.status_before_type_cast
+ else
+ assert_equal "proposed", @book.status
+ end
end
test "reserved enum names" do
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 9d25bdd82a..f1d5511bb8 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert_match "SELECT", sql
if binds.any?
assert_equal 1, binds.length
- assert_equal "honda", binds.flatten.last
+ assert_equal "honda", binds.last.value
else
assert_match 'honda', sql
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 07ec08ccf5..f41245dfd2 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -672,7 +672,8 @@ class FasterFixturesTest < ActiveRecord::TestCase
end
class FoxyFixturesTest < ActiveRecord::TestCase
- fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
+ fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers,
+ :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
require 'models/uuid_parent'
@@ -812,6 +813,12 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal pirates(:blackbeard), parrots(:polly).killer
end
+ def test_supports_sti_with_respective_files
+ assert_kind_of LiveParrot, live_parrots(:dusty)
+ assert_kind_of DeadParrot, dead_parrots(:deadbird)
+ assert_equal pirates(:blackbeard), dead_parrots(:deadbird).killer
+ end
+
def test_namespaced_models
assert admin_accounts(:signals37).users.include?(admin_users(:david))
assert_equal 2, admin_accounts(:signals37).users.size
@@ -834,9 +841,9 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
set_fixture_class :randomly_named_a9 =>
ClassNameThatDoesNotFollowCONVENTIONS,
:'admin/randomly_named_a9' =>
- Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ Admin::ClassNameThatDoesNotFollowCONVENTIONS1,
'admin/randomly_named_b0' =>
- Admin::ClassNameThatDoesNotFollowCONVENTIONS
+ Admin::ClassNameThatDoesNotFollowCONVENTIONS2
fixtures :randomly_named_a9, 'admin/randomly_named_a9',
:'admin/randomly_named_b0'
@@ -847,15 +854,15 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
end
def test_named_accessor_for_randomly_named_namespaced_fixture_and_class
- assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS1,
admin_randomly_named_a9(:first_instance)
- assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS2,
admin_randomly_named_b0(:second_instance)
end
def test_table_name_is_defined_in_the_model
- assert_equal 'randomly_named_table', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name
- assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
+ assert_equal 'randomly_named_table2', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name
+ assert_equal 'randomly_named_table2', Admin::ClassNameThatDoesNotFollowCONVENTIONS1.table_name
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 0a577fa2f5..f2ba28a32f 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -46,7 +46,7 @@ def in_memory_db?
end
def mysql_56?
- current_adapter?(:Mysql2Adapter) &&
+ current_adapter?(:MysqlAdapter, :Mysql2Adapter) &&
ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
end
@@ -124,7 +124,7 @@ def enable_extension!(extension, connection)
return connection.reconnect! if connection.extension_enabled?(extension)
connection.enable_extension extension
- connection.commit_db_transaction
+ connection.commit_db_transaction if connection.transaction_open?
connection.reconnect!
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index fe6323ab02..3268555cb8 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -121,6 +121,12 @@ class InheritanceTest < ActiveRecord::TestCase
assert_kind_of Cabbage, cabbage
end
+ def test_becomes_and_change_tracking_for_inheritance_columns
+ cucumber = Vegetable.find(1)
+ cabbage = cucumber.becomes!(Cabbage)
+ assert_equal ['Cucumber', 'Cabbage'], cabbage.custom_type_change
+ end
+
def test_alt_becomes_bang_resets_inheritance_type_column
vegetable = Vegetable.create!(name: "Red Pepper")
assert_nil vegetable.custom_type
@@ -294,12 +300,12 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_something_inherited
account = Account.all.merge!(:includes => :firm).find(1)
- assert account.association_cache.key?(:firm), "nil proves eager load failed"
+ assert account.association(:firm).loaded?, "association was not eager loaded"
end
def test_alt_eager_loading
cabbage = RedCabbage.all.merge!(:includes => :seller).find(4)
- assert cabbage.association_cache.key?(:seller), "nil proves eager load failed"
+ assert cabbage.association(:seller).loaded?, "association was not eager loaded"
end
def test_eager_load_belongs_to_primary_key_quoting
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 0021988083..018b7b0d8f 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/company'
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 97c0350911..4192d12ff4 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -63,14 +63,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
assert_match(/ruby rails/, logger.debugs.first)
end
- def test_ignore_binds_payload_with_nil_column
- event = Struct.new(:duration, :payload)
-
- logger = TestDebugLogSubscriber.new
- logger.sql(event.new(0, sql: 'hi mom!', binds: [[nil, 1]]))
- assert_equal 1, logger.debugs.length
- end
-
def test_basic_query_logging
Developer.all.load
wait
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index b3129a8984..30c91dfdcb 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -68,8 +68,8 @@ module ActiveRecord
five = columns.detect { |c| c.name == "five" } unless mysql
assert_equal "hello", one.default
- assert_equal true, two.type_cast_from_database(two.default)
- assert_equal false, three.type_cast_from_database(three.default)
+ assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
+ assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
assert_equal '1', four.default
assert_equal "hello", five.default unless mysql
end
@@ -403,6 +403,17 @@ module ActiveRecord
end
end
+ def test_drop_table_if_exists
+ connection.create_table(:testings)
+ assert connection.table_exists?(:testings)
+ connection.drop_table(:testings, if_exists: true)
+ assert_not connection.table_exists?(:testings)
+ end
+
+ def test_drop_table_if_exists_nothing_raised
+ assert_nothing_raised { connection.drop_table(:nonexistent, if_exists: true) }
+ end
+
private
def testing_table_with_only_foo_attribute
connection.create_table :testings, :id => false do |t|
@@ -426,7 +437,7 @@ module ActiveRecord
teardown do
[:wagons, :trains].each do |table|
- @connection.drop_table(table) if @connection.table_exists?(table)
+ @connection.drop_table table, if_exists: true
end
end
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index e6aa901814..e5ccfe0f91 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -65,7 +65,7 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_mysql_rename_column_preserves_auto_increment
rename_column "test_models", "id", "id_test"
- assert_equal "auto_increment", connection.columns("test_models").find { |c| c.name == "id_test" }.extra
+ assert connection.columns("test_models").find { |c| c.name == "id_test" }.auto_increment?
TestModel.reset_column_information
ensure
rename_column "test_models", "id_test", "id"
@@ -196,7 +196,7 @@ module ActiveRecord
old_columns = connection.columns(TestModel.table_name)
assert old_columns.find { |c|
- default = c.type_cast_from_database(c.default)
+ default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == 'approved' && c.type == :boolean && default == true
}
@@ -204,11 +204,11 @@ module ActiveRecord
new_columns = connection.columns(TestModel.table_name)
assert_not new_columns.find { |c|
- default = c.type_cast_from_database(c.default)
+ default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == 'approved' and c.type == :boolean and default == true
}
assert new_columns.find { |c|
- default = c.type_cast_from_database(c.default)
+ default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == 'approved' and c.type == :boolean and default == false
}
change_column :test_models, :approved, :boolean, :default => true
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 8cba777fe2..3844b1a92e 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -256,6 +256,11 @@ module ActiveRecord
assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }], nil], add
end
+ def test_invert_remove_reference_with_index_and_foreign_key
+ add = @recorder.inverse_of :remove_reference, [:table, :taggable, { index: true, foreign_key: true }]
+ assert_equal [:add_reference, [:table, :taggable, { index: true, foreign_key: true }], nil], add
+ end
+
def test_invert_remove_belongs_to_alias
add = @recorder.inverse_of :remove_belongs_to, [:table, :user]
assert_equal [:add_reference, [:table, :user], nil], add
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index f8b1bf8c9d..7f4790bf3e 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -8,6 +8,7 @@ module ActiveRecord
class ForeignKeyTest < ActiveRecord::TestCase
include DdlHelper
include SchemaDumpingHelper
+ include ActiveSupport::Testing::Stream
class Rocket < ActiveRecord::Base
end
@@ -29,8 +30,8 @@ module ActiveRecord
teardown do
if defined?(@connection)
- @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts'
- @connection.drop_table "rockets" if @connection.table_exists? 'rockets'
+ @connection.drop_table "astronauts", if_exists: true
+ @connection.drop_table "rockets", if_exists: true
end
end
@@ -57,7 +58,7 @@ module ActiveRecord
assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key
- assert_match(/^fk_rails_.{10}$/, fk.name)
+ assert_equal("fk_rails_78146ddd2e", fk.name)
end
def test_add_foreign_key_with_column
@@ -71,7 +72,7 @@ module ActiveRecord
assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key
- assert_match(/^fk_rails_.{10}$/, fk.name)
+ assert_equal("fk_rails_78146ddd2e", fk.name)
end
def test_add_foreign_key_with_non_standard_primary_key
@@ -146,6 +147,27 @@ module ActiveRecord
assert_equal :nullify, fk.on_update
end
+ def test_foreign_key_exists
+ @connection.add_foreign_key :astronauts, :rockets
+
+ assert @connection.foreign_key_exists?(:astronauts, :rockets)
+ assert_not @connection.foreign_key_exists?(:astronauts, :stars)
+ end
+
+ def test_foreign_key_exists_by_column
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
+
+ assert @connection.foreign_key_exists?(:astronauts, column: "rocket_id")
+ assert_not @connection.foreign_key_exists?(:astronauts, column: "star_id")
+ end
+
+ def test_foreign_key_exists_by_name
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
+
+ assert @connection.foreign_key_exists?(:astronauts, name: "fancy_named_fk")
+ assert_not @connection.foreign_key_exists?(:astronauts, name: "other_fancy_named_fk")
+ end
+
def test_remove_foreign_key_inferes_column
@connection.add_foreign_key :astronauts, :rockets
@@ -221,17 +243,6 @@ module ActiveRecord
silence_stream($stdout) { migration.migrate(:down) }
end
- private
-
- def silence_stream(stream)
- old_stream = stream.dup
- stream.reopen(IO::NULL)
- stream.sync = true
- yield
- ensure
- stream.reopen(old_stream)
- old_stream.close
- end
end
end
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index bba8897a0d..17ac72a109 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -10,8 +10,8 @@ module ActiveRecord
end
teardown do
- @connection.execute("drop table if exists testings")
- @connection.execute("drop table if exists testing_parents")
+ @connection.drop_table "testings", if_exists: true
+ @connection.drop_table "testing_parents", if_exists: true
end
test "foreign keys can be created with the table" do
@@ -95,6 +95,16 @@ module ActiveRecord
end
end
end
+
+ test "foreign key column can be removed" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, index: true, foreign_key: true
+ end
+
+ assert_difference "@connection.foreign_keys('testings').size", -1 do
+ @connection.remove_reference :testings, :testing_parent, foreign_key: true
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index a018bac43d..3eef308428 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -86,8 +86,8 @@ module ActiveRecord
assert connection.table_exists? :felines
ensure
disable_extension!('uuid-ossp', connection)
- connection.drop_table :cats if connection.table_exists? :cats
- connection.drop_table :felines if connection.table_exists? :felines
+ connection.drop_table :cats, if_exists: true
+ connection.drop_table :felines, if_exists: true
end
end
end
diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb
index 8fd770abd1..24cba84a09 100644
--- a/activerecord/test/cases/migration/table_and_index_test.rb
+++ b/activerecord/test/cases/migration/table_and_index_test.rb
@@ -6,11 +6,11 @@ module ActiveRecord
def test_add_schema_info_respects_prefix_and_suffix
conn = ActiveRecord::Base.connection
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
# Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
ActiveRecord::Base.table_name_prefix = 'p_'
ActiveRecord::Base.table_name_suffix = '_s'
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
conn.initialize_schema_migrations_table
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 7c2d3e81d6..5f9fd5d527 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -14,9 +14,9 @@ require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers"
class BigNumber < ActiveRecord::Base
unless current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
- attribute :value_of_e, Type::Integer.new
+ attribute :value_of_e, :integer
end
- attribute :my_house_population, Type::Integer.new
+ attribute :my_house_population, :integer
end
class Reminder < ActiveRecord::Base; end
@@ -90,7 +90,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_migration_detection_without_schema_migration_table
- ActiveRecord::Base.connection.drop_table('schema_migrations') if ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ ActiveRecord::Base.connection.drop_table 'schema_migrations', if_exists: true
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
@@ -119,10 +119,6 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_force_true_does_not_drop_nonexisting_table
- if Person.connection.table_exists?(:testings2)
- Person.connection.drop_table :testings2
- end
-
# using a copy as we need the drop_table method to
# continue to work for the ensure block of the test
temp_conn = Person.connection.dup
@@ -133,7 +129,7 @@ class MigrationTest < ActiveRecord::TestCase
t.column :foo, :string
end
ensure
- Person.connection.drop_table :testings2 rescue nil
+ Person.connection.drop_table :testings2, if_exists: true
end
def connection
@@ -162,6 +158,7 @@ class MigrationTest < ActiveRecord::TestCase
assert !BigNumber.table_exists?
GiveMeBigNumbers.up
+ BigNumber.reset_column_information
assert BigNumber.create(
:bank_balance => 1586.43,
@@ -397,6 +394,7 @@ class MigrationTest < ActiveRecord::TestCase
Thing.reset_table_name
Thing.reset_sequence_name
WeNeedThings.up
+ Thing.reset_column_information
assert Thing.create("content" => "hello world")
assert_equal "hello world", Thing.first.content
@@ -416,6 +414,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_suffix = '_suffix'
Reminder.reset_table_name
Reminder.reset_sequence_name
+ Reminder.reset_column_information
WeNeedReminders.up
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.first.content
@@ -427,8 +426,6 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_binary_column
- Person.connection.drop_table :binary_testings rescue nil
-
assert_nothing_raised {
Person.connection.create_table :binary_testings do |t|
t.column "data", :binary, :null => false
@@ -440,7 +437,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_nil data_column.default
- Person.connection.drop_table :binary_testings rescue nil
+ Person.connection.drop_table :binary_testings, if_exists: true
end
unless mysql_enforcing_gtid_consistency?
@@ -718,6 +715,8 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
class CopyMigrationsTest < ActiveRecord::TestCase
+ include ActiveSupport::Testing::Stream
+
def setup
end
@@ -927,23 +926,4 @@ class CopyMigrationsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = old
end
- private
-
- def quietly
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
-
- def silence_stream(stream)
- old_stream = stream.dup
- stream.reopen(IO::NULL)
- stream.sync = true
- yield
- ensure
- stream.reopen(old_stream)
- old_stream.close
- end
end
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index 4aaf6f8b5f..ae18573126 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -199,6 +199,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
+ Topic.reset_column_information
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
@@ -209,6 +210,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
assert_equal Time.zone, topic.written_on.time_zone
end
+ ensure
+ Topic.reset_column_information
end
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
@@ -227,6 +230,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
Topic.skip_time_zone_conversion_for_attributes = [:written_on]
+ Topic.reset_column_information
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
@@ -238,12 +242,14 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
end
ensure
Topic.skip_time_zone_conversion_for_attributes = []
+ Topic.reset_column_information
end
# Oracle does not have a TIME datatype.
unless current_adapter?(:OracleAdapter)
def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
+ Topic.reset_column_information
attributes = {
"bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
"bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
@@ -253,6 +259,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time
assert_not topic.bonus_time.utc?
end
+ ensure
+ Topic.reset_column_information
end
end
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 15c60d5562..f9bc266e84 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -93,14 +93,14 @@ class MultipleDbTest < ActiveRecord::TestCase
assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection
end
- def test_count_on_custom_connection
- ActiveRecord::Base.remove_connection
- assert_equal 1, College.count
- ensure
- ActiveRecord::Base.establish_connection :arunit
- end
-
unless in_memory_db?
+ def test_count_on_custom_connection
+ ActiveRecord::Base.remove_connection
+ assert_equal 1, College.count
+ ensure
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
def test_associations_should_work_when_model_has_no_connection
begin
ActiveRecord::Base.remove_connection
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 5c7e8a65d2..198cd6f341 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -672,7 +672,7 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_not_assign_destroy_key_to_a_record
- assert_nothing_raised ActiveRecord::UnknownAttributeError do
+ assert_nothing_raised ActiveModel::AttributeAssignment::UnknownAttributeError do
@pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }})
end
end
@@ -1037,4 +1037,21 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
ShipPart.create!(:ship => @ship, :name => "Stern")
assert_no_queries { @ship.valid? }
end
+
+ test "circular references do not perform unnecessary queries" do
+ ship = Ship.new(name: "The Black Rock")
+ part = ship.parts.build(name: "Stern")
+ ship.treasures.build(looter: part)
+
+ assert_queries 3 do
+ ship.save!
+ end
+ end
+
+ test "nested singular associations are validated" do
+ part = ShipPart.new(name: "Stern", ship_attributes: { name: nil })
+
+ assert_not part.valid?
+ assert_equal ["Ship name can't be blank"], part.errors.full_messages
+ end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 751eccc015..b45fbf0143 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -210,7 +210,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute("DROP TABLE IF EXISTS barcodes")
+ @connection.drop_table(:barcodes) if @connection.table_exists? :barcodes
end
def test_any_type_primary_key
@@ -260,7 +260,7 @@ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
end
teardown do
- @connection.execute("DROP TABLE IF EXISTS widgets")
+ @connection.drop_table 'widgets', if_exists: true
end
test "primary key column type with bigserial" do
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index ad09340518..6d91f96bf6 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -46,28 +46,28 @@ module ActiveRecord
def test_quoted_time_utc
with_timezone_config default: :utc do
- t = Time.now
+ t = Time.now.change(usec: 0)
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_local
with_timezone_config default: :local do
- t = Time.now
+ t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_crazy
with_timezone_config default: :asdfasdf do
- t = Time.now
+ t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_datetime_utc
with_timezone_config default: :utc do
- t = DateTime.now
+ t = Time.now.change(usec: 0).to_datetime
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
@@ -76,7 +76,7 @@ module ActiveRecord
# DateTime doesn't define getlocal, so make sure it does nothing
def test_quoted_datetime_local
with_timezone_config default: :local do
- t = DateTime.now
+ t = Time.now.change(usec: 0).to_datetime
assert_equal t.to_s(:db), @quoter.quoted_date(t)
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index a2252a836f..67e9bef808 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -86,18 +86,15 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "attribute_that_doesnt_exist", column.name
assert_equal nil, column.sql_type
assert_equal nil, column.type
- assert_not column.number?
- assert_not column.text?
- assert_not column.binary?
end
- def test_non_existent_columns_are_identity_types
- column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ def test_non_existent_types_are_identity_types
+ type = @first.type_for_attribute("attribute_that_doesnt_exist")
object = Object.new
- assert_equal object, column.type_cast_from_database(object)
- assert_equal object, column.type_cast_from_user(object)
- assert_equal object, column.type_cast_for_database(object)
+ assert_equal object, type.deserialize(object)
+ assert_equal object, type.cast(object)
+ assert_equal object, type.serialize(object)
end
def test_reflection_klass_for_nested_class_name
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index eb76ef6328..0a2e874e4f 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -82,26 +82,15 @@ class RelationMergingTest < ActiveRecord::TestCase
left = Post.where(title: "omg").where(comments_count: 1)
right = Post.where(title: "wtf").where(title: "bbq")
- expected = [left.bind_values[1]] + right.bind_values
+ expected = [left.bound_attributes[1]] + right.bound_attributes
merged = left.merge(right)
- assert_equal expected, merged.bind_values
+ assert_equal expected, merged.bound_attributes
assert !merged.to_sql.include?("omg")
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
- def test_merging_keeps_lhs_bind_parameters
- column = Post.columns_hash['id']
- binds = [[column, 20]]
-
- right = Post.where(id: 20)
- left = Post.where(id: 10)
-
- merged = left.merge(right)
- assert_equal binds, merged.bind_values
- end
-
def test_merging_reorders_bind_params
post = Post.first
right = Post.where(id: 1)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 2443f10269..45ead08bd5 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -81,7 +81,7 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -90,7 +90,7 @@ module ActiveRecord
test '#from!' do
assert relation.from!('foo').equal?(relation)
- assert_equal ['foo', nil], relation.from_value
+ assert_equal 'foo', relation.from_clause.value
end
test '#lock!' do
@@ -136,12 +136,12 @@ module ActiveRecord
end
test 'test_merge!' do
- assert relation.merge!(where: :foo).equal?(relation)
- assert_equal [:foo], relation.where_values
+ assert relation.merge!(select: :foo).equal?(relation)
+ assert_equal [:foo], relation.select_values
end
test 'merge with a proc' do
- assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values
+ assert_equal [:foo], relation.merge(-> { select(:foo) }).select_values
end
test 'none!' do
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
new file mode 100644
index 0000000000..2006fc9611
--- /dev/null
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -0,0 +1,84 @@
+require "cases/helper"
+require 'models/post'
+
+module ActiveRecord
+ class OrTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_or_with_relation
+ expected = Post.where('id = 1 or id = 2').to_a
+ assert_equal expected, Post.where('id = 1').or(Post.where('id = 2')).to_a
+ end
+
+ def test_or_identity
+ expected = Post.where('id = 1').to_a
+ assert_equal expected, Post.where('id = 1').or(Post.where('id = 1')).to_a
+ end
+
+ def test_or_with_null_left
+ expected = Post.where('id = 1').to_a
+ assert_equal expected, Post.none.or(Post.where('id = 1')).to_a
+ end
+
+ def test_or_with_null_right
+ expected = Post.where('id = 1').to_a
+ assert_equal expected, Post.where('id = 1').or(Post.none).to_a
+ end
+
+ def test_or_with_bind_params
+ assert_equal Post.find([1, 2]), Post.where(id: 1).or(Post.where(id: 2)).to_a
+ end
+
+ def test_or_with_null_both
+ expected = Post.none.to_a
+ assert_equal expected, Post.none.or(Post.none).to_a
+ end
+
+ def test_or_without_left_where
+ expected = Post.all
+ assert_equal expected, Post.or(Post.where('id = 1')).to_a
+ end
+
+ def test_or_without_right_where
+ expected = Post.all
+ assert_equal expected, Post.where('id = 1').or(Post.all).to_a
+ end
+
+ def test_or_preserves_other_querying_methods
+ expected = Post.where('id = 1 or id = 2 or id = 3').order('body asc').to_a
+ partial = Post.order('body asc')
+ assert_equal expected, partial.where('id = 1').or(partial.where(:id => [2, 3])).to_a
+ assert_equal expected, Post.order('body asc').where('id = 1').or(Post.order('body asc').where(:id => [2, 3])).to_a
+ end
+
+ def test_or_with_incompatible_relations
+ assert_raises ArgumentError do
+ Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a
+ end
+ end
+
+ def test_or_when_grouping
+ groups = Post.where('id < 10').group('body').select('body, COUNT(*) AS c')
+ expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map {|o| [o.body, o.c] }
+ assert_equal expected, groups.having('COUNT(*) > 1').or(groups.having("body like 'Such%'")).to_a.map {|o| [o.body, o.c] }
+ end
+
+ def test_or_with_named_scope
+ expected = Post.where("id = 1 or body LIKE '\%a\%'").to_a
+ assert_equal expected, Post.where('id = 1').or(Post.containing_the_letter_a)
+ end
+
+ def test_or_inside_named_scope
+ expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order('id DESC').to_a
+ assert_equal expected, Post.order(id: :desc).typographically_interesting
+ end
+
+ def test_or_on_loaded_relation
+ expected = Post.where('id = 1 or id = 2').to_a
+ p = Post.where('id = 1')
+ p.load
+ assert_equal p.loaded?, true
+ assert_equal expected, p.or(Post.where('id = 2')).to_a
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 0f9019bb1b..27bbd80f79 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -11,22 +11,11 @@ module ActiveRecord
@name = 'title'
end
- def test_not_eq
+ def test_not_inverts_where_clause
relation = Post.where.not(title: 'hello')
+ expected_where_clause = Post.where(title: 'hello').where_clause.invert
- assert_equal 1, relation.where_values.length
-
- value = relation.where_values.first
- bind = relation.bind_values.first
-
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'hello', bind.last
- end
-
- def test_not_null
- expected = Post.arel_table[@name].not_eq(nil)
- relation = Post.where.not(title: nil)
- assert_equal([expected], relation.where_values)
+ assert_equal expected_where_clause, relation.where_clause
end
def test_not_with_nil
@@ -35,146 +24,81 @@ module ActiveRecord
end
end
- def test_not_in
- expected = Post.arel_table[@name].not_in(%w[hello goodbye])
- relation = Post.where.not(title: %w[hello goodbye])
- assert_equal([expected], relation.where_values)
- end
-
def test_association_not_eq
- expected = Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)
+ expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new))
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
- assert_equal(expected.to_sql, relation.where_values.first.to_sql)
+ assert_equal(expected.to_sql, relation.where_clause.ast.to_sql)
end
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
+ expected_where_clause =
+ Post.where(title: 'hello').where_clause +
+ Post.where(title: 'world').where_clause.invert
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'hello', bind.last
-
- value = relation.where_values.last
- bind = relation.bind_values.last
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'world', bind.last
+ assert_equal expected_where_clause, relation.where_clause
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
+ expected_where_clause =
+ Post.where(title: 'hello').where_clause.invert +
+ Post.where(title: 'world').where_clause
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'hello', bind.last
-
- value = relation.where_values.last
- bind = relation.bind_values.last
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'world', bind.last
- end
-
- def test_not_eq_with_string_parameter
- expected = Arel::Nodes::Not.new("title = 'hello'")
- relation = Post.where.not("title = 'hello'")
- assert_equal([expected], relation.where_values)
- end
-
- def test_not_eq_with_array_parameter
- expected = Arel::Nodes::Not.new("title = 'hello'")
- relation = Post.where.not(['title = ?', 'hello'])
- assert_equal([expected], relation.where_values)
+ assert_equal expected_where_clause, relation.where_clause
end
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
+ expected_where_clause =
+ Post.where(author_id: [1, 2]).where_clause.invert +
+ Post.where(title: 'ruby on rails').where_clause.invert
- expected = Post.arel_table['author_id'].not_in([1, 2])
- assert_equal(expected, relation.where_values[0])
-
- value = relation.where_values[1]
- bind = relation.bind_values.first
-
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'ruby on rails', bind.last
+ assert_equal expected_where_clause, relation.where_clause
end
def test_rewhere_with_one_condition
relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone')
+ expected = Post.where(title: 'alone')
- assert_equal 1, relation.where_values.size
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_multiple_overwriting_conditions
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again')
+ expected = Post.where(title: 'alone', body: 'again')
- assert_equal 2, relation.where_values.size
-
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
-
- value = relation.where_values[1]
- bind = relation.bind_values[1]
- assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
- assert_equal 'again', bind.last
- end
-
- def assert_bound_ast value, table, type
- assert_equal table, value.left
- assert_kind_of type, value
- assert_kind_of Arel::Nodes::BindParam, value.right
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_one_overwriting_condition_and_one_unrelated
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone')
+ expected = Post.where(body: 'world', title: 'alone')
- assert_equal 2, relation.where_values.size
-
- value = relation.where_values.first
- bind = relation.bind_values.first
-
- assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
- assert_equal 'world', bind.last
-
- value = relation.where_values.second
- bind = relation.bind_values.second
-
- assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_range
relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_upper_bound_range
relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_lower_bound_range
relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_range
relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
new file mode 100644
index 0000000000..c20ed94d90
--- /dev/null
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -0,0 +1,182 @@
+require "cases/helper"
+
+class ActiveRecord::Relation
+ class WhereClauseTest < ActiveRecord::TestCase
+ test "+ combines two where clauses" do
+ first_clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]])
+ second_clause = WhereClause.new([table["name"].eq(bind_param)], [["name", "Sean"]])
+ combined = WhereClause.new(
+ [table["id"].eq(bind_param), table["name"].eq(bind_param)],
+ [["id", 1], ["name", "Sean"]],
+ )
+
+ assert_equal combined, first_clause + second_clause
+ end
+
+ test "+ is associative, but not commutative" do
+ a = WhereClause.new(["a"], ["bind a"])
+ b = WhereClause.new(["b"], ["bind b"])
+ c = WhereClause.new(["c"], ["bind c"])
+
+ assert_equal a + (b + c), (a + b) + c
+ assert_not_equal a + b, b + a
+ end
+
+ test "an empty where clause is the identity value for +" do
+ clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]])
+
+ assert_equal clause, clause + WhereClause.empty
+ end
+
+ test "merge combines two where clauses" do
+ a = WhereClause.new([table["id"].eq(1)], [])
+ b = WhereClause.new([table["name"].eq("Sean")], [])
+ expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], [])
+
+ assert_equal expected, a.merge(b)
+ end
+
+ test "merge keeps the right side, when two equality clauses reference the same column" do
+ a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], [])
+ b = WhereClause.new([table["name"].eq("Jim")], [])
+ expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")], [])
+
+ assert_equal expected, a.merge(b)
+ end
+
+ test "merge removes bind parameters matching overlapping equality clauses" do
+ a = WhereClause.new(
+ [table["id"].eq(bind_param), table["name"].eq(bind_param)],
+ [attribute("id", 1), attribute("name", "Sean")],
+ )
+ b = WhereClause.new(
+ [table["name"].eq(bind_param)],
+ [attribute("name", "Jim")]
+ )
+ expected = WhereClause.new(
+ [table["id"].eq(bind_param), table["name"].eq(bind_param)],
+ [attribute("id", 1), attribute("name", "Jim")],
+ )
+
+ assert_equal expected, a.merge(b)
+ end
+
+ test "merge allows for columns with the same name from different tables" do
+ skip "This is not possible as of 4.2, and the binds do not yet contain sufficient information for this to happen"
+ # We might be able to change the implementation to remove conflicts by index, rather than column name
+ end
+
+ test "a clause knows if it is empty" do
+ assert WhereClause.empty.empty?
+ assert_not WhereClause.new(["anything"], []).empty?
+ end
+
+ test "invert cannot handle nil" do
+ where_clause = WhereClause.new([nil], [])
+
+ assert_raises ArgumentError do
+ where_clause.invert
+ end
+ end
+
+ test "invert replaces each part of the predicate with its inverse" do
+ random_object = Object.new
+ original = WhereClause.new([
+ table["id"].in([1, 2, 3]),
+ table["id"].eq(1),
+ "sql literal",
+ random_object
+ ], [])
+ expected = WhereClause.new([
+ table["id"].not_in([1, 2, 3]),
+ table["id"].not_eq(1),
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")),
+ Arel::Nodes::Not.new(random_object)
+ ], [])
+
+ assert_equal expected, original.invert
+ end
+
+ test "accept removes binary predicates referencing a given column" do
+ where_clause = WhereClause.new([
+ table["id"].in([1, 2, 3]),
+ table["name"].eq(bind_param),
+ table["age"].gteq(bind_param),
+ ], [
+ attribute("name", "Sean"),
+ attribute("age", 30),
+ ])
+ expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)])
+
+ assert_equal expected, where_clause.except("id", "name")
+ end
+
+ test "ast groups its predicates with AND" do
+ predicates = [
+ table["id"].in([1, 2, 3]),
+ table["name"].eq(bind_param),
+ ]
+ where_clause = WhereClause.new(predicates, [])
+ expected = Arel::Nodes::And.new(predicates)
+
+ assert_equal expected, where_clause.ast
+ end
+
+ test "ast wraps any SQL literals in parenthesis" do
+ random_object = Object.new
+ where_clause = WhereClause.new([
+ table["id"].in([1, 2, 3]),
+ "foo = bar",
+ random_object,
+ ], [])
+ expected = Arel::Nodes::And.new([
+ table["id"].in([1, 2, 3]),
+ Arel::Nodes::Grouping.new(Arel.sql("foo = bar")),
+ Arel::Nodes::Grouping.new(random_object),
+ ])
+
+ assert_equal expected, where_clause.ast
+ end
+
+ test "ast removes any empty strings" do
+ where_clause = WhereClause.new([table["id"].in([1, 2, 3])], [])
+ where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ''], [])
+
+ assert_equal where_clause.ast, where_clause_with_empty.ast
+ end
+
+ test "or joins the two clauses using OR" do
+ where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
+ other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")])
+ expected_ast =
+ Arel::Nodes::Grouping.new(
+ Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param))
+ )
+ expected_binds = where_clause.binds + other_clause.binds
+
+ assert_equal expected_ast.to_sql, where_clause.or(other_clause).ast.to_sql
+ assert_equal expected_binds, where_clause.or(other_clause).binds
+ end
+
+ test "or returns an empty where clause when either side is empty" do
+ where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
+
+ assert_equal WhereClause.empty, where_clause.or(WhereClause.empty)
+ assert_equal WhereClause.empty, WhereClause.empty.or(where_clause)
+ end
+
+ private
+
+ def table
+ Arel::Table.new("table")
+ end
+
+ def bind_param
+ Arel::Nodes::BindParam.new
+ end
+
+ def attribute(name, value)
+ ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new)
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 537937decd..6af31017d6 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -5,6 +5,7 @@ require "models/cake_designer"
require "models/chef"
require "models/comment"
require "models/edge"
+require "models/essay"
require "models/post"
require "models/price_estimate"
require "models/topic"
@@ -13,7 +14,7 @@ require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :binaries
+ fixtures :posts, :edges, :authors, :binaries, :essays
def test_where_copies_bind_params
author = authors(:david)
@@ -240,5 +241,40 @@ module ActiveRecord
count = Binary.where(:data => 0).count
assert_equal 0, count
end
+
+ def test_where_on_association_with_custom_primary_key
+ author = authors(:david)
+ essay = Essay.where(writer: author).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
+ def test_where_on_association_with_custom_primary_key_with_relation
+ author = authors(:david)
+ essay = Essay.where(writer: Author.where(id: author.id)).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
+ def test_where_on_association_with_relation_performs_subselect_not_two_queries
+ author = authors(:david)
+
+ assert_queries(1) do
+ Essay.where(writer: Author.where(id: author.id)).to_a
+ end
+ end
+
+ def test_where_on_association_with_custom_primary_key_with_array_of_base
+ author = authors(:david)
+ essay = Essay.where(writer: [author]).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
+ def test_where_on_association_with_custom_primary_key_with_array_of_ids
+ essay = Essay.where(writer: ["David"]).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index f7cb471984..9353be1ba7 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -156,35 +156,45 @@ module ActiveRecord
relation = Relation.new(FakeKlass, :b, nil)
relation = relation.merge where: :lol, readonly: true
- assert_equal [:lol], relation.where_values
+ assert_equal Relation::WhereClause.new([:lol], []), relation.where_clause
assert_equal true, relation.readonly_value
end
test 'merging an empty hash into a relation' do
- assert_equal [], Relation.new(FakeKlass, :b, nil).merge({}).where_values
+ assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause
end
test 'merging a hash with unknown keys raises' do
assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') }
end
+ test 'merging nil or false raises' do
+ relation = Relation.new(FakeKlass, :b, nil)
+
+ e = assert_raises(ArgumentError) do
+ relation = relation.merge nil
+ end
+
+ assert_equal 'invalid argument: nil.', e.message
+
+ e = assert_raises(ArgumentError) do
+ relation = relation.merge false
+ end
+
+ assert_equal 'invalid argument: false.', e.message
+ end
+
test '#values returns a dup of the values' do
relation = Relation.new(FakeKlass, :b, nil).where! :foo
values = relation.values
values[:where] = nil
- assert_not_nil relation.where_values
+ assert_not_nil relation.where_clause
end
test 'relations can be created with a values hash' do
- relation = Relation.new(FakeKlass, :b, nil, where: [:foo])
- assert_equal [:foo], relation.where_values
- end
-
- test 'merging a single where value' do
- relation = Relation.new(FakeKlass, :b, nil)
- relation.merge!(where: :foo)
- assert_equal [:foo], relation.where_values
+ relation = Relation.new(FakeKlass, :b, nil, select: [:foo])
+ assert_equal [:foo], relation.select_values
end
test 'merging a hash interpolates conditions' do
@@ -197,7 +207,7 @@ module ActiveRecord
relation = Relation.new(klass, :b, nil)
relation.merge!(where: ['foo = ?', 'bar'])
- assert_equal ['foo = bar'], relation.where_values
+ assert_equal Relation::WhereClause.new(['foo = bar'], []), relation.where_clause
end
def test_merging_readonly_false
@@ -244,12 +254,12 @@ module ActiveRecord
:string
end
- def type_cast_from_database(value)
+ def deserialize(value)
raise value unless value == "type cast for database"
"type cast from database"
end
- def type_cast_for_database(value)
+ def serialize(value)
raise value unless value == "value from user"
"type cast for database"
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 9631ea79be..dec3a37f42 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -16,6 +16,7 @@ require 'models/engine'
require 'models/tyre'
require 'models/minivan'
require 'models/aircraft'
+require "models/possession"
class RelationTest < ActiveRecord::TestCase
@@ -39,15 +40,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first
end
- def test_bind_values
- relation = Post.all
- assert_equal [], relation.bind_values
-
- relation2 = relation.bind 'foo'
- assert_equal %w{ foo }, relation2.bind_values
- assert_equal [], relation.bind_values
- end
-
def test_two_scopes_with_includes_should_not_drop_any_include
# heat habtm cache
car = Car.incl_engines.incl_tyres.first
@@ -350,7 +342,9 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 0, Developer.none.size
assert_equal 0, Developer.none.count
assert_equal true, Developer.none.empty?
+ assert_equal true, Developer.none.none?
assert_equal false, Developer.none.any?
+ assert_equal false, Developer.none.one?
assert_equal false, Developer.none.many?
end
end
@@ -857,6 +851,12 @@ class RelationTest < ActiveRecord::TestCase
assert ! fake.exists?(authors(:david).id)
end
+ def test_exists_uses_existing_scope
+ post = authors(:david).posts.first
+ authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
+ assert authors.exists?(authors(:david).id)
+ end
+
def test_last
authors = Author.all
assert_equal authors(:bob), authors.last
@@ -1104,6 +1104,38 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.limit(1).many?
end
+ def test_none?
+ posts = Post.all
+ assert_queries(1) do
+ assert ! posts.none? # Uses COUNT()
+ end
+
+ assert ! posts.loaded?
+
+ assert_queries(1) do
+ assert posts.none? {|p| p.id < 0 }
+ assert ! posts.none? {|p| p.id == 1 }
+ end
+
+ assert posts.loaded?
+ end
+
+ def test_one
+ posts = Post.all
+ assert_queries(1) do
+ assert ! posts.one? # Uses COUNT()
+ end
+
+ assert ! posts.loaded?
+
+ assert_queries(1) do
+ assert ! posts.one? {|p| p.id < 3 }
+ assert posts.one? {|p| p.id == 1 }
+ end
+
+ assert posts.loaded?
+ end
+
def test_build
posts = Post.all
@@ -1462,10 +1494,31 @@ class RelationTest < ActiveRecord::TestCase
def test_doesnt_add_having_values_if_options_are_blank
scope = Post.having('')
- assert_equal [], scope.having_values
+ assert scope.having_clause.empty?
scope = Post.having([])
- assert_equal [], scope.having_values
+ assert scope.having_clause.empty?
+ end
+
+ def test_having_with_binds_for_both_where_and_having
+ post = Post.first
+ having_then_where = Post.having(id: post.id).where(title: post.title).group(:id)
+ where_then_having = Post.where(title: post.title).having(id: post.id).group(:id)
+
+ assert_equal [post], having_then_where
+ assert_equal [post], where_then_having
+ end
+
+ def test_multiple_where_and_having_clauses
+ post = Post.first
+ having_then_where = Post.having(id: post.id).where(title: post.title)
+ .having(id: post.id).where(title: post.title).group(:id)
+
+ assert_equal [post], having_then_where
+ end
+
+ def test_grouping_by_column_with_reserved_name
+ assert_equal [], Possession.select(:where).group(:where).to_a
end
def test_references_triggers_eager_loading
@@ -1744,14 +1797,13 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_keeps_lhs_bind_parameters
- column = Post.columns_hash['id']
- binds = [[column, 20]]
+ binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))]
right = Post.where(id: 20)
left = Post.where(id: 10)
merged = left.merge(right)
- assert_equal binds, merged.bind_values
+ assert_equal binds, merged.bound_attributes
end
def test_merging_reorders_bind_params
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index c8ea702488..7d8d6421a9 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -73,10 +73,10 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid|point)\s+"/)
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|xml|uuid|point)\s+"/)
match[0].length
end
- end
+ end.compact
assert_equal 1, lengths.uniq.length
end
@@ -232,13 +232,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
- if mysql_56?
- def test_schema_dump_includes_datetime_precision
- output = standard_dump
- assert_match %r{t\.datetime\s+"written_on",\s+precision: 6$}, output
- end
- end
-
def test_schema_dump_includes_decimal_options
output = dump_all_table_schema([/^[^n]/])
assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
@@ -362,7 +355,7 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
teardown do
return unless @connection
- @connection.execute 'DROP TABLE defaults' if @connection.table_exists? 'defaults'
+ @connection.drop_table 'defaults', if_exists: true
end
def test_schema_dump_defaults_with_universally_supported_types
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 0738df1b54..4137b20c4a 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -284,8 +284,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_merging
merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where))
- assert merged.where_values.empty?
- assert !merged.where(name: "Jon").where_values.empty?
+ assert merged.where_clause.empty?
+ assert !merged.where(name: "Jon").where_clause.empty?
end
def test_order_in_default_scope_should_not_prevail
@@ -426,19 +426,19 @@ class DefaultScopingTest < ActiveRecord::TestCase
test "additional conditions are ANDed with the default scope" do
scope = DeveloperCalledJamis.where(name: "David")
- assert_equal 2, scope.where_values.length
+ assert_equal 2, scope.where_clause.ast.children.length
assert_equal [], scope.to_a
end
test "additional conditions in a scope are ANDed with the default scope" do
scope = DeveloperCalledJamis.david
- assert_equal 2, scope.where_values.length
+ assert_equal 2, scope.where_clause.ast.children.length
assert_equal [], scope.to_a
end
test "a scope can remove the condition from the default scope" do
scope = DeveloperCalledJamis.david2
- assert_equal 1, scope.where_values.length
- assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
+ assert_equal 1, scope.where_clause.ast.children.length
+ assert_equal Developer.where(name: "David"), scope
end
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 41f3449828..8cd94ebcc2 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -380,8 +380,8 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_should_not_duplicates_where_values
- where_values = Topic.where("1=1").scope_with_lambda.where_values
- assert_equal ["1=1"], where_values
+ relation = Topic.where("1=1")
+ assert_equal relation.where_clause, relation.scope_with_lambda.where_clause
end
def test_chaining_with_duplicate_joins
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index d7bcbf6203..4bfffbe9c6 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -184,7 +184,7 @@ class RelationScopingTest < ActiveRecord::TestCase
rescue
end
- assert !Developer.all.where_values.include?("name = 'Jamis'")
+ assert_not Developer.all.to_sql.include?("name = 'Jamis'"), "scope was not restored"
end
def test_default_scope_filters_on_joins
@@ -208,6 +208,12 @@ class RelationScopingTest < ActiveRecord::TestCase
assert_equal [], DeveloperFilteredOnJoins.all
assert_not_equal [], Developer.all
end
+
+ def test_current_scope_does_not_pollute_other_subclasses
+ Post.none.scoping do
+ assert StiPost.all.any?
+ end
+ end
end
class NestedRelationScopingTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb
index 3f7455d12d..e731443fc2 100644
--- a/activerecord/test/cases/secure_token_test.rb
+++ b/activerecord/test/cases/secure_token_test.rb
@@ -22,4 +22,11 @@ class SecureTokenTest < ActiveRecord::TestCase
assert_not_equal @user.token, old_token
assert_not_equal @user.auth_token, old_auth_token
end
+
+ def test_token_value_not_overwritten_when_present
+ @user.token = "custom-secure-token"
+ @user.save
+
+ assert_equal @user.token, "custom-secure-token"
+ end
end
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
new file mode 100644
index 0000000000..1c449d42fe
--- /dev/null
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -0,0 +1,21 @@
+require 'cases/helper'
+require 'models/notification'
+require 'models/user'
+
+class SuppressorTest < ActiveRecord::TestCase
+ def test_suppresses_creation_of_record_generated_by_callback
+ assert_difference -> { User.count } do
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress { UserWithNotification.create! }
+ end
+ end
+ end
+
+ def test_resumes_saving_after_suppression_complete
+ Notification.suppress { UserWithNotification.create! }
+
+ assert_difference -> { Notification.count } do
+ Notification.create!
+ end
+ end
+end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 5ba17359f0..e0b01ae8e0 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -1,10 +1,13 @@
require 'active_support/test_case'
+require 'active_support/testing/stream'
module ActiveRecord
# = Active Record Test Case
#
# Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
+ include ActiveSupport::Testing::Stream
+
def teardown
SQLCounter.clear_log
end
@@ -13,23 +16,6 @@ module ActiveRecord
assert_equal expected.to_s, actual.to_s, message
end
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
-
def capture_sql
SQLCounter.clear_log
yield
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
new file mode 100644
index 0000000000..ff4e5ecec5
--- /dev/null
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -0,0 +1,108 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+if ActiveRecord::Base.connection.supports_datetime_with_precision?
+class TimePrecisionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_fixtures = false
+
+ class Foo < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ teardown do
+ @connection.drop_table :foos, if_exists: true
+ end
+
+ def test_time_data_type_with_precision
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :start, :time, precision: 3
+ @connection.add_column :foos, :finish, :time, precision: 6
+ assert_equal 3, activerecord_column_option('foos', 'start', 'precision')
+ assert_equal 6, activerecord_column_option('foos', 'finish', 'precision')
+ end
+
+ def test_passing_precision_to_time_does_not_set_limit
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 3
+ t.time :finish, precision: 6
+ end
+ assert_nil activerecord_column_option('foos', 'start', 'limit')
+ assert_nil activerecord_column_option('foos', 'finish', 'limit')
+ end
+
+ def test_invalid_time_precision_raises_error
+ assert_raises ActiveRecord::ActiveRecordError do
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 7
+ t.time :finish, precision: 7
+ end
+ end
+ end
+
+ def test_database_agrees_with_activerecord_about_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 2
+ t.time :finish, precision: 4
+ end
+ assert_equal 2, database_datetime_precision('foos', 'start')
+ assert_equal 4, database_datetime_precision('foos', 'finish')
+ end
+
+ def test_formatting_time_according_to_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 0
+ t.time :finish, precision: 4
+ end
+ time = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
+ Foo.create!(start: time, finish: time)
+ assert foo = Foo.find_by(start: time)
+ assert_equal 1, Foo.where(finish: time).count
+ assert_equal time.to_s, foo.start.to_s
+ assert_equal time.to_s, foo.finish.to_s
+ assert_equal 000000, foo.start.usec
+ assert_equal 999900, foo.finish.usec
+ end
+
+ def test_schema_dump_includes_time_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 4
+ t.time :finish, precision: 6
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.time\s+"start",\s+precision: 4$}, output
+ assert_match %r{t\.time\s+"finish",\s+precision: 6$}, output
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_time_precision_with_zero_should_be_dumped
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 0
+ t.time :finish, precision: 0
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.time\s+"start",\s+precision: 0$}, output
+ assert_match %r{t\.time\s+"finish",\s+precision: 0$}, output
+ end
+ end
+
+ private
+
+ def database_datetime_precision(table_name, column_name)
+ results = @connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
+ result = results.find do |result_hash|
+ result_hash["column_name"] == column_name
+ end
+ result && result["datetime_precision"].to_i
+ end
+
+ def activerecord_column_option(tablename, column_name, option)
+ result = @connection.columns(tablename).find do |column|
+ column.name == column_name
+ end
+ result && result.send(option)
+ end
+end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index db474c63a4..c0c62527df 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -73,6 +73,15 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal @previously_updated_at, @developer.updated_at
end
+ def test_touching_updates_timestamp_with_given_time
+ previously_updated_at = @developer.updated_at
+ new_time = Time.utc(2015, 2, 16, 0, 0, 0)
+ @developer.touch(time: new_time)
+
+ assert_not_equal previously_updated_at, @developer.updated_at
+ assert_equal new_time, @developer.updated_at
+ end
+
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
@developer.touch(:created_at)
@@ -91,6 +100,18 @@ class TimestampTest < ActiveRecord::TestCase
assert_in_delta Time.now, task.ending, 1
end
+ def test_touching_an_attribute_updates_timestamp_with_given_time
+ previously_updated_at = @developer.updated_at
+ previously_created_at = @developer.created_at
+ new_time = Time.utc(2015, 2, 16, 4, 54, 0)
+ @developer.touch(:created_at, time: new_time)
+
+ assert_not_equal previously_created_at, @developer.created_at
+ assert_not_equal previously_updated_at, @developer.updated_at
+ assert_equal new_time, @developer.created_at
+ assert_equal new_time, @developer.updated_at
+ end
+
def test_touching_many_attributes_updates_them
task = Task.first
previous_starting = task.starting
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 185fc22e98..f185cda263 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -4,7 +4,6 @@ require 'models/pet'
require 'models/topic'
class TransactionCallbacksTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
fixtures :topics, :owners, :pets
class ReplyWithCallbacks < ActiveRecord::Base
@@ -200,21 +199,21 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_call_after_rollback_when_commit_fails
- @first.class.connection.singleton_class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
- begin
- @first.class.connection.singleton_class.class_eval do
- def commit_db_transaction; raise "boom!"; end
- end
+ @first.after_commit_block { |r| r.history << :after_commit }
+ @first.after_rollback_block { |r| r.history << :after_rollback }
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
+ assert_raises RuntimeError do
+ @first.transaction do
+ tx = @first.class.connection.transaction_manager.current_transaction
+ def tx.commit
+ raise
+ end
- assert !@first.save rescue nil
- assert_equal [:after_rollback], @first.history
- ensure
- @first.class.connection.singleton_class.send(:remove_method, :commit_db_transaction)
- @first.class.connection.singleton_class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
+ @first.save
+ end
end
+
+ assert_equal [:after_rollback], @first.history
end
def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index d1d8e71c34..88e595c39f 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -668,7 +668,7 @@ class TransactionTest < ActiveRecord::TestCase
end
end
ensure
- connection.execute("DROP TABLE IF EXISTS transaction_without_primary_keys")
+ connection.drop_table 'transaction_without_primary_keys', if_exists: true
end
private
diff --git a/activerecord/test/cases/type/adapter_specific_registry_test.rb b/activerecord/test/cases/type/adapter_specific_registry_test.rb
new file mode 100644
index 0000000000..8b836b4793
--- /dev/null
+++ b/activerecord/test/cases/type/adapter_specific_registry_test.rb
@@ -0,0 +1,133 @@
+require "cases/helper"
+
+module ActiveRecord
+ class AdapterSpecificRegistryTest < ActiveRecord::TestCase
+ test "a class can be registered for a symbol" do
+ registry = Type::AdapterSpecificRegistry.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::AdapterSpecificRegistry.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 "filtering by adapter" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String, adapter: :sqlite3)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ assert_equal [], registry.lookup(:foo, adapter: :postgresql)
+ end
+
+ test "an error is raised if both a generic and adapter specific type match" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_raises TypeConflictError do
+ registry.lookup(:foo, adapter: :postgresql)
+ end
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ end
+
+ test "a generic type can explicitly override an adapter specific type" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String, override: true)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_equal "", registry.lookup(:foo, adapter: :postgresql)
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ end
+
+ test "a generic type can explicitly allow an adapter type to be used instead" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String, override: false)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_equal [], registry.lookup(:foo, adapter: :postgresql)
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ end
+
+ test "a reasonable error is given when no type is found" do
+ registry = Type::AdapterSpecificRegistry.new
+
+ e = assert_raises(ArgumentError) do
+ registry.lookup(:foo)
+ end
+
+ assert_equal "Unknown type :foo", e.message
+ end
+
+ test "construct args are passed to the type" do
+ type = Struct.new(:args)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, type)
+
+ assert_equal type.new, registry.lookup(:foo)
+ assert_equal type.new(:ordered_arg), registry.lookup(:foo, :ordered_arg)
+ assert_equal type.new(keyword: :arg), registry.lookup(:foo, keyword: :arg)
+ assert_equal type.new(keyword: :arg), registry.lookup(:foo, keyword: :arg, adapter: :postgresql)
+ end
+
+ test "registering a modifier" do
+ decoration = Struct.new(:value)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String)
+ registry.register(:bar, Hash)
+ registry.add_modifier({ array: true }, decoration)
+
+ assert_equal decoration.new(""), registry.lookup(:foo, array: true)
+ assert_equal decoration.new({}), registry.lookup(:bar, array: true)
+ assert_equal "", registry.lookup(:foo)
+ end
+
+ test "registering multiple modifiers" do
+ decoration = Struct.new(:value)
+ other_decoration = Struct.new(:value)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String)
+ registry.add_modifier({ array: true }, decoration)
+ registry.add_modifier({ range: true }, other_decoration)
+
+ assert_equal "", registry.lookup(:foo)
+ assert_equal decoration.new(""), registry.lookup(:foo, array: true)
+ assert_equal other_decoration.new(""), registry.lookup(:foo, range: true)
+ assert_equal(
+ decoration.new(other_decoration.new("")),
+ registry.lookup(:foo, array: true, range: true)
+ )
+ end
+
+ test "registering adapter specific modifiers" do
+ decoration = Struct.new(:value)
+ type = Struct.new(:args)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, type)
+ registry.add_modifier({ array: true }, decoration, adapter: :postgresql)
+
+ assert_equal(
+ decoration.new(type.new(keyword: :arg)),
+ registry.lookup(:foo, array: true, adapter: :postgresql, keyword: :arg)
+ )
+ assert_equal(
+ type.new(array: true),
+ registry.lookup(:foo, array: true, adapter: :sqlite3)
+ )
+ end
+ end
+end
diff --git a/activerecord/test/cases/type/decimal_test.rb b/activerecord/test/cases/type/decimal_test.rb
index 34ed1d7b19..fe49d0e79a 100644
--- a/activerecord/test/cases/type/decimal_test.rb
+++ b/activerecord/test/cases/type/decimal_test.rb
@@ -5,29 +5,29 @@ module ActiveRecord
class DecimalTest < ActiveRecord::TestCase
def test_type_cast_decimal
type = Decimal.new
- assert_equal BigDecimal.new("0"), type.type_cast_from_user(BigDecimal.new("0"))
- assert_equal BigDecimal.new("123"), type.type_cast_from_user(123.0)
- assert_equal BigDecimal.new("1"), type.type_cast_from_user(:"1")
+ assert_equal BigDecimal.new("0"), type.cast(BigDecimal.new("0"))
+ assert_equal BigDecimal.new("123"), type.cast(123.0)
+ assert_equal BigDecimal.new("1"), type.cast(:"1")
end
def test_type_cast_decimal_from_float_with_large_precision
type = Decimal.new(precision: ::Float::DIG + 2)
- assert_equal BigDecimal.new("123.0"), type.type_cast_from_user(123.0)
+ assert_equal BigDecimal.new("123.0"), type.cast(123.0)
end
def test_type_cast_from_float_with_unspecified_precision
type = Decimal.new
- assert_equal 22.68.to_d, type.type_cast_from_user(22.68)
+ assert_equal 22.68.to_d, type.cast(22.68)
end
def test_type_cast_decimal_from_rational_with_precision
type = Decimal.new(precision: 2)
- assert_equal BigDecimal("0.33"), type.type_cast_from_user(Rational(1, 3))
+ assert_equal BigDecimal("0.33"), type.cast(Rational(1, 3))
end
def test_type_cast_decimal_from_rational_without_precision_defaults_to_18_36
type = Decimal.new
- assert_equal BigDecimal("0.333333333333333333E0"), type.type_cast_from_user(Rational(1, 3))
+ assert_equal BigDecimal("0.333333333333333333E0"), type.cast(Rational(1, 3))
end
def test_type_cast_decimal_from_object_responding_to_d
@@ -36,7 +36,7 @@ module ActiveRecord
BigDecimal.new("1")
end
type = Decimal.new
- assert_equal BigDecimal("1"), type.type_cast_from_user(value)
+ assert_equal BigDecimal("1"), type.cast(value)
end
def test_changed?
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index ff956b7680..84fb05dd8e 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -6,45 +6,45 @@ module ActiveRecord
class IntegerTest < ActiveRecord::TestCase
test "simple values" do
type = Type::Integer.new
- assert_equal 1, type.type_cast_from_user(1)
- assert_equal 1, type.type_cast_from_user('1')
- assert_equal 1, type.type_cast_from_user('1ignore')
- assert_equal 0, type.type_cast_from_user('bad1')
- assert_equal 0, type.type_cast_from_user('bad')
- assert_equal 1, type.type_cast_from_user(1.7)
- assert_equal 0, type.type_cast_from_user(false)
- assert_equal 1, type.type_cast_from_user(true)
- assert_nil type.type_cast_from_user(nil)
+ 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.type_cast_from_user([1,2])
- assert_nil type.type_cast_from_user({1 => 2})
- assert_nil type.type_cast_from_user((1..2))
+ 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.type_cast_from_user(firm)
+ assert_nil type.cast(firm)
end
test "casting objects without to_i" do
type = Type::Integer.new
- assert_nil type.type_cast_from_user(::Object.new)
+ assert_nil type.cast(::Object.new)
end
test "casting nan and infinity" do
type = Type::Integer.new
- assert_nil type.type_cast_from_user(::Float::NAN)
- assert_nil type.type_cast_from_user(1.0/0.0)
+ 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.type_cast_for_database(true)
- assert_equal 0, type.type_cast_for_database(false)
+ assert_equal 1, type.serialize(true)
+ assert_equal 0, type.serialize(false)
end
test "changed?" do
@@ -60,55 +60,68 @@ module ActiveRecord
test "values below int min value are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("-2147483649")
+ Integer.new.serialize(-2147483649)
end
end
test "values above int max value are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("2147483648")
+ Integer.new.serialize(2147483648)
end
end
test "very small numbers are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("-9999999999999999999999999999999")
+ Integer.new.serialize(-9999999999999999999999999999999)
end
end
test "very large numbers are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("9999999999999999999999999999999")
+ Integer.new.serialize(9999999999999999999999999999999)
end
end
test "normal numbers are in range" do
type = Integer.new
- assert_equal(0, type.type_cast_from_user("0"))
- assert_equal(-1, type.type_cast_from_user("-1"))
- assert_equal(1, type.type_cast_from_user("1"))
+ 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.type_cast_from_user("2147483647"))
+ assert_equal(2147483647, Integer.new.serialize(2147483647))
end
test "int min value is in range" do
- assert_equal(-2147483648, Integer.new.type_cast_from_user("-2147483648"))
+ 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.type_cast_from_user("9223372036854775807"))
- assert_equal(-9223372036854775808, type.type_cast_from_user("-9223372036854775808"))
+ assert_equal(9223372036854775807, type.serialize(9223372036854775807))
+ assert_equal(-9223372036854775808, type.serialize(-9223372036854775808))
assert_raises(::RangeError) do
- type.type_cast_from_user("-9999999999999999999999999999999")
+ type.serialize(-9999999999999999999999999999999)
end
assert_raises(::RangeError) do
- type.type_cast_from_user("9999999999999999999999999999999")
+ 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'
+ attribute :foo, :integer
+ end
+ model = klass.new
+
+ model.foo = 2147483648
+ model.foo = 1
+
+ assert_equal 1, model.foo
+ end
end
end
end
diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb
index 4d78f287f1..56e9bf434d 100644
--- a/activerecord/test/cases/type/string_test.rb
+++ b/activerecord/test/cases/type/string_test.rb
@@ -4,16 +4,16 @@ module ActiveRecord
class StringTypeTest < ActiveRecord::TestCase
test "type casting" do
type = Type::String.new
- assert_equal "t", type.type_cast_from_user(true)
- assert_equal "f", type.type_cast_from_user(false)
- assert_equal "123", type.type_cast_from_user(123)
+ 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.type_cast_from_user(s)
- assert_not_same s, type.type_cast_from_database(s)
+ assert_not_same s, type.cast(s)
+ assert_not_same s, type.deserialize(s)
end
test "string mutations are detected" do
diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb
index b6f673109e..f2c910eade 100644
--- a/activerecord/test/cases/type/unsigned_integer_test.rb
+++ b/activerecord/test/cases/type/unsigned_integer_test.rb
@@ -4,12 +4,12 @@ module ActiveRecord
module Type
class UnsignedIntegerTest < ActiveRecord::TestCase
test "unsigned int max value is in range" do
- assert_equal(4294967295, UnsignedInteger.new.type_cast_from_user("4294967295"))
+ assert_equal(4294967295, UnsignedInteger.new.serialize(4294967295))
end
test "minus value is out of range" do
assert_raises(::RangeError) do
- UnsignedInteger.new.type_cast_from_user("-1")
+ UnsignedInteger.new.serialize(-1)
end
end
end
diff --git a/activerecord/test/cases/type_test.rb b/activerecord/test/cases/type_test.rb
new file mode 100644
index 0000000000..d45a9b3141
--- /dev/null
+++ b/activerecord/test/cases/type_test.rb
@@ -0,0 +1,39 @@
+require "cases/helper"
+
+class TypeTest < ActiveRecord::TestCase
+ setup do
+ @old_registry = ActiveRecord::Type.registry
+ ActiveRecord::Type.registry = ActiveRecord::Type::AdapterSpecificRegistry.new
+ end
+
+ teardown do
+ ActiveRecord::Type.registry = @old_registry
+ end
+
+ test "registering a new type" do
+ type = Struct.new(:args)
+ ActiveRecord::Type.register(:foo, type)
+
+ assert_equal type.new(:arg), ActiveRecord::Type.lookup(:foo, :arg)
+ end
+
+ test "looking up a type for a specific adapter" do
+ type = Struct.new(:args)
+ pgtype = Struct.new(:args)
+ ActiveRecord::Type.register(:foo, type, override: false)
+ ActiveRecord::Type.register(:foo, pgtype, adapter: :postgresql)
+
+ assert_equal type.new, ActiveRecord::Type.lookup(:foo, adapter: :sqlite)
+ assert_equal pgtype.new, ActiveRecord::Type.lookup(:foo, adapter: :postgresql)
+ end
+
+ test "lookup defaults to the current adapter" do
+ current_adapter = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
+ type = Struct.new(:args)
+ adapter_type = Struct.new(:args)
+ ActiveRecord::Type.register(:foo, type, override: false)
+ ActiveRecord::Type.register(:foo, adapter_type, adapter: current_adapter)
+
+ assert_equal adapter_type.new, ActiveRecord::Type.lookup(:foo)
+ end
+end
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index 73e92addfe..9b1859c2ce 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -5,38 +5,38 @@ module ActiveRecord
class TypesTest < ActiveRecord::TestCase
def test_type_cast_boolean
type = Type::Boolean.new
- assert type.type_cast_from_user('').nil?
- assert type.type_cast_from_user(nil).nil?
-
- assert type.type_cast_from_user(true)
- assert type.type_cast_from_user(1)
- assert type.type_cast_from_user('1')
- assert type.type_cast_from_user('t')
- assert type.type_cast_from_user('T')
- assert type.type_cast_from_user('true')
- assert type.type_cast_from_user('TRUE')
- assert type.type_cast_from_user('on')
- assert type.type_cast_from_user('ON')
- assert type.type_cast_from_user(' ')
- assert type.type_cast_from_user("\u3000\r\n")
- assert type.type_cast_from_user("\u0000")
- assert type.type_cast_from_user('SOMETHING RANDOM')
+ 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.type_cast_from_user(false)
- assert_equal false, type.type_cast_from_user(0)
- assert_equal false, type.type_cast_from_user('0')
- assert_equal false, type.type_cast_from_user('f')
- assert_equal false, type.type_cast_from_user('F')
- assert_equal false, type.type_cast_from_user('false')
- assert_equal false, type.type_cast_from_user('FALSE')
- assert_equal false, type.type_cast_from_user('off')
- assert_equal false, type.type_cast_from_user('OFF')
+ 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.type_cast_from_user("1")
+ assert_equal 1.0, type.cast("1")
end
def test_changing_float
@@ -50,54 +50,54 @@ module ActiveRecord
def test_type_cast_binary
type = Type::Binary.new
- assert_equal nil, type.type_cast_from_user(nil)
- assert_equal "1", type.type_cast_from_user("1")
- assert_equal 1, type.type_cast_from_user(1)
+ 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.type_cast_from_user(nil)
- assert_equal nil, type.type_cast_from_user('')
- assert_equal nil, type.type_cast_from_user('ABC')
+ 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.type_cast_from_user(time_string).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.type_cast_from_user(nil)
- assert_equal nil, type.type_cast_from_user('')
- assert_equal nil, type.type_cast_from_user(' ')
- assert_equal nil, type.type_cast_from_user('ABC')
+ 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.type_cast_from_user(datetime_string).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.type_cast_from_user(nil)
- assert_equal nil, type.type_cast_from_user('')
- assert_equal nil, type.type_cast_from_user(' ')
- assert_equal nil, type.type_cast_from_user('ABC')
+ 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.type_cast_from_user(date_string).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.type_cast_from_user(30.minutes)
- assert_equal 7200, type.type_cast_from_user(2.hours)
+ 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.type_cast_from_user("Wed, 04 Sep 2013 03:00:00 EAT")
+ assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
end
end
end
@@ -108,14 +108,21 @@ module ActiveRecord
assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
end
- if current_adapter?(:SQLite3Adapter)
- def test_binary_encoding
- type = SQLite3Binary.new
- utf8_string = "a string".encode(Encoding::UTF_8)
- type_cast = type.type_cast_from_user(utf8_string)
-
- assert_equal Encoding::ASCII_8BIT, type_cast.encoding
+ 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(*)
+ raise
+ end
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'posts'
+ attribute :foo, type_which_cannot_go_to_the_database
end
+ model = klass.new
+
+ model.foo = "foo"
+ model.foo = "bar"
+
+ assert_equal "bar", model.foo
end
end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index e4edc437e6..bff5ffa65e 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -50,7 +50,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
Topic.validates_presence_of :content
r = Reply.create("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
+ assert_not_operator r, :valid?
assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic]
end
@@ -82,5 +82,4 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated"
end
end
-
end
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 2c0e282761..952e1681a7 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'
@@ -37,7 +36,7 @@ class LengthValidationTest < ActiveRecord::TestCase
def test_validates_size_of_association_utf8
repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ Owner.validates_size_of :pets, :minimum => 1
o = Owner.new('name' => 'あいうえおかきくけこ')
assert !o.save
assert o.errors[:pets].any?
@@ -46,8 +45,8 @@ class LengthValidationTest < ActiveRecord::TestCase
end
end
- def test_validates_size_of_reprects_records_marked_for_destruction
- assert_nothing_raised { Owner.validates_size_of :pets, minimum: 1 }
+ def test_validates_size_of_respects_records_marked_for_destruction
+ Owner.validates_size_of :pets, minimum: 1
owner = Owner.new
assert_not owner.save
assert owner.errors[:pets].any?
@@ -62,4 +61,17 @@ class LengthValidationTest < ActiveRecord::TestCase
assert_equal pet_count, Pet.count
end
+ def test_does_not_validate_length_of_if_parent_record_is_validate_false
+ Owner.validates_length_of :name, minimum: 1
+ owner = Owner.new
+ owner.save!(validate: false)
+ assert owner.persisted?
+
+ pet = Pet.new(owner_id: owner.id)
+ pet.save!
+
+ assert_equal owner.pets.size, 1
+ assert owner.valid?
+ assert pet.valid?
+ end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 4f38849131..6f8ad06ab6 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/man'
require 'models/face'
@@ -65,4 +64,20 @@ class PresenceValidationTest < ActiveRecord::TestCase
assert_nothing_raised { s.valid? }
end
+
+ def test_does_not_validate_presence_of_if_parent_record_is_validate_false
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:topic)
+ interest = Interest.new
+ interest.save!(validate: false)
+ assert interest.persisted?
+
+ man = Man.new(interest_ids: [interest.id])
+ man.save!
+
+ assert_equal man.interests.size, 1
+ assert interest.valid?
+ assert man.valid?
+ end
+ end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 524f59876e..062bc733f9 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/topic'
require 'models/reply'
@@ -386,4 +385,21 @@ class UniquenessValidationTest < ActiveRecord::TestCase
topic = TopicWithUniqEvent.new
assert topic.valid?
end
+
+ def test_does_not_validate_uniqueness_of_if_parent_record_is_validate_false
+ Reply.validates_uniqueness_of(:content)
+
+ Reply.create!(content: "Topic Title")
+
+ reply = Reply.new(content: "Topic Title")
+ reply.save!(validate: false)
+ assert reply.persisted?
+
+ topic = Topic.new(reply_ids: [reply.id])
+ topic.save!
+
+ assert_equal topic.replies.size, 1
+ assert reply.valid?
+ assert topic.valid?
+ end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 959c58aa85..f4f316f393 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/topic'
require 'models/reply'
@@ -151,7 +150,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_numericality_validation_with_mutation
Topic.class_eval do
- attribute :wibble, ActiveRecord::Type::String.new
+ attribute :wibble, :string
validates_numericality_of :wibble, only_integer: true
end
diff --git a/activerecord/test/fixtures/dead_parrots.yml b/activerecord/test/fixtures/dead_parrots.yml
new file mode 100644
index 0000000000..da5529dd27
--- /dev/null
+++ b/activerecord/test/fixtures/dead_parrots.yml
@@ -0,0 +1,5 @@
+deadbird:
+ name: "Dusty DeadBird"
+ treasures: [ruby, sapphire]
+ parrot_sti_class: DeadParrot
+ killer: blackbeard
diff --git a/activerecord/test/fixtures/live_parrots.yml b/activerecord/test/fixtures/live_parrots.yml
new file mode 100644
index 0000000000..95b2078da7
--- /dev/null
+++ b/activerecord/test/fixtures/live_parrots.yml
@@ -0,0 +1,4 @@
+dusty:
+ name: "Dusty Bluebird"
+ treasures: [ruby, sapphire]
+ parrot_sti_class: LiveParrot
diff --git a/activerecord/test/migrations/missing/1000_people_have_middle_names.rb b/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
index 9fd495b97c..4b83d61beb 100644
--- a/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
+++ b/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
@@ -6,4 +6,4 @@ class PeopleHaveMiddleNames < ActiveRecord::Migration
def self.down
remove_column "people", "middle_name"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/missing/1_people_have_last_names.rb b/activerecord/test/migrations/missing/1_people_have_last_names.rb
index 81af5fef5e..68209f3ce9 100644
--- a/activerecord/test/migrations/missing/1_people_have_last_names.rb
+++ b/activerecord/test/migrations/missing/1_people_have_last_names.rb
@@ -6,4 +6,4 @@ class PeopleHaveLastNames < ActiveRecord::Migration
def self.down
remove_column "people", "last_name"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/missing/3_we_need_reminders.rb b/activerecord/test/migrations/missing/3_we_need_reminders.rb
index d5e71ce8ef..25bb49cb32 100644
--- a/activerecord/test/migrations/missing/3_we_need_reminders.rb
+++ b/activerecord/test/migrations/missing/3_we_need_reminders.rb
@@ -9,4 +9,4 @@ class WeNeedReminders < ActiveRecord::Migration
def self.down
drop_table "reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/missing/4_innocent_jointable.rb b/activerecord/test/migrations/missing/4_innocent_jointable.rb
index 21c9ca5328..002a1bf2a6 100644
--- a/activerecord/test/migrations/missing/4_innocent_jointable.rb
+++ b/activerecord/test/migrations/missing/4_innocent_jointable.rb
@@ -9,4 +9,4 @@ class InnocentJointable < ActiveRecord::Migration
def self.down
drop_table "people_reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/rename/1_we_need_things.rb b/activerecord/test/migrations/rename/1_we_need_things.rb
index cdbe0b1679..f5484ac54f 100644
--- a/activerecord/test/migrations/rename/1_we_need_things.rb
+++ b/activerecord/test/migrations/rename/1_we_need_things.rb
@@ -8,4 +8,4 @@ class WeNeedThings < ActiveRecord::Migration
def self.down
drop_table "things"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/rename/2_rename_things.rb b/activerecord/test/migrations/rename/2_rename_things.rb
index d441b71fc9..533a113ea8 100644
--- a/activerecord/test/migrations/rename/2_rename_things.rb
+++ b/activerecord/test/migrations/rename/2_rename_things.rb
@@ -6,4 +6,4 @@ class RenameThings < ActiveRecord::Migration
def self.down
rename_table "awesome_things", "things"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid/2_we_need_reminders.rb b/activerecord/test/migrations/valid/2_we_need_reminders.rb
index d5e71ce8ef..25bb49cb32 100644
--- a/activerecord/test/migrations/valid/2_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid/2_we_need_reminders.rb
@@ -9,4 +9,4 @@ class WeNeedReminders < ActiveRecord::Migration
def self.down
drop_table "reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid/3_innocent_jointable.rb b/activerecord/test/migrations/valid/3_innocent_jointable.rb
index 21c9ca5328..002a1bf2a6 100644
--- a/activerecord/test/migrations/valid/3_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid/3_innocent_jointable.rb
@@ -9,4 +9,4 @@ class InnocentJointable < ActiveRecord::Migration
def self.down
drop_table "people_reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
index d5e71ce8ef..25bb49cb32 100644
--- a/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
@@ -9,4 +9,4 @@ class WeNeedReminders < ActiveRecord::Migration
def self.down
drop_table "reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
index 21c9ca5328..002a1bf2a6 100644
--- a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
@@ -9,4 +9,4 @@ class InnocentJointable < ActiveRecord::Migration
def self.down
drop_table "people_reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/admin.rb b/activerecord/test/models/admin.rb
index 00e69fbed8..a38e3f4846 100644
--- a/activerecord/test/models/admin.rb
+++ b/activerecord/test/models/admin.rb
@@ -2,4 +2,4 @@ module Admin
def self.table_name_prefix
'admin_'
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/admin/account.rb b/activerecord/test/models/admin/account.rb
index 46de28aae1..bd23192d20 100644
--- a/activerecord/test/models/admin/account.rb
+++ b/activerecord/test/models/admin/account.rb
@@ -1,3 +1,3 @@
class Admin::Account < ActiveRecord::Base
has_many :users
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/admin/randomly_named_c1.rb b/activerecord/test/models/admin/randomly_named_c1.rb
index 2f81d5b831..b64ae7fc41 100644
--- a/activerecord/test/models/admin/randomly_named_c1.rb
+++ b/activerecord/test/models/admin/randomly_named_c1.rb
@@ -1,3 +1,7 @@
-class Admin::ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
- self.table_name = :randomly_named_table
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS1 < ActiveRecord::Base
+ self.table_name = :randomly_named_table2
+end
+
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS2 < ActiveRecord::Base
+ self.table_name = :randomly_named_table3
end
diff --git a/activerecord/test/models/binary.rb b/activerecord/test/models/binary.rb
index 950c459199..39b2f5090a 100644
--- a/activerecord/test/models/binary.rb
+++ b/activerecord/test/models/binary.rb
@@ -1,2 +1,2 @@
class Binary < ActiveRecord::Base
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index db0f93f63b..81263b79d1 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -8,7 +8,7 @@ class Car < ActiveRecord::Base
has_one :bulb
has_many :tyres
- has_many :engines, :dependent => :destroy
+ has_many :engines, :dependent => :destroy, inverse_of: :my_car
has_many :wheels, :as => :wheelable, :dependent => :destroy
scope :incl_tyres, -> { includes(:tyres) }
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 5a56616eb9..6961f8fd6f 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -214,7 +214,7 @@ class Account < ActiveRecord::Base
protected
def check_empty_credit_limit
- errors.add_on_empty "credit_limit"
+ errors.add("credit_limit", :blank) if credit_limit.blank?
end
private
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index dae102d12b..bf0a0d1c3e 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -91,7 +91,7 @@ module MyApplication
protected
def check_empty_credit_limit
- errors.add_on_empty "credit_limit"
+ errors.add("credit_card", :blank) if credit_card.blank?
end
end
end
diff --git a/activerecord/test/models/event.rb b/activerecord/test/models/event.rb
index 99fa0feeb7..365ab32b0b 100644
--- a/activerecord/test/models/event.rb
+++ b/activerecord/test/models/event.rb
@@ -1,3 +1,3 @@
class Event < ActiveRecord::Base
validates_uniqueness_of :title
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/guid.rb b/activerecord/test/models/guid.rb
index 9208dc28fa..05653ba498 100644
--- a/activerecord/test/models/guid.rb
+++ b/activerecord/test/models/guid.rb
@@ -1,2 +1,2 @@
class Guid < ActiveRecord::Base
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb
new file mode 100644
index 0000000000..b4b4b8f1b6
--- /dev/null
+++ b/activerecord/test/models/notification.rb
@@ -0,0 +1,2 @@
+class Notification < ActiveRecord::Base
+end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 366c70f902..30545bdcd7 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -89,4 +89,4 @@ class FamousPirate < ActiveRecord::Base
self.table_name = 'pirates'
has_many :famous_ships
validates_presence_of :catchphrase, on: :conference
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 7b637c9e3f..052b1c9690 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -18,6 +18,7 @@ class Post < ActiveRecord::Base
end
scope :containing_the_letter_a, -> { where("body LIKE '%a%'") }
+ scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") }
scope :ranked_by_comments, -> { order("comments_count DESC") }
scope :limit_by, lambda {|l| limit(l) }
@@ -43,6 +44,8 @@ class Post < ActiveRecord::Base
scope :tagged_with, ->(id) { joins(:taggings).where(taggings: { tag_id: id }) }
scope :tagged_with_comment, ->(comment) { joins(:taggings).where(taggings: { comment: comment }) }
+ scope :typographically_interesting, -> { containing_the_letter_a.or(titled_with_an_apostrophe) }
+
has_many :comments do
def find_most_recent
order("id DESC").first
diff --git a/activerecord/test/models/randomly_named_c1.rb b/activerecord/test/models/randomly_named_c1.rb
index 18a86c4989..d4be1e13b4 100644
--- a/activerecord/test/models/randomly_named_c1.rb
+++ b/activerecord/test/models/randomly_named_c1.rb
@@ -1,3 +1,3 @@
class ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
- self.table_name = :randomly_named_table
+ self.table_name = :randomly_named_table1
end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index c2f6d492d8..312caef604 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -4,6 +4,7 @@ class Ship < ActiveRecord::Base
belongs_to :pirate
belongs_to :update_only_pirate, :class_name => 'Pirate'
has_many :parts, :class_name => 'ShipPart'
+ has_many :treasures
accepts_nested_attributes_for :parts, :allow_destroy => true
accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
diff --git a/activerecord/test/models/ship_part.rb b/activerecord/test/models/ship_part.rb
index b6a8a506b4..05c65f8a4a 100644
--- a/activerecord/test/models/ship_part.rb
+++ b/activerecord/test/models/ship_part.rb
@@ -2,6 +2,7 @@ class ShipPart < ActiveRecord::Base
belongs_to :ship
has_many :trinkets, :class_name => "Treasure", :as => :looter
accepts_nested_attributes_for :trinkets, :allow_destroy => true
+ accepts_nested_attributes_for :ship
validates_presence_of :name
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb
index a69d3fd3df..ffc65466d5 100644
--- a/activerecord/test/models/treasure.rb
+++ b/activerecord/test/models/treasure.rb
@@ -1,6 +1,7 @@
class Treasure < ActiveRecord::Base
has_and_belongs_to_many :parrots
belongs_to :looter, :polymorphic => true
+ belongs_to :ship
has_many :price_estimates, :as => :estimate_of
has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
index 23cd2e0e1c..f5dc93e994 100644
--- a/activerecord/test/models/user.rb
+++ b/activerecord/test/models/user.rb
@@ -2,3 +2,7 @@ class User < ActiveRecord::Base
has_secure_token
has_secure_token :auth_token
end
+
+class UserWithNotification < User
+ after_create -> { Notification.create! message: "A new user has been created." }
+end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 55360b9aa2..77d2f7fda2 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -2,7 +2,7 @@ ActiveRecord::Schema.define do
%w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
- execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
+ drop_table table_name, if_exists: true
end
execute 'DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE'
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 21b23d8e0c..08a16d5c9e 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
ActiveRecord::Schema.define do
def except(adapter_names_to_exclude)
@@ -469,6 +468,10 @@ ActiveRecord::Schema.define do
t.string :name
end
+ create_table :notifications, force: true do |t|
+ t.string :message
+ end
+
create_table :numeric_data, force: true do |t|
t.decimal :bank_balance, precision: 10, scale: 2
t.decimal :big_bank_balance, precision: 15, scale: 2
@@ -623,7 +626,17 @@ ActiveRecord::Schema.define do
t.string :type
end
- create_table :randomly_named_table, force: true do |t|
+ create_table :randomly_named_table1, force: true do |t|
+ t.string :some_attribute
+ t.integer :another_attribute
+ end
+
+ create_table :randomly_named_table2, force: true do |t|
+ t.string :some_attribute
+ t.integer :another_attribute
+ end
+
+ create_table :randomly_named_table3, force: true do |t|
t.string :some_attribute
t.integer :another_attribute
end
@@ -772,6 +785,7 @@ ActiveRecord::Schema.define do
t.column :type, :string
t.column :looter_id, :integer
t.column :looter_type, :string
+ t.belongs_to :ship
end
create_table :tyres, force: true do |t|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 2caf0b27ee..44735e4b75 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,49 @@
+* Fixed a roundtrip problem with AS::SafeBuffer where primitive-like strings
+ will be dumped as primitives:
+
+ Before:
+
+ YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello"
+ YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => true
+ YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => false
+ YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => 1
+ YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => 1.1
+
+ After:
+
+ YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello"
+ YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => "true"
+ YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => "false"
+ YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => "1"
+ YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => "1.1"
+
+ *Godfrey Chan*
+
+* Enable number_to_percentage to keep the number's precision by allowing :precision to be nil
+
+ *Jack Xu*
+
+* config_accessor became a private method, as with Ruby's attr_accessor.
+
+ *Akira Matsuda*
+
+* `AS::Testing::TimeHelpers#travel_to` now changes `DateTime.now` as well as
+ `Time.now` and `Date.today`.
+
+ *Yuki Nishijima*
+
+* Add `file_fixture` to `ActiveSupport::TestCase`.
+ It provides a simple mechanism to access sample files in your test cases.
+
+ By default file fixtures are stored in `test/fixtures/files`. This can be
+ configured per test-case using the `file_fixture_path` class attribute.
+
+ *Yves Senn*
+
+* Return value of yielded block in `File.atomic_write`.
+
+ *Ian Ker-Seymer*
+
* Duplicate frozen array when assigning it to a HashWithIndifferentAccess so
that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`.
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 0f1de8b076..f32bb8a0cc 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -291,13 +291,11 @@ module ActiveSupport
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = run.call env
- env.value
+ run.call.value
}
-
env
else
- run.call env
+ run.call
end
end
end
@@ -309,11 +307,10 @@ module ActiveSupport
value = env.value
if env.halted
- run.call env
+ run.call
else
user_callback.call(target, value) {
- env = run.call env
- env.value
+ run.call.value
}
env
end
@@ -328,12 +325,11 @@ module ActiveSupport
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = run.call env
- env.value
+ run.call.value
}
env
else
- run.call env
+ run.call
end
end
end
@@ -342,8 +338,7 @@ module ActiveSupport
def self.simple(callback_sequence, user_callback)
callback_sequence.around do |env, &run|
user_callback.call(env.target, env.value) {
- env = run.call env
- env.value
+ run.call.value
}
env
end
@@ -373,14 +368,14 @@ module ActiveSupport
def filter; @key; end
def raw_filter; @filter; end
- def merge(chain, new_options)
+ def merge_conditional_options(chain, if_option:, unless_option:)
options = {
:if => @if.dup,
:unless => @unless.dup
}
- options[:if].concat Array(new_options.fetch(:unless, []))
- options[:unless].concat Array(new_options.fetch(:if, []))
+ options[:if].concat Array(unless_option)
+ options[:unless].concat Array(if_option)
self.class.build chain, @filter, @kind, options
end
@@ -701,7 +696,7 @@ module ActiveSupport
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
- new_filter = filter.merge(chain, options)
+ new_filter = filter.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
chain.insert(chain.index(filter), new_filter)
end
@@ -746,8 +741,8 @@ module ActiveSupport
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
- # default after callbacks executed no matter if callback chain was
- # terminated or not. Option makes sense only when <tt>:terminator</tt>
+ # default after callbacks are executed no matter if callback chain was
+ # terminated or not. This option makes sense only when <tt>:terminator</tt>
# option is specified.
#
# * <tt>:scope</tt> - Indicates which methods should be executed when an
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 342d3a9d52..4082d2d464 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -114,7 +114,7 @@ module ActiveSupport
return false
else
return false if base < self
- @_dependencies.each { |dep| base.send(:include, dep) }
+ @_dependencies.each { |dep| base.include(dep) }
super
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 3dd44e32d8..8256c325af 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -122,6 +122,7 @@ module ActiveSupport
send("#{name}=", yield) if block_given?
end
end
+ private :config_accessor
end
# Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 38374af388..fad6fa8d9d 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -20,7 +20,7 @@ class File
temp_file = Tempfile.new(basename(file_name), temp_dir)
temp_file.binmode
- yield temp_file
+ return_val = yield temp_file
temp_file.close
if File.exist?(file_name)
@@ -40,6 +40,9 @@ class File
chown(old_stat.uid, old_stat.gid, file_name)
# This operation will affect filesystem ACL's
chmod(old_stat.mode, file_name)
+
+ # Make sure we return the result of the yielded block
+ return_val
rescue Errno::EPERM, Errno::EACCES
# Changing file ownership failed, moving on.
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 24df83800b..a5f4d03256 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -185,19 +185,31 @@ class Module
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
-
- exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
-
- method_def = [
- "def #{method_prefix}#{method}(#{definition})",
- " _ = #{to}",
- " if !_.nil? || nil.respond_to?(:#{method})",
- " _.#{method}(#{definition})",
- " else",
- " #{exception unless allow_nil}",
- " end",
+ if allow_nil
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})",
+ "_ = #{to}",
+ "if !_.nil? || nil.respond_to?(:#{method})",
+ " _.#{method}(#{definition})",
+ "end",
"end"
- ].join ';'
+ ].join ';'
+ else
+ exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
+
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})",
+ " _ = #{to}",
+ " _.#{method}(#{definition})",
+ "rescue NoMethodError => e",
+ " if _.nil? && e.name == :#{method}",
+ " #{exception}",
+ " else",
+ " raise",
+ " end",
+ "end"
+ ].join ';'
+ end
module_eval(method_def, file, line)
end
diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb
index b82148e4e5..6b447d772b 100644
--- a/activesupport/lib/active_support/core_ext/name_error.rb
+++ b/activesupport/lib/active_support/core_ext/name_error.rb
@@ -23,8 +23,7 @@ class NameError
# # => true
def missing_name?(name)
if name.is_a? Symbol
- last_name = (missing_name || '').split('::').last
- last_name == name.to_s
+ self.name == name
else
missing_name == name.to_s
end
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index a124202936..2eedd4fdb1 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/multibyte'
class String
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 ba8d4acd6d..bae4e206e6 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -85,6 +85,11 @@ class ERB
# automatically flag the result as HTML safe, since the raw value is unsafe to
# 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.
+ # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
+ # content returned by your JSON.
+ #
# If you need to output JSON elsewhere in your HTML, you can just do something
# like this, as any unsafe characters (including quotation marks) will be
# automatically escaped for you:
@@ -217,7 +222,7 @@ module ActiveSupport #:nodoc:
end
def encode_with(coder)
- coder.represent_scalar nil, to_str
+ coder.represent_object nil, to_str
end
UNSAFE_STRING_METHODS.each do |unsafe_method|
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 649dc52865..13610ba19f 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -69,7 +69,7 @@ class Time
# and minute is passed, then sec, usec and nsec is set to 0. The +options+
# parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
# <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>
- # <tt>:nsec</tt>. Path either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
+ # <tt>:nsec</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
#
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 0668eadb1e..d683e7c777 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -26,7 +26,7 @@ class Time
# <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
#
# class ApplicationController < ActionController::Base
- # around_filter :set_time_zone
+ # around_action :set_time_zone
#
# def set_time_zone
# if logged_in?
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index e03e7c30d8..664cc15a29 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -373,7 +373,7 @@ module ActiveSupport #:nodoc:
# Is the provided constant path defined?
def qualified_const_defined?(path)
- Object.qualified_const_defined?(path.sub(/^::/, ''), false)
+ Object.const_defined?(path, false)
end
# Given +path+, a filesystem path to a ruby file, return an array of
@@ -607,7 +607,7 @@ module ActiveSupport #:nodoc:
def autoloaded?(desc)
return false if desc.is_a?(Module) && desc.anonymous?
name = to_constant_name desc
- return false unless qualified_const_defined? name
+ return false unless qualified_const_defined?(name)
return autoloaded_constants.include?(name)
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 9e742b1917..95f3f6255a 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -68,7 +68,7 @@ module I18n
end
def self.include_fallbacks_module
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.backend.class.include(I18n::Backend::Fallbacks)
end
def self.init_fallbacks(fallbacks)
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 34439ee8be..cfca42bc69 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -94,7 +94,7 @@ module ActiveSupport
# * <tt>:locale</tt> - Sets the locale to be used for formatting
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
+ # (defaults to 3). Keeps the number's precision if nil.
# * <tt>:significant</tt> - If +true+, precision will be the #
# of significant_digits. If +false+, the # of fractional
# digits (defaults to +false+).
@@ -116,6 +116,7 @@ module ActiveSupport
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
# 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 %
def number_to_percentage(number, options = {})
@@ -161,7 +162,7 @@ module ActiveSupport
# * <tt>:locale</tt> - Sets the locale to be used for formatting
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
+ # (defaults to 3). Keeps the number's precision if nil.
# * <tt>:significant</tt> - If +true+, precision will be the #
# of significant_digits. If +false+, the # of fractional
# digits (defaults to +false+).
@@ -182,6 +183,7 @@ module ActiveSupport
# number_to_rounded(111.2345, significant: true) # => 111
# number_to_rounded(111.2345, precision: 1, significant: true) # => 100
# number_to_rounded(13, precision: 5, significant: true) # => 13.000
+ # number_to_rounded(13, precision: nil) # => 13
# number_to_rounded(111.234, locale: :fr) # => 111,234
#
# number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index dcf9a567e8..df316a08e6 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -6,36 +6,39 @@ module ActiveSupport
def convert
precision = options.delete :precision
- significant = options.delete :significant
- case number
- when Float, String
- @number = BigDecimal(number.to_s)
- when Rational
- @number = BigDecimal(number, digit_count(number.to_i) + precision)
- else
- @number = number.to_d
- end
-
- if significant && precision > 0
- digits, rounded_number = digits_and_rounded_number(precision)
- precision -= digits
- precision = 0 if precision < 0 # don't let it be negative
- else
- rounded_number = number.round(precision)
- rounded_number = rounded_number.to_i if precision == 0
- rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
- end
+ if precision
+ case number
+ when Float, String
+ @number = BigDecimal(number.to_s)
+ when Rational
+ @number = BigDecimal(number, digit_count(number.to_i) + precision)
+ else
+ @number = number.to_d
+ end
- formatted_string =
- if BigDecimal === rounded_number && rounded_number.finite?
- s = rounded_number.to_s('F') + '0'*precision
- a, b = s.split('.', 2)
- a + '.' + b[0, precision]
+ if options.delete(:significant) && precision > 0
+ digits, rounded_number = digits_and_rounded_number(precision)
+ precision -= digits
+ precision = 0 if precision < 0 # don't let it be negative
else
- "%00.#{precision}f" % rounded_number
+ rounded_number = number.round(precision)
+ rounded_number = rounded_number.to_i if precision == 0
+ rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
end
+ formatted_string =
+ if BigDecimal === rounded_number && rounded_number.finite?
+ s = rounded_number.to_s('F') + '0'*precision
+ a, b = s.split('.', 2)
+ a + '.' + b[0, precision]
+ else
+ "%00.#{precision}f" % rounded_number
+ end
+ else
+ formatted_string = number
+ end
+
delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
format_number(delimited_number)
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 98be78b41b..cd40284660 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -96,7 +96,7 @@ module ActiveSupport
event.end = finished
event.payload.merge!(payload)
- method = name.split('.').first
+ method = name.split('.'.freeze).first
send(method, event)
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index c7d6c62129..739823bd56 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -8,6 +8,7 @@ require 'active_support/testing/declarative'
require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
+require 'active_support/testing/file_fixtures'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
@@ -55,6 +56,7 @@ module ActiveSupport
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
include ActiveSupport::Testing::TimeHelpers
+ include ActiveSupport::Testing::FileFixtures
extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb
new file mode 100644
index 0000000000..4c6a0801b8
--- /dev/null
+++ b/activesupport/lib/active_support/testing/file_fixtures.rb
@@ -0,0 +1,34 @@
+module ActiveSupport
+ module Testing
+ # Adds simple access to sample files called file fixtures.
+ # File fixtures are normal files stored in
+ # <tt>ActiveSupport::TestCase.file_fixture_path</tt>.
+ #
+ # File fixtures are represented as +Pathname+ objects.
+ # This makes it easy to extract specific information:
+ #
+ # file_fixture("example.txt").read # get the file's content
+ # file_fixture("example.mp3").size # get the file size
+ module FileFixtures
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :file_fixture_path, instance_writer: false
+ end
+
+ # Returns a +Pathname+ to the fixture file named +fixture_name+.
+ #
+ # Raises ArgumentError if +fixture_name+ can't be found.
+ def file_fixture(fixture_name)
+ path = Pathname.new(File.join(file_fixture_path, fixture_name))
+
+ if path.exist?
+ path
+ else
+ msg = "the directory '%s' does not contain a file named '%s'"
+ raise ArgumentError, msg % [file_fixture_path, fixture_name]
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb
new file mode 100644
index 0000000000..895192ad05
--- /dev/null
+++ b/activesupport/lib/active_support/testing/stream.rb
@@ -0,0 +1,42 @@
+module ActiveSupport
+ module Testing
+ module Stream #:nodoc:
+ private
+
+ def silence_stream(stream)
+ old_stream = stream.dup
+ stream.reopen(IO::NULL)
+ stream.sync = true
+ yield
+ ensure
+ stream.reopen(old_stream)
+ old_stream.close
+ end
+
+ def quietly
+ silence_stream(STDOUT) do
+ silence_stream(STDERR) do
+ yield
+ end
+ end
+ end
+
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 8c63815660..df5186ddec 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -99,6 +99,7 @@ module ActiveSupport
simple_stubs.stub_object(Time, :now, now)
simple_stubs.stub_object(Date, :today, now.to_date)
+ simple_stubs.stub_object(DateTime, :now, now.to_datetime)
if block_given?
begin
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 728b53849d..da39f0d245 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -224,13 +224,6 @@ module ActiveSupport
@zones ||= zones_map.values.sort
end
- def zones_map
- @zones_map ||= begin
- MAPPING.each_key {|place| self[place]} # load all the zones
- @lazy_zones_map
- end
- end
-
# Locate a specific time zone object. If the argument is a string, it
# is interpreted to mean the name of the timezone to locate. If it is a
# numeric value it is either the hour offset, or the second offset, of the
@@ -257,6 +250,14 @@ module ActiveSupport
def us_zones
@us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
end
+
+ private
+ def zones_map
+ @zones_map ||= begin
+ MAPPING.each_key {|place| self[place]} # load all the zones
+ @lazy_zones_map
+ end
+ end
end
include Comparable
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 47a2824186..bb0ea9c582 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -75,5 +75,5 @@ module LibXML #:nodoc:
end
end
-LibXML::XML::Document.send(:include, LibXML::Conversions::Document)
-LibXML::XML::Node.send(:include, LibXML::Conversions::Node)
+LibXML::XML::Document.include(LibXML::Conversions::Document)
+LibXML::XML::Node.include(LibXML::Conversions::Node)
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 7398d4fa82..619cc7522d 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -77,7 +77,7 @@ module ActiveSupport
end
end
- Nokogiri::XML::Document.send(:include, Conversions::Document)
- Nokogiri::XML::Node.send(:include, Conversions::Node)
+ Nokogiri::XML::Document.include(Conversions::Document)
+ Nokogiri::XML::Node.include(Conversions::Node)
end
end
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 60bd8a06aa..253c1adc23 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -59,24 +59,24 @@ class ConcernTest < ActiveSupport::TestCase
end
def test_module_is_included_normally
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal "baz", @klass.new.baz
assert @klass.included_modules.include?(ConcernTest::Baz)
end
def test_class_methods_are_extended
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal "baz", @klass.baz
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
end
def test_included_block_is_ran
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal true, @klass.included_ran
end
def test_modules_dependencies_are_met
- @klass.send(:include, Bar)
+ @klass.include(Bar)
assert_equal "bar", @klass.new.bar
assert_equal "bar+baz", @klass.new.baz
assert_equal "bar's baz + baz", @klass.baz
@@ -84,7 +84,7 @@ class ConcernTest < ActiveSupport::TestCase
end
def test_dependencies_with_multiple_modules
- @klass.send(:include, Foo)
+ @klass.include(Foo)
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index ef847fc557..5d22ded2de 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -111,6 +111,14 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
end
end
+ test 'the config_accessor method should not be publicly callable' do
+ assert_raises NoMethodError do
+ Class.new {
+ include ActiveSupport::Configurable
+ }.config_accessor :foo
+ end
+ end
+
def assert_method_defined(object, method)
methods = object.public_methods.map(&:to_s)
assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}"
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index 2c04e9687c..cde0132b97 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -57,6 +57,16 @@ class AtomicWriteTest < ActiveSupport::TestCase
File.unlink(file_name) rescue nil
end
+ def test_atomic_write_returns_result_from_yielded_block
+ block_return_value = File.atomic_write(file_name, Dir.pwd) do |file|
+ "Hello world!"
+ end
+
+ assert_equal "Hello world!", block_return_value
+ ensure
+ File.unlink(file_name) rescue nil
+ end
+
private
def file_name
"atomic.file"
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 3c49c4d14f..c9c9b66a6c 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -78,7 +78,7 @@ Product = Struct.new(:name) do
def type
@type ||= begin
- :thing_without_same_method_name_as_delegated.name
+ nil.type_name
end
end
end
diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 246bc7fa61..8a5e385dd7 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index cdc695f036..24037c665a 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'date'
require 'abstract_unit'
require 'inflector_test_cases'
diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb
index 43a5997ddd..1694fe7e72 100644
--- a/activesupport/test/core_ext/uri_ext_test.rb
+++ b/activesupport/test/core_ext/uri_ext_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'uri'
require 'active_support/core_ext/uri'
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 7aff56cbad..20bd8ee5dd 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/testing/stream'
class Deprecatee
def initialize
@@ -36,6 +37,8 @@ end
class DeprecationTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Stream
+
def setup
# Track the last warning.
@old_behavior = ActiveSupport::Deprecation.behavior
@@ -356,20 +359,4 @@ class DeprecationTest < ActiveSupport::TestCase
deprecator
end
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
end
diff --git a/activesupport/test/file_fixtures/sample.txt b/activesupport/test/file_fixtures/sample.txt
new file mode 100644
index 0000000000..0fa80e7383
--- /dev/null
+++ b/activesupport/test/file_fixtures/sample.txt
@@ -0,0 +1 @@
+sample file fixture
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 3770f00fe3..18a8b92eb9 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module InflectorTestCases
SingularToPlural = {
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 80bf255080..f2fc456f4b 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/json'
require 'active_support/time'
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 5c5045da1e..63d921e3b4 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'securerandom'
require 'abstract_unit'
require 'active_support/core_ext/string/inflections'
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 94748dd991..e1c4b705f8 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
require 'active_support/core_ext/string/multibyte'
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index f7bd21c8ab..d8704716e7 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb
index d8ffd7ca9c..11f5374017 100644
--- a/activesupport/test/multibyte_proxy_test.rb
+++ b/activesupport/test/multibyte_proxy_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 90af2dadd4..2e4b5cc873 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module MultibyteTestHelpers
UNICODE_STRING = 'こにちわ'.freeze
diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb
index bec65daf50..52ae266f01 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 50d84a9470..23996ef381 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -83,6 +83,10 @@ module ActiveSupport
assert_equal("98a%", number_helper.number_to_percentage("98a"))
assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN))
assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY))
+ assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil))
+ assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil))
+ assert_equal("1000.1%", number_helper.number_to_percentage(1000.1, precision: nil))
+ assert_equal("-0.13 %", number_helper.number_to_percentage("-0.13", precision: nil, format: "%n %"))
end
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 4532152996..18fb6d2fbf 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -61,6 +61,13 @@ class SafeBufferTest < ActiveSupport::TestCase
assert_equal({'str' => str}, YAML.load(yaml))
end
+ test "Should work with primitive-like-strings in to_yaml conversion" do
+ assert_equal 'true', YAML.load(ActiveSupport::SafeBuffer.new('true').to_yaml)
+ assert_equal 'false', YAML.load(ActiveSupport::SafeBuffer.new('false').to_yaml)
+ assert_equal '1', YAML.load(ActiveSupport::SafeBuffer.new('1').to_yaml)
+ assert_equal '1.1', YAML.load(ActiveSupport::SafeBuffer.new('1.1').to_yaml)
+ end
+
test "Should work with underscore" do
str = "MyTest".html_safe.underscore
assert_equal "my_test", str
diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb
new file mode 100644
index 0000000000..91b8a9071c
--- /dev/null
+++ b/activesupport/test/testing/file_fixtures_test.rb
@@ -0,0 +1,28 @@
+require 'abstract_unit'
+
+class FileFixturesTest < ActiveSupport::TestCase
+ self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__)
+
+ test "#file_fixture returns Pathname to file fixture" do
+ path = file_fixture("sample.txt")
+ assert_kind_of Pathname, path
+ assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s
+ end
+
+ test "raises an exception when the fixture file does not exist" do
+ e = assert_raises(ArgumentError) do
+ file_fixture("nope")
+ end
+ assert_match(/^the directory '[^']+test\/file_fixtures' does not contain a file named 'nope'$/, e.message)
+ end
+end
+
+class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase
+ self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__))
+
+ test "#file_fixture_path returns Pathname to file fixture" do
+ path = file_fixture("sample.txt")
+ assert_kind_of Pathname, path
+ assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s
+ end
+end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index 065539671d..676a143692 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/core_ext/date'
+require 'active_support/core_ext/date_time'
require 'active_support/core_ext/numeric/time'
class TimeTravelTest < ActiveSupport::TestCase
@@ -17,6 +18,7 @@ class TimeTravelTest < ActiveSupport::TestCase
assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
assert_equal expected_time.to_date, Date.today
+ assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
end
def test_time_helper_travel_with_block
@@ -25,10 +27,12 @@ class TimeTravelTest < ActiveSupport::TestCase
travel 1.day do
assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
assert_equal expected_time.to_date, Date.today
+ assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
end
assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db)
assert_not_equal expected_time.to_date, Date.today
+ assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
end
def test_time_helper_travel_to
@@ -37,6 +41,7 @@ class TimeTravelTest < ActiveSupport::TestCase
assert_equal expected_time, Time.now
assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
end
def test_time_helper_travel_to_with_block
@@ -45,10 +50,12 @@ class TimeTravelTest < ActiveSupport::TestCase
travel_to expected_time do
assert_equal expected_time, Time.now
assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
end
assert_not_equal expected_time, Time.now
assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
end
def test_time_helper_travel_back
@@ -57,16 +64,20 @@ class TimeTravelTest < ActiveSupport::TestCase
travel_to expected_time
assert_equal expected_time, Time.now
assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
travel_back
assert_not_equal expected_time, Time.now
assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
end
def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding
travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do
assert_equal 50, Time.now.sec
assert_equal 0, Time.now.usec
+ assert_equal 50, DateTime.now.sec
+ assert_equal 0, DateTime.now.usec
end
end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index cd7e184cda..7888b9919b 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -402,8 +402,7 @@ class TimeZoneTest < ActiveSupport::TestCase
end
def test_unknown_zones_dont_store_mapping_keys
- ActiveSupport::TimeZone["bogus"]
- assert !ActiveSupport::TimeZone.zones_map.key?("bogus")
+ assert_nil ActiveSupport::TimeZone["bogus"]
end
def test_new
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index 6833ae68a7..378421fedd 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/inflector/transliterate'
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index f49431cbbf..bcd6997b06 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -11,7 +11,7 @@ module XmlMiniTest
assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key")
end
- def test_rename_key_does_nothing_with_dasherize_true
+ def test_rename_key_dasherizes_with_dasherize_true
assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true)
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index cd3fa9f65b..dd5ca4b395 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,5 +1,13 @@
+* New section in Active Record Association Basics: Single Table Inheritance
+
+ *Andrey Nering*
+
* New section in Active Record Querying: Understanding The Method Chaining
*Andrey Nering*
+* New section in Configuring: Search Engines Indexing
+
+ *Andrey Nering*
+
Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/assets/images/favicon.ico b/guides/assets/images/favicon.ico
index e0e80cf8f1..faa10b4580 100644
--- a/guides/assets/images/favicon.ico
+++ b/guides/assets/images/favicon.ico
Binary files differ
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index e04d79c818..032e6bfe11 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -2,6 +2,7 @@
gem 'rails', '4.2.0'
require 'rails'
+require 'rack/test'
require 'action_controller/railtie'
class TestApp < Rails::Application
@@ -27,7 +28,6 @@ class TestController < ActionController::Base
end
require 'minitest/autorun'
-require 'rack/test'
# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 80000baf66..f68179841e 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -737,7 +737,7 @@ You can choose not to yield and build the response yourself, in which case the a
While the most common way to use filters is by creating private methods and using *_action to add them, there are two other ways to do the same thing.
-The first is to use a block directly with the *_action methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block:
+The first is to use a block directly with the *\_action methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block:
```ruby
class ApplicationController < ActionController::Base
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index e55ff16495..73b240ff2c 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -442,6 +442,39 @@ end
Will render the HTML part using the `my_layout.html.erb` file and the text part
with the usual `user_mailer.text.erb` file if it exists.
+### Previewing Emails
+
+Action Mailer previews provide a way to see how emails look by visiting a
+special URL that renders them. In the above example, the preview class for
+`UserMailer` should be named `UserMailerPreview` and located in
+`test/mailers/previews/user_mailer_preview.rb`. To see the preview of
+`welcome_email`, implement a method that has the same name and call
+`UserMailer.welcome_email`:
+
+```ruby
+class UserMailerPreview < ActionMailer::Preview
+ def welcome_email
+ UserMailer.welcome_email(User.first)
+ end
+end
+```
+
+Then the preview will be available in <http://localhost:3000/rails/mailers/user_mailer/welcome_email>.
+
+If you change something in `app/views/user_mailer/welcome_email.html.erb`
+or the mailer itself, it'll automatically reload and render it so you can
+visually see the new style instantly. A list of previews are also available
+in <http://localhost:3000/rails/mailers>.
+
+By default, these preview classes live in `test/mailers/previews`.
+This can be configured using the `preview_path` option. For example, if you
+want to change it to `lib/mailer_previews`, you can configure it in
+`config/application.rb`:
+
+```ruby
+config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
+```
+
### Generating URLs in Action Mailer Views
Unlike controllers, the mailer instance doesn't have any context about the
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 665a2b71ff..8f6676dc65 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -190,6 +190,22 @@ One way to use partials is to treat them as the equivalent of subroutines; a way
Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page.
+#### `render` without `partial` and `locals` options
+
+In the above example, `render` takes 2 options: `partial` and `locals`. But if
+these are the only options you want to pass, you can skip using these options.
+For example, instead of:
+
+```erb
+<%= render partial: "product", locals: {product: @product} %>
+```
+
+You can also do:
+
+```erb
+<%= render "product", product: @product %>
+```
+
#### The `as` and `object` options
By default `ActionView::Partials::PartialRenderer` has its object in a local variable with the same name as the template. So, given:
@@ -349,83 +365,6 @@ WIP: Not all the helpers are listed here. For a full list see the [API documenta
The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the [API Documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html), which covers all of the helpers in more detail, but this should serve as a good starting point.
-### RecordTagHelper
-
-This module provides methods for generating container tags, such as `div`, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use.
-
-#### content_tag_for
-
-Renders a container tag that relates to your Active Record Object.
-
-For example, given `@article` is the object of `Article` class, you can do:
-
-```html+erb
-<%= content_tag_for(:tr, @article) do %>
- <td><%= @article.title %></td>
-<% end %>
-```
-
-This will generate this HTML output:
-
-```html
-<tr id="article_1234" class="article">
- <td>Hello World!</td>
-</tr>
-```
-
-You can also supply HTML attributes as an additional option hash. For example:
-
-```html+erb
-<%= content_tag_for(:tr, @article, class: "frontpage") do %>
- <td><%= @article.title %></td>
-<% end %>
-```
-
-Will generate this HTML output:
-
-```html
-<tr id="article_1234" class="article frontpage">
- <td>Hello World!</td>
-</tr>
-```
-
-You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@articles` is an array of two `Article` objects:
-
-```html+erb
-<%= content_tag_for(:tr, @articles) do |article| %>
- <td><%= article.title %></td>
-<% end %>
-```
-
-Will generate this HTML output:
-
-```html
-<tr id="article_1234" class="article">
- <td>Hello World!</td>
-</tr>
-<tr id="article_1235" class="article">
- <td>Ruby on Rails Rocks!</td>
-</tr>
-```
-
-#### div_for
-
-This is actually a convenient method which calls `content_tag_for` internally with `:div` as the tag name. You can pass either an Active Record object or a collection of objects. For example:
-
-```html+erb
-<%= div_for(@article, class: "frontpage") do %>
- <td><%= @article.title %></td>
-<% end %>
-```
-
-Will generate this HTML output:
-
-```html
-<div id="article_1234" class="article frontpage">
- <td>Hello World!</td>
-</div>
-```
-
### AssetTagHelper
This module provides methods for generating HTML that links views to assets such as images, JavaScript files, stylesheets, and feeds.
@@ -1601,7 +1540,7 @@ details can be found in the [Rails Security Guide](security.html#cross-site-requ
Localized Views
---------------
-Action View has the ability render different templates depending on the current locale.
+Action View has the ability to render different templates depending on the current locale.
For example, suppose you have a `ArticlesController` with a show action. By default, calling this action will render `app/views/articles/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/articles/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available.
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 4d1625b28d..953c29719d 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -205,7 +205,7 @@ class ProcessVideoJob < ActiveJob::Base
end
def perform(video)
- # do process video
+ # Do process video
end
end
@@ -238,13 +238,13 @@ class GuestsCleanupJob < ActiveJob::Base
queue_as :default
before_enqueue do |job|
- # do something with the job instance
+ # Do something with the job instance
end
around_perform do |job, block|
- # do something before perform
+ # Do something before perform
block.call
- # do something after perform
+ # Do something after perform
end
def perform
@@ -311,7 +311,7 @@ class GuestsCleanupJob < ActiveJob::Base
queue_as :default
rescue_from(ActiveRecord::RecordNotFound) do |exception|
- # do something with the exception
+ # Do something with the exception
end
def perform
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 97cabc1728..b8db21a989 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -479,7 +479,8 @@ Rails will generate a name for every foreign key starting with
There is a `:name` option to specify a different name if needed.
NOTE: Active Record only supports single column foreign keys. `execute` and
-`structure.sql` are required to use composite foreign keys.
+`structure.sql` are required to use composite foreign keys. See
+[Schema Dumping and You](#schema-dumping-and-you).
Removing a foreign key is easy as well:
@@ -695,6 +696,10 @@ of `create_table` and `reversible`, replacing `create_table`
by `drop_table`, and finally replacing `up` by `down` and vice-versa.
This is all taken care of by `revert`.
+NOTE: If you want to add check constraints like in the examples above,
+you will have to use `structure.sql` as dump method. See
+[Schema Dumping and You](#schema-dumping-and-you).
+
Running Migrations
------------------
@@ -943,10 +948,10 @@ that Active Record supports. This could be very useful if you were to
distribute an application that is able to run against multiple databases.
There is however a trade-off: `db/schema.rb` cannot express database specific
-items such as triggers, or stored procedures. While in a migration you can
-execute custom SQL statements, the schema dumper cannot reconstitute those
-statements from the database. If you are using features like this, then you
-should set the schema format to `:sql`.
+items such as triggers, stored procedures or check constraints. While in a
+migration you can execute custom SQL statements, the schema dumper cannot
+reconstitute those statements from the database. If you are using features like
+this, then you should set the schema format to `:sql`.
Instead of using Active Record's schema dumper, the database's structure will
be dumped using a tool specific to the database (via the `db:structure:dump`
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 373a98bb85..ad5103da69 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -317,7 +317,7 @@ end
The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`.
-Two additional options, `:batch_size` and `:start`, are available as well.
+Two additional options, `:batch_size` and `:begin_at`, are available as well.
**`:batch_size`**
@@ -329,19 +329,32 @@ User.find_each(batch_size: 5000) do |user|
end
```
-**`:start`**
+**`:begin_at`**
-By default, records are fetched in ascending order of the primary key, which must be an integer. The `:start` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
+By default, records are fetched in ascending order of the primary key, which must be an integer. The `:begin_at` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000:
```ruby
-User.find_each(start: 2000, batch_size: 5000) do |user|
+User.find_each(begin_at: 2000, batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
```
-Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:start` option on each worker.
+Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:begin_at` option on each worker.
+
+**`:end_at`**
+
+Similar to the `:begin_at` option, `:end_at` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need.
+This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:begin_at` and `:end_at`
+
+For example, to send newsletters only to users with the primary key starting from 2000 upto 10000 and to retrieve them in batches of 1000:
+
+```ruby
+User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user|
+ NewsMailer.weekly(user).deliver_now
+end
+```
#### `find_in_batches`
@@ -356,7 +369,7 @@ end
##### Options for `find_in_batches`
-The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`.
+The `find_in_batches` method accepts the same `:batch_size`, `:begin_at` and `:end_at` options as `find_each`.
Conditions
----------
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index c9af70934a..31c5b07a05 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -227,8 +227,26 @@ end
```
We'll cover validation errors in greater depth in the [Working with Validation
-Errors](#working-with-validation-errors) section. For now, let's turn to the
-built-in validation helpers that Rails provides by default.
+Errors](#working-with-validation-errors) section.
+
+### `errors.details`
+
+To check which validations failed on an invalid attribute, you can use
+`errors.details[:attribute]`. It returns an array of hashes with an `:error`
+key to get the symbol of the validator:
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true
+end
+
+>> person = Person.new
+>> person.valid?
+>> person.errors.details[:name] #=> [{error: :blank}]
+```
+
+Using `details` with custom validators is covered in the [Working with
+Validation Errors](#working-with-validation-errors) section.
Validation Helpers
------------------
@@ -452,7 +470,7 @@ point number. To specify that only integral numbers are allowed set
If you set `:only_integer` to `true`, then it will use the
```ruby
-/\A[+-]?\d+\Z/
+/\A[+-]?\d+\z/
```
regular expression to validate the attribute's value. Otherwise, it will try to
@@ -588,9 +606,7 @@ This helper validates that the attribute's value is unique right before the
object gets saved. It does not create a uniqueness constraint in the database,
so it may happen that two different database connections create two records
with the same value for a column that you intend to be unique. To avoid that,
-you must create a unique index on both columns in your database. See
-[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html)
-for more details about multiple column indexes.
+you must create a unique index on that column in your database.
```ruby
class Account < ActiveRecord::Base
@@ -610,6 +626,7 @@ class Holiday < ActiveRecord::Base
message: "should happen once per year" }
end
```
+Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/9.4/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns.
There is also a `:case_sensitive` option that you can use to define whether the
uniqueness constraint will be case sensitive or not. This option defaults to
@@ -1061,7 +1078,7 @@ Another way to do this is using `[]=` setter
```ruby
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
- errors[:name] = "cannot contain the characters !@#%*()_-+="
+ errors.messages[:name] << "cannot contain the characters !@#%*()_-+="
end
end
@@ -1074,6 +1091,43 @@ Another way to do this is using `[]=` setter
# => ["Name cannot contain the characters !@#%*()_-+="]
```
+### `errors.details`
+
+You can specify a validator type to the returned error details hash using the
+`errors.add` method.
+
+```ruby
+class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors.add(:name, :invalid_characters)
+ end
+end
+
+person = Person.create(name: "!@#")
+
+person.errors.details[:name]
+# => [{error: :invalid_characters}]
+```
+
+To improve the error details to contain the unallowed characters set for instance,
+you can pass additional keys to `errors.add`.
+
+```ruby
+class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=")
+ end
+end
+
+person = Person.create(name: "!@#")
+
+person.errors.details[:name]
+# => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}]
+```
+
+All built in Rails validators populate the details hash with the corresponding
+validator type.
+
### `errors[:base]`
You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index dab885a8fb..cd715aba1f 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -171,7 +171,7 @@ class CreateCustomers < ActiveRecord::Migration
end
create_table :orders do |t|
- t.belongs_to :customer, index:true
+ t.belongs_to :customer, index: true
t.datetime :order_date
t.timestamps null: false
end
@@ -691,7 +691,7 @@ c.first_name = 'Manny'
c.first_name == o.customer.first_name # => false
```
-This happens because c and o.customer are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
+This happens because `c` and `o.customer` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
```ruby
class Customer < ActiveRecord::Base
@@ -726,10 +726,10 @@ Most associations with standard names will be supported. However, associations
that contain the following options will not have their inverses set
automatically:
-* :conditions
-* :through
-* :polymorphic
-* :foreign_key
+* `:conditions`
+* `:through`
+* `:polymorphic`
+* `:foreign_key`
Detailed Association Reference
------------------------------
@@ -1986,8 +1986,8 @@ While Rails uses intelligent defaults that will work well in most situations, th
```ruby
class Parts < ActiveRecord::Base
- has_and_belongs_to_many :assemblies, autosave: true,
- readonly: true
+ has_and_belongs_to_many :assemblies, -> { readonly },
+ autosave: true
end
```
@@ -1999,7 +1999,6 @@ The `has_and_belongs_to_many` association supports these options:
* `:foreign_key`
* `:join_table`
* `:validate`
-* `:readonly`
##### `:association_foreign_key`
@@ -2243,3 +2242,67 @@ Extensions can refer to the internals of the association proxy using these three
* `proxy_association.owner` returns the object that the association is a part of.
* `proxy_association.reflection` returns the reflection object that describes the association.
* `proxy_association.target` returns the associated object for `belongs_to` or `has_one`, or the collection of associated objects for `has_many` or `has_and_belongs_to_many`.
+
+Single Table Inheritance
+------------------------
+
+Sometimes, you may want to share fields and behavior between different models.
+Let's say we have Car, Motorcycle and Bicycle models. We will want to share
+the `color` and `price` fields and some methods for all of them, but having some
+specific behavior for each, and separated controllers too.
+
+Rails makes this quite easy. First, let's generate the base Vehicle model:
+
+```bash
+$ rails generate model vehicle type:string color:string price:decimal{10.2}
+```
+
+Did you note we are adding a "type" field? Since all models will be saved in a
+single database table, Rails will save in this column the name of the model that
+is being saved. In our example, this can be "Car", "Motorcycle" or "Bicycle."
+STI won't work without a "type" field in the table.
+
+Next, we will generate the three models that inherit from Vehicle. For this,
+we can use the `--parent=PARENT` option, which will generate a model that
+inherits from the specified parent and without equivalent migration (since the
+table already exists).
+
+For example, to generate the Car model:
+
+```bash
+$ rails generate model car --parent=Vehicle
+```
+
+The generated model will look like this:
+
+```ruby
+class Car < Vehicle
+end
+```
+
+This means that all behavior added to Vehicle is available for Car too, as
+associations, public methods, etc.
+
+Creating a car will save it in the `vehicles` table with "Car" as the `type` field:
+
+```ruby
+Car.create color: 'Red', price: 10000
+```
+
+will generate the following SQL:
+
+```sql
+INSERT INTO "vehicles" ("type", "color", "price") VALUES ("Car", "Red", 10000)
+```
+
+Querying car records will just search for vehicles that are cars:
+
+```ruby
+Car.all
+```
+
+will run a query like:
+
+```sql
+SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')
+```
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index c4fac1cff5..9e78eebf82 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -114,6 +114,16 @@ certain nesting does not necessarily correlate with the namespaces at the spot.
Even more, they are totally independent, take for instance
```ruby
+module X
+ module Y
+ end
+end
+
+module A
+ module B
+ end
+end
+
module X::Y
module A::B
# (3)
@@ -141,9 +151,10 @@ executed, and popped after it.
* A singleton class opened with `class << object` gets pushed, and popped later.
-* When any of the `*_eval` family of methods is called using a string argument,
+* When `instance_eval` is called using a string argument,
the singleton class of the receiver is pushed to the nesting of the eval'ed
-code.
+code. When `class_eval` or `module_eval` is called using a string argument,
+the receiver is pushed to the nesting of the eval'ed code.
* The nesting at the top-level of code interpreted by `Kernel#load` is empty
unless the `load` call receives a true value as second argument, in which case
@@ -290,7 +301,9 @@ order. The ancestors of those elements are ignored.
2. If not found, then the algorithm walks up the ancestor chain of the cref.
-3. If not found, `const_missing` is invoked on the cref. The default
+3. If not found and the cref is a module, the constant is looked up in `Object`.
+
+4. If not found, `const_missing` is invoked on the cref. The default
implementation of `const_missing` raises `NameError`, but it can be overridden.
Rails autoloading **does not emulate this algorithm**, but its starting point is
@@ -313,7 +326,7 @@ relative: `::Billing::Invoice`. That would force `Billing` to be looked up
only as a top-level constant.
`Invoice` on the other hand is qualified by `Billing` and we are going to see
-its resolution next. Let's call *parent* to that qualifying class or module
+its resolution next. Let's define *parent* to be that qualifying class or module
object, that is, `Billing` in the example above. The algorithm for qualified
constants goes like this:
@@ -450,8 +463,9 @@ Also, this collection is configurable via `config.autoload_paths`. For example,
by adding this to `config/application.rb`:
```ruby
-config.autoload_paths += "#{Rails.root}/lib"
+config.autoload_paths << "#{Rails.root}/lib"
```
+`config.autoload_paths` is accessible from environment-specific configuration files, but any changes made to it outside `config/application.rb` don't have an effect.
The value of `autoload_paths` can be inspected. In a just generated application
it is (edited):
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 78f26ccefa..19ccdc5488 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -419,14 +419,6 @@ The most common tasks of the `db:` Rake namespace are `migrate` and `create`, an
More information about migrations can be found in the [Migrations](migrations.html) guide.
-### `doc`
-
-The `doc:` namespace has the tools to generate documentation for your app, API documentation, guides. Documentation can also be stripped which is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform.
-
-* `rake doc:app` generates documentation for your application in `doc/app`.
-* `rake doc:guides` generates Rails guides in `doc/guides`.
-* `rake doc:rails` generates API documentation for Rails in `doc/api`.
-
### `notes`
`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 9c0f2ddc8a..994cc31cff 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -1049,3 +1049,21 @@ These configuration points are then available through the configuration object:
Rails.configuration.x.super_debugger # => true
Rails.configuration.x.super_debugger.not_set # => nil
```
+
+Search Engines Indexing
+-----------------------
+
+Sometimes, you may want to prevent some pages of your application be visible on search sites like Google,
+Bing, Yahoo or Duck Duck Go. The robots that index these sites will first analyse the
+`https://your-site.com/robots.txt` file to know what pages it is allowed to index.
+
+Rails creates this file for you on `/public` folder. By default, it allows search engines to index all
+pages of your application. If you want to block indexing on all pages of you application, use this:
+
+```
+User-agent: *
+Disallow: /
+```
+
+To block just specific pages, it's necessary to use a more complex syntax. Learn it on the
+[official documentation](http://www.robotstxt.org/robotstxt.html).
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 7381521658..32d1e2c6e7 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -173,6 +173,14 @@ $ git checkout -b my_new_branch
It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository.
+### Bundle install
+
+Install the required gems.
+
+```bash
+$ bundle install
+```
+
### Running an Application Against Your Local Branch
In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch:
@@ -368,6 +376,10 @@ A CHANGELOG entry should summarize what was changed and should end with author's
Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph.
+### Updating the Gemfile.lock
+
+Some changes requires the dependencies to be upgraded. In these cases make sure you run `bundle update` to get the right version of the dependency and commit the `Gemfile.lock` file within your changes.
+
### Sanity Check
You should not be the only person who looks at the code before you submit it.
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index a788dd48ad..6113a61f4c 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -242,6 +242,58 @@ The contents of the block, and therefore the string interpolation, is only
evaluated if debug is enabled. This performance savings is only really
noticeable with large amounts of logging, but it's a good practice to employ.
+
+Debugging with the `web-console` gem
+-------------------------------------
+
+The web console allows you to create an interactive ruby session in your browser. An interactive
+console is launched automatically in case on an error but can also be launched for debugging purposes
+by invoking `console` in a view or controller.
+
+For example in a view:
+
+```ruby
+# new.html.erb
+<%= console %>
+```
+
+Or in a controller:
+
+```ruby
+# posts_controller.rb
+class PostsController < ApplicationController
+ def new
+ console
+ @post = Post.new
+ end
+end
+```
+###config.web_console.whitelisted_ips
+
+By default the web console can only be accessed from localhost. `config.web_console.whitelisted_ips`
+lets you control which IPs have access to the console.
+
+For example, to allow access from both localhost and 192.168.0.100:
+
+```ruby
+# config/application.rb
+class Application < Rails::Application
+ config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.100 )
+end
+```
+
+To allow access from an entire network:
+
+```ruby
+# config/application.rb
+class Application < Rails::Application
+ config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.0/16 )
+end
+```
+
+Web console is a powerful tool so be careful who you open access to.
+
+
Debugging with the `byebug` gem
---------------------------------
@@ -544,7 +596,7 @@ This way an irb session will be started within the context you invoked it. But
be warned: this is an experimental feature.
The `var` method is the most convenient way to show variables and their values.
-Let's let `byebug` to help us with it.
+Let's let `byebug` help us with it.
```
(byebug) help var
@@ -832,7 +884,7 @@ application. Here is a list of useful plugins for debugging:
* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has
footnotes that give request information and link back to your source via
TextMate.
-* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query
+* [Query Trace](https://github.com/ruckus/active-record-query-trace/tree/master) Adds query
origin tracing to your logs.
* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin
not only runs "EXPLAIN" before each of your select queries in development, but
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 67032a31f5..7ae3640937 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -33,7 +33,7 @@
url: active_record_querying.html
description: This guide covers the database query interface provided by Active Record.
-
- name: Active Model basics
+ name: Active Model Basics
url: active_model_basics.html
description: This guide covers the use of model classes without Active Record.
work_in_progress: true
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 4c02c99cc7..90004c611b 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -687,7 +687,14 @@ class LabellingFormBuilder < ActionView::Helpers::FormBuilder
end
```
-If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option.
+If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option:
+
+```ruby
+def labeled_form_for(record, options = {}, &block)
+ options.merge! builder: LabellingFormBuilder
+ form_for record, options, &block
+end
+```
The form builder used also determines what happens when you do
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 5945d48e98..51b8a2ca5f 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -193,6 +193,9 @@ following in the `blog` directory:
$ bin/rails server
```
+TIP: If you are using Windows, you have to pass the scripts under the `bin`
+folder directly to the Ruby interpreter e.g. `ruby bin\rails server`.
+
TIP: Compiling CoffeeScript and JavaScript asset compression requires you
have a JavaScript runtime available on your system, in the absence
of a runtime you will see an `execjs` error during asset compilation.
@@ -908,6 +911,7 @@ And then finally, add the view for this action, located at
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
</tr>
<% end %>
</table>
@@ -2051,19 +2055,6 @@ resources:
* The [Ruby on Rails mailing list](http://groups.google.com/group/rubyonrails-talk)
* The [#rubyonrails](irc://irc.freenode.net/#rubyonrails) channel on irc.freenode.net
-Rails also comes with built-in help that you can generate using the rake
-command-line utility:
-
-* Running `rake doc:guides` will put a full copy of the Rails Guides in the
- `doc/guides` folder of your application. Open `doc/guides/index.html` in your
- web browser to explore the Guides.
-* Running `rake doc:rails` will put a full copy of the API documentation for
- Rails in the `doc/api` folder of your application. Open `doc/api/index.html`
- in your web browser to explore the API documentation.
-
-TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake
-task you need to install the Redcarpet and Nokogiri gems. Add it to your `Gemfile` and run
-`bundle install` and you're ready to go.
Configuration Gotchas
---------------------
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index fd2051e675..4dc4c5a660 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -588,6 +588,26 @@ you can look up the `books.index.title` value **inside** `app/views/books/index.
NOTE: Automatic translation scoping by partial is only available from the `translate` view helper method.
+"Lazy" lookup can also be used in controllers:
+
+```yaml
+en:
+ books:
+ create:
+ success: Book created!
+```
+
+This is useful for setting flash messages for instance:
+
+```ruby
+class BooksController < ApplicationController
+ def create
+ # ...
+ redirect_to books_url, notice: t('.success')
+ end
+end
+```
+
### Interpolation
In many cases you want to abstract your translations so that **variables can be interpolated into the translation**. For this reason the I18n API provides an interpolation feature.
@@ -809,7 +829,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co
| validation | with option | message | interpolation |
| ------------ | ------------------------- | ------------------------- | ------------- |
-| confirmation | - | :confirmation | - |
+| confirmation | - | :confirmation | attribute |
| acceptance | - | :accepted | - |
| presence | - | :blank | - |
| absence | - | :present | - |
@@ -829,6 +849,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co
| numericality | :equal_to | :equal_to | count |
| numericality | :less_than | :less_than | count |
| numericality | :less_than_or_equal_to | :less_than_or_equal_to | count |
+| numericality | :other_than | :other_than | count |
| numericality | :only_integer | :not_an_integer | - |
| numericality | :odd | :odd | - |
| numericality | :even | :even | - |
@@ -1009,7 +1030,7 @@ In other contexts you might want to change this behavior, though. E.g. the defau
module I18n
class JustRaiseExceptionHandler < ExceptionHandler
def call(exception, locale, key, options)
- if exception.is_a?(MissingTranslation)
+ if exception.is_a?(MissingTranslationData)
raise exception.to_exception
else
super
@@ -1026,7 +1047,7 @@ This would re-raise only the `MissingTranslationData` exception, passing all oth
However, if you are using `I18n::Backend::Pluralization` this handler will also raise `I18n::MissingTranslationData: translation missing: en.i18n.plural.rule` exception that should normally be ignored to fall back to the default pluralization rule for English locale. To avoid this you may use additional check for translation key:
```ruby
-if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
+if exception.is_a?(MissingTranslationData) && key.to_s != 'i18n.plural.rule'
raise exception.to_exception
else
super
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index eb3c188d38..329d501ce0 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -316,12 +316,13 @@ NOTE: Unless overridden, your response returned from this render option will be
#### Options for `render`
-Calls to the `render` method generally accept four options:
+Calls to the `render` method generally accept five options:
* `:content_type`
* `:layout`
* `:location`
* `:status`
+* `:formats`
##### The `:content_type` Option
@@ -430,6 +431,15 @@ Rails understands both numeric status codes and the corresponding symbols shown
NOTE: If you try to render content along with a non-content status code
(100-199, 204, 205 or 304), it will be dropped from the response.
+##### The `:formats` Option
+
+Rails uses the format specified in request (or `:html` by default). You can change this adding the `:formats` option with a symbol or an array:
+
+```ruby
+render formats: :xml
+render formats: [:json, :xml]
+```
+
#### Finding Layouts
To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.
@@ -1039,7 +1049,7 @@ As you already could see from the previous sections of this guide, `yield` is a
<p>
Name contains: <%= f.text_field :name_contains %>
</p>
- <%= end %>
+ <% end %>
```
* `roles/index.html.erb`
@@ -1049,7 +1059,7 @@ As you already could see from the previous sections of this guide, `yield` is a
<p>
Title contains: <%= f.text_field :title_contains %>
</p>
- <%= end %>
+ <% end %>
```
* `shared/_search_filters.html.erb`
@@ -1114,6 +1124,36 @@ You can also pass local variables into partials, making them even more powerful
Although the same partial will be rendered into both views, Action View's submit helper will return "Create Zone" for the new action and "Update Zone" for the edit action.
+To pass a local variable to a partial in only specific cases use the `local_assigns`.
+
+* `index.html.erb`
+
+ ```erb
+ <%= render user.articles %>
+ ```
+
+* `show.html.erb`
+
+ ```erb
+ <%= render article, full: true %>
+ ```
+
+* `_articles.html.erb`
+
+ ```erb
+ <%= content_tag_for :article, article do |article| %>
+ <h2><%= article.title %></h2>
+
+ <% if local_assigns[:full] %>
+ <%= simple_format article.body %>
+ <% else %>
+ <%= truncate article.body %>
+ <% end %>
+ <% end %>
+ ```
+
+This way it is possible to use the partial without the need to declare all local variables.
+
Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the `:object` option:
```erb
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 94cfcf12b7..14bc75aa7d 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -120,9 +120,9 @@ user_<%= n %>:
Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps:
-* Remove any existing data from the table corresponding to the fixture
-* Load the fixture data into the table
-* Dump the fixture data into a method in case you want to access it directly
+1. Remove any existing data from the table corresponding to the fixture
+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))
@@ -270,6 +270,8 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
+This will run all test methods from the test case.
+
You can also run a particular test method from the test case by running the test and providing the `test method name`.
```bash
@@ -281,8 +283,6 @@ Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
-This will run all test methods from the test case.
-
The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary.
#### Your first failing test
@@ -347,7 +347,11 @@ Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
-Now, if you noticed, we first wrote a test which fails for a desired functionality, then we wrote some code which adds the functionality and finally we ensured that our test passes. This approach to software development is referred to as _Test-Driven Development_ (TDD).
+Now, if you noticed, we first wrote a test which fails for a desired
+functionality, then we wrote some code which adds the functionality and finally
+we ensured that our test passes. This approach to software development is
+referred to as
+[_Test-Driven Development_ (TDD)](http://c2.com/cgi/wiki?TestDrivenDevelopment).
#### What an error looks like
@@ -379,7 +383,11 @@ NameError: undefined local variable or method `some_undefined_variable' for #<Ar
Notice the 'E' in the output. It denotes a test with error.
-NOTE: The execution of each test method stops as soon as any error or an assertion failure is encountered, and the test suite continues with the next method. All test methods are executed in alphabetical order.
+NOTE: The execution of each test method stops as soon as any error or an
+assertion failure is encountered, and the test suite continues with the next
+method. All test methods are executed in random order. The
+[`config.active_support.test_order` option](http://edgeguides.rubyonrails.org/configuring.html#configuring-active-support)
+can be used to configure test order.
When a test fails you are presented with the corresponding backtrace. By default
Rails filters that backtrace and will only print lines relevant to your
@@ -480,21 +488,28 @@ In the `test_should_get_index` test, Rails simulates a request on the action cal
The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
-* The action of the controller you are requesting. This can be in the form of a string or a symbol.
-* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables).
-* An optional hash of session variables to pass along with the request.
-* An optional hash of flash values.
+* The action of the controller you are requesting.
+ This can be in the form of a string or a symbol.
+
+* `params`: option with a hash of request parameters to pass into the action
+ (e.g. query string parameters or article variables).
+
+* `session`: option with a hash of session variables to pass along with the request.
+
+* `flash`: option with a hash of flash values.
+
+All the keyword arguments are optional.
Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting a `user_id` of 5 in the session:
```ruby
-get(:show, {'id' => "12"}, {'user_id' => 5})
+get(:show, params: { 'id' => "12" }, session: { 'user_id' => 5 })
```
Another example: Calling the `:view` action, passing an `id` of 12 as the `params`, this time with no session, but with a flash message.
```ruby
-get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})
+get(:view, params: { 'id' => '12' }, flash: { 'message' => 'booya!' })
```
NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so.
@@ -504,7 +519,7 @@ Let us modify `test_should_create_article` test in `articles_controller_test.rb`
```ruby
test "should create article" do
assert_difference('Article.count') do
- post :create, article: {title: 'Some title'}
+ post :create, params: { article: { title: 'Some title' } }
end
assert_redirected_to article_path(assigns(:article))
@@ -530,11 +545,12 @@ NOTE: Functional tests do not verify whether the specified request type is accep
### Testing XHR (AJAX) requests
-`xhr` accepts method (listed in the section above), action name and parameters:
+To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`,
+`patch`, `put`, and `delete` methods:
```ruby
test "ajax request responds with no layout" do
- xhr :get, :show, id: articles(:first).id
+ get :show, params: { id: articles(:first).id }, xhr: true
assert_template :index
assert_template layout: nil
@@ -638,7 +654,7 @@ Let's start by adding this assertion to our `test_should_create_article` test:
```ruby
test "should create article" do
assert_difference('Article.count') do
- post :create, article: {title: 'Some title'}
+ post :create, params: { article: { title: 'Some title' } }
end
assert_redirected_to article_path(assigns(:article))
@@ -708,7 +724,7 @@ Let's write a test for the `:show` action:
```ruby
test "should show article" do
article = articles(:one)
- get :show, id: article.id
+ get :show, params: { id: article.id }
assert_response :success
end
```
@@ -721,7 +737,7 @@ How about deleting an existing Article?
test "should destroy article" do
article = articles(:one)
assert_difference('Article.count', -1) do
- delete :destroy, id: article.id
+ delete :destroy, params: { id: article.id }
end
assert_redirected_to articles_path
@@ -733,7 +749,7 @@ We can also add a test for updating an existing Article.
```ruby
test "should update article" do
article = articles(:one)
- patch :update, id: article.id, article: {title: "updated"}
+ patch :update, params: { id: article.id, article: { title: "updated" } }
assert_redirected_to article_path(assigns(:article))
end
```
@@ -759,20 +775,20 @@ class ArticlesControllerTest < ActionController::TestCase
test "should show article" do
# Reuse the @article instance variable from setup
- get :show, id: @article.id
+ get :show, params: { id: @article.id }
assert_response :success
end
test "should destroy article" do
assert_difference('Article.count', -1) do
- delete :destroy, id: @article.id
+ delete :destroy, params: { id: @article.id }
end
assert_redirected_to articles_path
end
test "should update article" do
- patch :update, id: @article.id, article: {title: "updated"}
+ patch :update, params: { id: @article.id, article: { title: "updated" } }
assert_redirected_to article_path(assigns(:article))
end
end
@@ -1026,7 +1042,8 @@ test "can create an article" do
assert_response :success
assert_template "articles/new", partial: "articles/_form"
- post "/articles", article: {title: "can create", body: "article successfully."}
+ post "/articles",
+ params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
assert_response :success
@@ -1042,7 +1059,8 @@ We start by calling the `:new` action on our Articles controller. This response
After this we make a post request to the `:create` action of our Articles controller:
```ruby
-post "/articles", article: {title: "can create", body: "article successfully."}
+post "/articles",
+ params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
```
@@ -1147,7 +1165,7 @@ require 'test_helper'
class UserControllerTest < ActionController::TestCase
test "invite friend" do
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
- post :invite_friend, email: 'friend@example.com'
+ post :invite_friend, params: { email: 'friend@example.com' }
end
invite_email = ActionMailer::Base.deliveries.last
@@ -1201,7 +1219,7 @@ within a model:
```ruby
require 'test_helper'
-class ProductTest < ActiveSupport::TestCase
+class ProductTest < ActiveJob::TestCase
test 'billing job scheduling' do
assert_enqueued_with(job: BillingJob) do
product.charge(account)
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index d7fbe575c2..20b90bdba0 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -276,6 +276,22 @@ class Notifier < ActionMailer::Base
end
```
+### Foreign Key Support
+
+The migration DSL has been expanded to support foreign key definitions. If
+you've been using the Foreigner gem, you might want to consider removing it.
+Note that the foreign key support of Rails is a subset of Foreigner. This means
+that not every Foreigner definition can be fully replaced by it's Rails
+migration DSL counterpart.
+
+The migration procedure is as follows:
+
+1. remove `gem "foreigner"` from the Gemfile.
+2. run `bundle install`.
+3. run `bin/rake db:schema:dump`.
+4. make sure that `db/schema.rb` contains every foreign key definition with
+the necessary options.
+
Upgrading from Rails 4.0 to Rails 4.1
-------------------------------------
@@ -534,7 +550,7 @@ module FixtureFileHelpers
Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
end
end
-ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
```
### I18n enforcing available locales
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 367abddcfa..e3856a285a 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -191,6 +191,34 @@ $(document).ready ->
Obviously, you'll want to be a bit more sophisticated than that, but it's a
start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
+Another possibility is returning javascript directly from the server side on
+remote calls:
+
+```ruby
+# articles_controller
+def create
+ respond_to do |format|
+ if @article.save
+ format.html { ... }
+ format.js do
+ render js: <<-endjs
+ alert('Article saved successfully!');
+ window.location = '#{article_path(@article)}';
+ endjs
+ end
+ else
+ format.html { ... }
+ format.js do
+ render js: "alert('There are empty fields in the form!');"
+ end
+ end
+ end
+end
+```
+
+NOTE: If javascript is disabled in the user browser, `format.html { ... }`
+block should be executed as fallback.
+
### form_tag
[`form_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag)
@@ -357,7 +385,7 @@ This gem uses Ajax to speed up page rendering in most applications.
Turbolinks attaches a click handler to all `<a>` on the page. If your browser
supports
-[PushState](https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history#The_pushState(\).C2.A0method),
+[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState()_method),
Turbolinks will make an Ajax request for the page, parse the response, and
replace the entire `<body>` of the page with the `<body>` of the response. It
will then use PushState to change the URL to the correct one, preserving
diff --git a/rails.gemspec b/rails.gemspec
index b3143e6fe1..1398169922 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = ['README.md'] + Dir['guides/**/*'] - Dir['guides/output/**/*']
+ s.files = ['README.md']
s.add_dependency 'activesupport', version
s.add_dependency 'actionpack', version
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 30b86db8bb..74d1ca3bd8 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,33 @@
+* `delete` operations in configurations are run last in order to eliminate
+ 'No such middleware' errors when `insert_before` or `insert_after` are added
+ after the `delete` operation for the middleware being deleted.
+
+ Fixes: #16433.
+
+ *Guo Xiang Tan*
+
+* Newly generated applications get a `README.md` in Markdown.
+
+ *Xavier Noria*
+
+* Remove the documentation tasks `doc:app`, `doc:rails`, and `doc:guides`.
+
+ *Xavier Noria*
+
+* Force generated routes to be inserted into routes.rb
+
+ *Andrew White*
+
+* Don't remove all line endings from routes.rb when revoking scaffold.
+
+ Fixes #15913.
+
+ *Andrew White*
+
+* Rename `--skip-test-unit` option to `--skip-test` in app generator
+
+ *Melanie Gilman*
+
* Add the `method_source` gem to the default Gemfile for apps
*Sean Griffin*
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index 4d49244807..a082932632 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -152,19 +152,5 @@ module Rails
File.read('RAILS_VERSION').strip
end
end
-
- class AppTask < Task
- def component_root_dir(gem_name)
- $:.grep(%r{#{gem_name}[\w.-]*/lib\z}).first[0..-5]
- end
-
- def api_dir
- 'doc/api'
- end
-
- def rails_version
- Rails::VERSION::STRING
- end
- end
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 8da73db821..b11815e013 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -428,8 +428,8 @@ module Rails
# and the order specified by the +railties_order+ config.
#
# While when running initializers we need engines in reverse
- # order here when copying migrations from railties we need then in the same
- # order as given by +railties_order+
+ # order here when copying migrations from railties we need them in the same
+ # order as given by +railties_order+.
def migration_railties # :nodoc:
ordered_railties.flatten - [self]
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 2821c8d757..dc3ec4274b 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -6,7 +6,7 @@ require 'rails/source_annotation_extractor'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :assets, :autoflush_log,
+ attr_accessor :allow_concurrency, :asset_host, :autoflush_log,
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
@@ -49,21 +49,6 @@ module Rails
@secret_token = nil
@secret_key_base = nil
@x = Custom.new
-
- @assets = ActiveSupport::OrderedOptions.new
- @assets.enabled = true
- @assets.paths = []
- @assets.precompile = [ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) },
- /(?:\/|\\|\A)application\.(css|js)$/ ]
- @assets.prefix = "/assets"
- @assets.version = '1.0'
- @assets.debug = false
- @assets.compile = true
- @assets.digest = false
- @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/#{Rails.env}/" ]
- @assets.js_compressor = nil
- @assets.css_compressor = nil
- @assets.logger = nil
end
def encoding=(value)
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 7a1bb1e25c..0599e988d9 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -108,6 +108,13 @@ module Rails
ActionDispatch::Reloader.to_cleanup(&callback)
end
end
+
+ # Disable dependency loading during request cycle
+ initializer :disable_dependency_loading do
+ if config.eager_load && config.cache_classes
+ ActiveSupport::Dependencies.unhook!
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 35a815a34f..5d37a2b699 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -79,7 +79,7 @@ module Rails
end
if defined?(console::ExtendCommandBundle)
- console::ExtendCommandBundle.send :include, Rails::ConsoleMethods
+ console::ExtendCommandBundle.include(Rails::ConsoleMethods)
end
console.start
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index bc8f1a8dea..546d3725d8 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -91,10 +91,6 @@ module Rails
Hash.new(middlewares)
end
- def log_path
- "log/#{options[:environment]}.log"
- end
-
def default_options
super.merge({
Port: 3000,
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index f99cec04c5..76364cea8f 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -35,6 +35,7 @@ module Rails
class MiddlewareStackProxy
def initialize
@operations = []
+ @delete_operations = []
end
def insert_before(*args, &block)
@@ -56,7 +57,7 @@ module Rails
end
def delete(*args, &block)
- @operations << [__method__, args, block]
+ @delete_operations << [__method__, args, block]
end
def unshift(*args, &block)
@@ -64,9 +65,10 @@ module Rails
end
def merge_into(other) #:nodoc:
- @operations.each do |operation, args, block|
+ (@operations + @delete_operations).each do |operation, args, block|
other.send(operation, *args, &block)
end
+
other
end
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index a338f31f15..e1d5caf790 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -217,7 +217,7 @@ module Rails
# <tt>url_helpers</tt> from <tt>MyEngine::Engine.routes</tt>.
#
# The next thing that changes in isolated engines is the behavior of routes. Normally, when you namespace
- # your controllers, you also need to do namespace all your routes. With an isolated engine,
+ # your controllers, you also need to namespace all your routes. With an isolated engine,
# the namespace is applied by default, so you can ignore it in routes:
#
# MyEngine::Engine.routes.draw do
@@ -296,7 +296,7 @@ module Rails
# helper MyEngine::SharedEngineHelper
# end
#
- # If you want to include all of the engine's helpers, you can use #helper method on an engine's
+ # If you want to include all of the engine's helpers, you can use the #helper method on an engine's
# instance:
#
# class ApplicationController < ActionController::Base
@@ -312,7 +312,7 @@ module Rails
# Engines can have their own migrations. The default path for migrations is exactly the same
# as in application: <tt>db/migrate</tt>
#
- # To use engine's migrations in application you can use rake task, which copies them to
+ # To use engine's migrations in application you can use the rake task below, which copies them to
# application's dir:
#
# rake ENGINE_NAME:install:migrations
@@ -328,7 +328,7 @@ module Rails
#
# == Loading priority
#
- # In order to change engine's priority you can use +config.railties_order+ in main application.
+ # In order to change engine's priority you can use +config.railties_order+ in the main application.
# It will affect the priority of loading views, helpers, assets and all the other files
# related to engine or application.
#
@@ -484,7 +484,7 @@ module Rails
helpers = Module.new
all = ActionController::Base.all_helpers_from_path(helpers_paths)
ActionController::Base.modules_for_helpers(all).each do |mod|
- helpers.send(:include, mod)
+ helpers.include(mod)
end
helpers
end
@@ -599,12 +599,6 @@ module Rails
end
end
- initializer :append_assets_path, group: :all do |app|
- app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
- app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
- app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
- end
-
initializer :prepend_helpers_path do |app|
if !isolated? || (app == self)
app.config.helpers_paths.unshift(*paths["app/helpers"].existent)
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index e25c364178..db8b184213 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -156,7 +156,7 @@ module Rails
args << "--help" if args.empty? && klass.arguments.any?(&:required?)
klass.start(args, config)
else
- options = sorted_groups.map(&:last).flatten
+ options = sorted_groups.flat_map(&:last)
suggestions = options.sort_by {|suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
msg = "Could not find generator '#{namespace}'. "
msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.join(" or ") }\n"
@@ -286,7 +286,7 @@ module Rails
d[m] = x
end
- return x
+ x
end
# Prints a list of generators.
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index e35015f630..c1bc646c65 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -218,10 +218,10 @@ module Rails
# route "root 'welcome#index'"
def route(routing_code)
log :route, routing_code
- sentinel = /\.routes\.draw do\s*$/
+ sentinel = /\.routes\.draw do\s*\n/m
in_root do
- inject_into_file 'config/routes.rb', "\n #{routing_code}", { after: sentinel, verbose: false }
+ inject_into_file 'config/routes.rb', " #{routing_code}", { after: sentinel, verbose: false, force: true }
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 71186891a3..253272c7dd 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -69,8 +69,8 @@ module Rails
class_option :skip_turbolinks, type: :boolean, default: false,
desc: 'Skip turbolinks gem'
- class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false,
- desc: 'Skip Test::Unit files'
+ class_option :skip_test, type: :boolean, aliases: '-T', default: false,
+ desc: 'Skip test files'
class_option :rc, type: :string, default: false,
desc: "Path to file containing extra configuration options for rails command"
@@ -113,7 +113,6 @@ module Rails
assets_gemfile_entry,
javascript_gemfile_entry,
jbuilder_gemfile_entry,
- sdoc_gemfile_entry,
psych_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -127,7 +126,7 @@ module Rails
def builder
@builder ||= begin
builder_class = get_builder_class
- builder_class.send(:include, ActionMethods)
+ builder_class.include(ActionMethods)
builder_class.new(self)
end
end
@@ -168,7 +167,7 @@ module Rails
end
def include_all_railties?
- options.values_at(:skip_active_record, :skip_action_mailer, :skip_test_unit, :skip_sprockets).none?
+ options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets).none?
end
def comment_if(value)
@@ -265,11 +264,6 @@ module Rails
GemfileEntry.version('jbuilder', '~> 2.0', comment)
end
- def sdoc_gemfile_entry
- comment = 'bundle exec rake doc:rails generates the API under doc/api.'
- GemfileEntry.new('sdoc', '~> 0.4.0', comment, group: :doc)
- end
-
def coffee_gemfile_entry
comment = 'Use CoffeeScript for .coffee assets and views'
if options.dev? || options.edge?
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 7d27321610..65563aa6db 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -6,7 +6,7 @@ module Erb # :nodoc:
argument :actions, type: :array, default: [], banner: "method method"
def copy_view_files
- view_base_path = File.join("app/views", class_path, file_name)
+ view_base_path = File.join("app/views", class_path, file_name + '_mailer')
empty_directory view_base_path
if self.behavior == :invoke
@@ -31,6 +31,10 @@ module Erb # :nodoc:
def formats
[:text, :html]
end
+
+ def file_name
+ @_file_name ||= super.gsub(/\_mailer/i, '')
+ end
end
end
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index cd388e590a..51e6d68bf0 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -3,8 +3,8 @@ require 'rails/generators/actions/create_migration'
module Rails
module Generators
- # Holds common methods for migrations. It assumes that migrations has the
- # [0-9]*_name format and can be used by another frameworks (like Sequel)
+ # Holds common methods for migrations. It assumes that migrations have the
+ # [0-9]*_name format and can be used by other frameworks (like Sequel)
# just by implementing the next migration version method.
module Migration
extend ActiveSupport::Concern
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 0550bf113e..977f5a1c03 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -38,7 +38,7 @@ module Rails
end
def readme
- copy_file "README.rdoc", "README.rdoc"
+ copy_file "README.md", "README.md"
end
def gemfile
@@ -125,6 +125,7 @@ module Rails
def test
empty_directory_with_keep_file 'test/fixtures'
+ empty_directory_with_keep_file 'test/fixtures/files'
empty_directory_with_keep_file 'test/controllers'
empty_directory_with_keep_file 'test/mailers'
empty_directory_with_keep_file 'test/models'
@@ -234,7 +235,7 @@ module Rails
end
def create_test_files
- build(:test) unless options[:skip_test_unit]
+ build(:test) unless options[:skip_test]
end
def create_tmp_files
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 143673f711..82a0315379 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -42,8 +42,8 @@ group :development, :test do
gem 'method_source'
<% end -%>
end
-
<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%>
+
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/README.rdoc b/railties/lib/rails/generators/rails/app/templates/README.md
index dd4e97e22e..55e144da18 100644
--- a/railties/lib/rails/generators/rails/app/templates/README.rdoc
+++ b/railties/lib/rails/generators/rails/app/templates/README.md
@@ -1,4 +1,4 @@
-== README
+## README
This README would normally document whatever steps are necessary to get the
application up and running.
@@ -22,7 +22,3 @@ Things you may want to cover:
* Deployment instructions
* ...
-
-
-Please feel free to use a different markup language if you do not plan to run
-<tt>rake doc:app</tt>.
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index f9cd5b3483..0cdd2788d0 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -6,9 +6,8 @@
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
+ * files in this directory. It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 20ee229e97..a2661bfb51 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -12,7 +12,7 @@ require "action_controller/railtie"
<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
-<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
+<%= comment_if :skip_test %>require "rails/test_unit/railtie"
<% end -%>
# Require the gems listed in Gemfile, including any gems
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
new file mode 100644
index 0000000000..ea930f54da
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,6 @@
+## Change renderer defaults here.
+#
+# ApplicationController.renderer.defaults.merge!(
+# http_host: 'example.org',
+# https: false
+# )
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index f2110c2c70..94f612c3dd 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -11,6 +11,6 @@ end
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
-# self.include_root_in_json = true
+# self.include_root_in_json = true
# end
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index b8f7845896..11daa5c3cb 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -22,7 +22,7 @@ Description:
If you pass a namespaced model name (e.g. admin/account or Admin::Account)
then the generator will create a module with a table_name_prefix method
- to prefix the model's table name with the module name (e.g. admin_account)
+ to prefix the model's table name with the module name (e.g. admin_accounts)
Available field types:
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 1c270dd7d4..68c3829515 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -18,14 +18,14 @@ module Rails
def app
if mountable?
directory 'app'
- empty_directory_with_keep_file "app/assets/images/#{name}"
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
elsif full?
empty_directory_with_keep_file 'app/models'
empty_directory_with_keep_file 'app/controllers'
empty_directory_with_keep_file 'app/views'
empty_directory_with_keep_file 'app/helpers'
empty_directory_with_keep_file 'app/mailers'
- empty_directory_with_keep_file "app/assets/images/#{name}"
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
end
end
@@ -50,10 +50,10 @@ module Rails
end
def lib
- template "lib/%name%.rb"
- template "lib/tasks/%name%_tasks.rake"
- template "lib/%name%/version.rb"
- template "lib/%name%/engine.rb" if engine?
+ template "lib/%namespaced_name%.rb"
+ template "lib/tasks/%namespaced_name%_tasks.rake"
+ template "lib/%namespaced_name%/version.rb"
+ template "lib/%namespaced_name%/engine.rb" if engine?
end
def config
@@ -62,7 +62,7 @@ module Rails
def test
template "test/test_helper.rb"
- template "test/%name%_test.rb"
+ template "test/%namespaced_name%_test.rb"
append_file "Rakefile", <<-EOF
#{rakefile_test_tasks}
@@ -117,9 +117,9 @@ task default: :test
def stylesheets
if mountable?
copy_file "rails/stylesheets.css",
- "app/assets/stylesheets/#{name}/application.css"
+ "app/assets/stylesheets/#{namespaced_name}/application.css"
elsif full?
- empty_directory_with_keep_file "app/assets/stylesheets/#{name}"
+ empty_directory_with_keep_file "app/assets/stylesheets/#{namespaced_name}"
end
end
@@ -128,9 +128,9 @@ task default: :test
if mountable?
template "rails/javascripts.js",
- "app/assets/javascripts/#{name}/application.js"
+ "app/assets/javascripts/#{namespaced_name}/application.js"
elsif full?
- empty_directory_with_keep_file "app/assets/javascripts/#{name}"
+ empty_directory_with_keep_file "app/assets/javascripts/#{namespaced_name}"
end
end
@@ -226,7 +226,7 @@ task default: :test
end
def create_test_files
- build(:test) unless options[:skip_test_unit]
+ build(:test) unless options[:skip_test]
end
def create_test_dummy_files
@@ -256,6 +256,14 @@ task default: :test
end
end
+ def underscored_name
+ @underscored_name ||= original_name.underscore
+ end
+
+ def namespaced_name
+ @namespaced_name ||= name.gsub('-', '/')
+ end
+
protected
def app_templates_dir
@@ -294,7 +302,7 @@ task default: :test
end
def with_dummy_app?
- options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy'
+ options[:skip_test].blank? || options[:dummy_path] != 'test/dummy'
end
def self.banner
@@ -305,6 +313,27 @@ task default: :test
@original_name ||= File.basename(destination_root)
end
+ def modules
+ @modules ||= namespaced_name.camelize.split("::")
+ end
+
+ def wrap_in_modules(unwrapped_code)
+ unwrapped_code = "#{unwrapped_code}".strip.gsub(/\W$\n/, '')
+ modules.reverse.inject(unwrapped_code) do |content, mod|
+ str = "module #{mod}\n"
+ str += content.lines.map { |line| " #{line}" }.join
+ str += content.present? ? "\nend" : "end"
+ end
+ end
+
+ def camelized_modules
+ @camelized_modules ||= namespaced_name.camelize
+ end
+
+ def humanized
+ @humanized ||= original_name.underscore.humanize
+ end
+
def camelized
@camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize
end
@@ -328,8 +357,10 @@ task default: :test
end
def valid_const?
- if original_name =~ /[^0-9a-zA-Z_]+/
- raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters."
+ if original_name =~ /-\d/
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which does not contain a namespace starting with numeric characters."
+ elsif original_name =~ /[^\w-]+/
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters."
elsif camelized =~ /^\d/
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(name)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
index 919c349470..f8ece4fe73 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
@@ -1,21 +1,21 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
-require "<%= name %>/version"
+require "<%= namespaced_name %>/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "<%= name %>"
- s.version = <%= camelized %>::VERSION
+ s.version = <%= camelized_modules %>::VERSION
s.authors = ["<%= author %>"]
s.email = ["<%= email %>"]
s.homepage = "TODO"
- s.summary = "TODO: Summary of <%= camelized %>."
- s.description = "TODO: Description of <%= camelized %>."
+ s.summary = "TODO: Summary of <%= camelized_modules %>."
+ s.description = "TODO: Description of <%= camelized_modules %>."
s.license = "MIT"
s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
-<% unless options.skip_test_unit? -%>
+<% unless options.skip_test? -%>
s.test_files = Dir["test/**/*"]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index f325455bac..2c91c6a0ea 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -41,7 +41,7 @@ end
# To use a debugger
# gem 'byebug', group: [:development, :test]
<% end -%>
-
<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%>
+
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
index 301d647731..25983ca5da 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
+++ b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
@@ -1,3 +1,3 @@
-= <%= camelized %>
+= <%= camelized_modules %>
This project rocks and uses MIT-LICENSE. \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
index c338a0bdb1..bda55bae29 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
@@ -8,7 +8,7 @@ require 'rdoc/task'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
- rdoc.title = '<%= camelized %>'
+ rdoc.title = '<%= camelized_modules %>'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
index 448ad7f989..7157e48c42 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
@@ -1,4 +1,5 @@
-module <%= camelized %>
+<%= wrap_in_modules <<-rb.strip_heredoc
class ApplicationController < ActionController::Base
end
-end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt
deleted file mode 100644
index 40ae9f52c2..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt
+++ /dev/null
@@ -1,4 +0,0 @@
-module <%= camelized %>
- module ApplicationHelper
- end
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt
new file mode 100644
index 0000000000..25d692732d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt
@@ -0,0 +1,5 @@
+<%= wrap_in_modules <<-rb.strip_heredoc
+ module ApplicationHelper
+ end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt
deleted file mode 100644
index 1d380420b4..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title><%= camelized %></title>
- <%%= stylesheet_link_tag "<%= name %>/application", media: "all" %>
- <%%= javascript_include_tag "<%= name %>/application" %>
- <%%= csrf_meta_tags %>
-</head>
-<body>
-
-<%%= yield %>
-
-</body>
-</html>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt
new file mode 100644
index 0000000000..6bc480161d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title><%= humanized %></title>
+ <%%= stylesheet_link_tag "<%= namespaced_name %>/application", media: "all" %>
+ <%%= javascript_include_tag "<%= namespaced_name %>/application" %>
+ <%%= csrf_meta_tags %>
+</head>
+<body>
+
+<%%= yield %>
+
+</body>
+</html>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
index c3314d7e68..3edaac35c9 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
@@ -1,7 +1,7 @@
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
-ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__)
+ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
index 8e158d5831..154452bfe5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
@@ -1,5 +1,5 @@
<% if mountable? -%>
-<%= camelized %>::Engine.routes.draw do
+<%= camelized_modules %>::Engine.routes.draw do
<% else -%>
Rails.application.routes.draw do
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore
index 086d87818a..d524fcbc4e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/gitignore
+++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore
@@ -1,10 +1,10 @@
.bundle/
log/*.log
pkg/
-<% unless options[:skip_test_unit] && options[:dummy_path] == 'test/dummy' -%>
+<% unless options[:skip_test] && options[:dummy_path] == 'test/dummy' -%>
<%= dummy_path %>/db/*.sqlite3
<%= dummy_path %>/db/*.sqlite3-journal
<%= dummy_path %>/log/*.log
<%= dummy_path %>/tmp/
<%= dummy_path %>/.sass-cache
-<% end -%> \ No newline at end of file
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb
deleted file mode 100644
index 40c074cced..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-<% if engine? -%>
-require "<%= name %>/engine"
-
-<% end -%>
-module <%= camelized %>
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb
deleted file mode 100644
index 967668fe66..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module <%= camelized %>
- class Engine < ::Rails::Engine
-<% if mountable? -%>
- isolate_namespace <%= camelized %>
-<% end -%>
- end
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb
deleted file mode 100644
index ef07ef2e19..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module <%= camelized %>
- VERSION = "0.0.1"
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
new file mode 100644
index 0000000000..40b1c4cee7
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
@@ -0,0 +1,5 @@
+<% if engine? -%>
+require "<%= namespaced_name %>/engine"
+
+<% end -%>
+<%= wrap_in_modules "# Your code goes here..." %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
new file mode 100644
index 0000000000..17afd52177
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
@@ -0,0 +1,6 @@
+<%= wrap_in_modules <<-rb.strip_heredoc
+ class Engine < ::Rails::Engine
+ #{mountable? ? ' isolate_namespace ' + camelized_modules : ' '}
+ end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
new file mode 100644
index 0000000000..d257295988
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
@@ -0,0 +1 @@
+<%= wrap_in_modules 'VERSION = "0.0.1"' %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake
index 7121f5ae23..88a2c4120f 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake
@@ -1,4 +1,4 @@
# desc "Explaining what the task does"
-# task :<%= name %> do
+# task :<%= underscored_name %> do
# # Task goes here
# end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
index 3a9a7e5437..b1038c839e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
@@ -9,10 +9,10 @@ require "action_controller/railtie"
<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
-<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
+<%= comment_if :skip_test %>require "rails/test_unit/railtie"
<% end -%>
Bundler.require(*Rails.groups)
-require "<%= name %>"
+require "<%= namespaced_name %>"
<%= application_definition %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
index 730ee31c3d..673de44108 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
@@ -1,4 +1,4 @@
Rails.application.routes.draw do
- mount <%= camelized %>::Engine => "/<%= name %>"
+ mount <%= camelized_modules %>::Engine => "/<%= name %>"
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
index f9cd5b3483..0cdd2788d0 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
@@ -6,9 +6,8 @@
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
+ * files in this directory. It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb
deleted file mode 100644
index 0a8bbd4aaf..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class <%= camelized %>Test < ActiveSupport::TestCase
- test "truth" do
- assert_kind_of Module, <%= camelized %>
- end
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb
new file mode 100644
index 0000000000..1ee05d7871
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class <%= camelized_modules %>::Test < ActiveSupport::TestCase
+ test "truth" do
+ assert_kind_of Module, <%= camelized_modules %>
+ end
+end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index bf3da1fc4d..0852ffce9a 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -20,4 +20,6 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
+ ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "files"
+ ActiveSupport::TestCase.fixtures :all
end
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index 1ae7000299..69af1e8307 100644
--- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
@@ -4,6 +4,7 @@ body, p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
+ margin: 33px;
}
pre {
@@ -16,6 +17,16 @@ a { color: #000; }
a:visited { color: #666; }
a:hover { color: #fff; background-color:#000; }
+th {
+ padding-bottom: 5px;
+}
+
+td {
+ padding-bottom: 7px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
div.field, div.actions {
margin-bottom: 10px;
}
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 3f84d76ae0..9c2037783e 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -8,7 +8,7 @@ module Rails
module ResourceHelpers # :nodoc:
def self.included(base) #:nodoc:
- base.send :include, Rails::Generators::ModelHelpers
+ base.include(Rails::Generators::ModelHelpers)
base.class_option :model_name, type: :string, desc: "ModelName to be used"
end
diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
index ba131da79d..343c8a3949 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -16,6 +16,11 @@ module TestUnit # :nodoc:
def create_preview_files
template "preview.rb", File.join('test/mailers/previews', class_path, "#{file_name}_mailer_preview.rb")
end
+
+ protected
+ def file_name
+ @_file_name ||= super.gsub(/\_mailer/i, '')
+ end
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
index 3cee517db3..a2f2d30de5 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
@@ -4,7 +4,7 @@ require 'test_helper'
class <%= class_name %>MailerTest < ActionMailer::TestCase
<% actions.each do |action| -%>
test "<%= action %>" do
- mail = <%= class_name %>.<%= action %>
+ mail = <%= class_name %>Mailer.<%= action %>
assert_equal <%= action.to_s.humanize.inspect %>, mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 18bd1ece9d..8d825ae7b0 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -19,30 +19,30 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
+ post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should show <%= singular_table_name %>" do
- get :show, id: <%= "@#{singular_table_name}" %>
+ get :show, params: { id: <%= "@#{singular_table_name}" %> }
assert_response :success
end
test "should get edit" do
- get :edit, id: <%= "@#{singular_table_name}" %>
+ get :edit, params: { id: <%= "@#{singular_table_name}" %> }
assert_response :success
end
test "should update <%= singular_table_name %>" do
- patch :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
+ patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete :destroy, id: <%= "@#{singular_table_name}" %>
+ delete :destroy, params: { id: <%= "@#{singular_table_name}" %> }
end
assert_redirected_to <%= index_helper %>_path
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index fd2ea274e1..c9700e1cd7 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/testing/stream'
require 'active_support/concern'
require 'rails/generators'
@@ -10,6 +11,7 @@ module Rails
module Testing
module Behaviour
extend ActiveSupport::Concern
+ include ActiveSupport::Testing::Stream
included do
class_attribute :destination_root, :current_path, :generator_class, :default_arguments
@@ -101,22 +103,6 @@ module Rails
Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
end
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
end
end
end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index 2f82d1285d..945fbdb3e2 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -3,7 +3,6 @@ require 'rake'
# Load Rails Rakefile extensions
%w(
annotations
- documentation
framework
log
middleware
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
deleted file mode 100644
index 8544890553..0000000000
--- a/railties/lib/rails/tasks/documentation.rake
+++ /dev/null
@@ -1,70 +0,0 @@
-begin
- require 'rdoc/task'
-rescue LoadError
- # Rubinius installs RDoc as a gem, and for this interpreter "rdoc/task" is
- # available only if the application bundle includes "rdoc" (normally as a
- # dependency of the "sdoc" gem.)
- #
- # If RDoc is not available it is fine that we do not generate the tasks that
- # depend on it. Just be robust to this gotcha and go on.
-else
- require 'rails/api/task'
-
- # Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
- class RDocTaskWithoutDescriptions < RDoc::Task
- include ::Rake::DSL
-
- def define
- task rdoc_task_name
-
- task rerdoc_task_name => [clobber_task_name, rdoc_task_name]
-
- task clobber_task_name do
- rm_r rdoc_dir rescue nil
- end
-
- task :clobber => [clobber_task_name]
-
- directory @rdoc_dir
- task rdoc_task_name => [rdoc_target]
- file rdoc_target => @rdoc_files + [Rake.application.rakefile] do
- rm_r @rdoc_dir rescue nil
- @before_running_rdoc.call if @before_running_rdoc
- args = option_list + @rdoc_files
- if @external
- argstring = args.join(' ')
- sh %{ruby -Ivendor vendor/rd #{argstring}}
- else
- require 'rdoc/rdoc'
- RDoc::RDoc.new.document(args)
- end
- end
- self
- end
- end
-
- namespace :doc do
- RDocTaskWithoutDescriptions.new("app") { |rdoc|
- rdoc.rdoc_dir = 'doc/app'
- rdoc.template = ENV['template'] if ENV['template']
- rdoc.title = ENV['title'] || "Rails Application Documentation"
- rdoc.options << '--line-numbers'
- rdoc.options << '--charset' << 'utf-8'
- rdoc.rdoc_files.include('README.rdoc')
- rdoc.rdoc_files.include('app/**/*.rb')
- rdoc.rdoc_files.include('lib/**/*.rb')
- }
- Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")"
-
- # desc 'Generate documentation for the Rails framework.'
- Rails::API::AppTask.new('rails')
- end
-end
-
-namespace :doc do
- task :guides do
- rails_gem_dir = Gem::Specification.find_by_name("rails").gem_dir
- require File.expand_path(File.join(rails_gem_dir, "/guides/rails_guides"))
- RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate
- end
-end
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index b33ae9862b..9162ef234a 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -11,7 +11,7 @@ namespace :tmp do
tmp_dirs.each { |d| directory d }
- desc "Creates tmp directories for sessions, cache, sockets, and pids"
+ desc "Creates tmp directories for cache, sockets, and pids"
task create: tmp_dirs
namespace :cache do
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index c837fadb40..40a1915b54 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -21,6 +21,7 @@ if defined?(ActiveRecord::Base)
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
self.fixture_path = "#{Rails.root}/test/fixtures/"
+ self.file_fixture_path = self.fixture_path + "files"
end
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 0749615d03..ab8883e135 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -4,6 +4,7 @@ require File.expand_path("../../../load_paths", __FILE__)
require 'stringio'
require 'active_support/testing/autorun'
+require 'active_support/testing/stream'
require 'fileutils'
require 'active_support'
@@ -28,26 +29,10 @@ def jruby_skip(message = '')
end
class ActiveSupport::TestCase
+ include ActiveSupport::Testing::Stream
+
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
self.test_order = :sorted
- private
-
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index d58a27403e..0a2f283cce 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require 'isolation/abstract_unit'
require 'rack/test'
require 'active_support/json'
diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
index a7472b37f1..4906f9a1e8 100644
--- a/railties/test/application/middleware/exceptions_test.rb
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'isolation/abstract_unit'
require 'rack/test'
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index eb7885e5b1..a8dc79d10a 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'isolation/abstract_unit'
require 'rack/test'
diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb
index 0a793f8f60..121c5d3321 100644
--- a/railties/test/application/middleware/static_test.rb
+++ b/railties/test/application/middleware/static_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'isolation/abstract_unit'
require 'rack/test'
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index c64fe082f3..04bd19784a 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -125,6 +125,22 @@ module ApplicationTests
assert !middleware.include?("ActionDispatch::Static")
end
+ test "can delete a middleware from the stack even if insert_before is added after delete" do
+ add_to_config "config.middleware.delete Rack::Runtime"
+ add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)"
+ boot!
+ assert middleware.include?("Rack::Config")
+ assert_not middleware.include?("Rack::Runtime")
+ end
+
+ test "can delete a middleware from the stack even if insert_after is added after delete" do
+ add_to_config "config.middleware.delete Rack::Runtime"
+ add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)"
+ boot!
+ assert middleware.include?("Rack::Config")
+ assert_not middleware.include?("Rack::Runtime")
+ end
+
test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do
add_to_config "config.action_dispatch.show_exceptions = false"
boot!
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index e8c8de9f73..8d71b813e6 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -227,7 +227,7 @@ module ApplicationTests
def test_rake_dump_structure_should_respect_db_structure_env_variable
Dir.chdir(app_path) do
# ensure we have a schema_migrations table to dump
- `bundle exec rake db:migrate db:structure:dump DB_STRUCTURE=db/my_structure.sql`
+ `bundle exec rake db:migrate db:structure:dump SCHEMA=db/my_structure.sql`
end
assert File.exist?(File.join(app_path, 'db', 'my_structure.sql'))
end
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 032b11a95f..a12f3cfc24 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -47,9 +47,8 @@ module ApplicationTests
def; end
RUBY
- error_stream = Tempfile.new('error')
- redirect_stderr(error_stream) { run_test_command('test/models/error_test.rb') }
- assert_match "syntax error", error_stream.read
+ error = capture(:stderr) { run_test_command('test/models/error_test.rb') }
+ assert_match "syntax error", error
end
def test_run_models
@@ -296,15 +295,6 @@ module ApplicationTests
app_file 'db/schema.rb', ''
end
- def redirect_stderr(target_stream)
- previous_stderr = STDERR.dup
- $stderr.reopen(target_stream)
- yield
- target_stream.rewind
- ensure
- $stderr = previous_stderr
- end
-
def create_test_file(path = :unit, name = 'test')
app_file "test/#{path}/#{name}_test.rb", <<-RUBY
require 'test_helper'
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index c4b6441397..c6de2c1fb9 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -222,14 +222,14 @@ class ActionsTest < Rails::Generators::TestCase
def test_readme
run_generator
Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root)
- assert_match "application up and running", action(:readme, "README.rdoc")
+ assert_match "application up and running", action(:readme, "README.md")
end
def test_readme_with_quiet
generator(default_arguments, quiet: true)
run_generator
Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root)
- assert_no_match "application up and running", action(:readme, "README.rdoc")
+ assert_no_match "application up and running", action(:readme, "README.md")
end
def test_log
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 5d270555ed..ca26e0c8d7 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -5,7 +5,7 @@ require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_APP_FILES = %w(
.gitignore
- README.rdoc
+ README.md
Gemfile
Rakefile
config.ru
@@ -33,6 +33,7 @@ DEFAULT_APP_FILES = %w(
log
test/test_helper.rb
test/fixtures
+ test/fixtures/files
test/controllers
test/models
test/helpers
@@ -416,11 +417,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_inclusion_of_doc
- run_generator
- assert_file 'Gemfile', /gem 'sdoc',\s+'~> 0.4.0',\s+group: :doc/
- end
-
def test_template_from_dir_pwd
FileUtils.cd(Rails.root)
assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]))
@@ -445,13 +441,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file 'lib/test_file.rb', 'heres test data'
end
- def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given
- run_generator [destination_root, "--skip-test-unit"]
+ def test_tests_are_removed_from_frameworks_if_skip_test_is_given
+ run_generator [destination_root, "--skip-test"]
assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
end
- def test_no_active_record_or_test_unit_if_skips_given
- run_generator [destination_root, "--skip-test-unit", "--skip-active-record"]
+ def test_no_active_record_or_tests_if_skips_given
+ run_generator [destination_root, "--skip-test", "--skip-active-record"]
assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "config/application.rb", /\s+require\s+["']active_job\/railtie["']/
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 7c5183e6c8..62ca0ecb4b 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/testing/stream'
require 'rails/generators'
require 'rails/generators/test_case'
@@ -23,6 +24,8 @@ require 'action_dispatch'
require 'action_view'
module GeneratorsTestHelper
+ include ActiveSupport::Testing::Stream
+
def self.included(base)
base.class_eval do
destination File.join(Rails.root, "tmp")
@@ -42,21 +45,4 @@ module GeneratorsTestHelper
FileUtils.cp routes, destination
end
- def quietly
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
-
- def silence_stream(stream)
- old_stream = stream.dup
- stream.reopen(IO::NULL)
- stream.sync = true
- yield
- ensure
- stream.reopen(old_stream)
- old_stream.close
- end
end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index febd2fd12e..584e7a82aa 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -78,13 +78,13 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_text_template_engine
run_generator
- assert_file "app/views/notifier/foo.text.erb" do |view|
- assert_match(%r(\sapp/views/notifier/foo\.text\.erb), view)
+ assert_file "app/views/notifier_mailer/foo.text.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/foo\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
- assert_file "app/views/notifier/bar.text.erb" do |view|
- assert_match(%r(\sapp/views/notifier/bar\.text\.erb), view)
+ assert_file "app/views/notifier_mailer/bar.text.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
@@ -95,13 +95,13 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_html_template_engine
run_generator
- assert_file "app/views/notifier/foo.html.erb" do |view|
- assert_match(%r(\sapp/views/notifier/foo\.html\.erb), view)
+ assert_file "app/views/notifier_mailer/foo.html.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/foo\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
- assert_file "app/views/notifier/bar.html.erb" do |view|
- assert_match(%r(\sapp/views/notifier/bar\.html\.erb), view)
+ assert_file "app/views/notifier_mailer/bar.html.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/bar\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
@@ -112,7 +112,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_template_engine_even_with_no_action
run_generator ["notifier"]
- assert_file "app/views/notifier"
+ assert_file "app/views/notifier_mailer"
assert_file "app/views/layouts/mailer.text.erb"
assert_file "app/views/layouts/mailer.html.erb"
end
@@ -133,8 +133,8 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(/class Farm::AnimalMailerPreview < ActionMailer::Preview/, preview)
assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal\/moos/, preview)
end
- assert_file "app/views/farm/animal/moos.text.erb"
- assert_file "app/views/farm/animal/moos.html.erb"
+ assert_file "app/views/farm/animal_mailer/moos.text.erb"
+ assert_file "app/views/farm/animal_mailer/moos.html.erb"
end
def test_actions_are_turned_into_methods
@@ -173,5 +173,14 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/mailers/notifier_mailer_mailer.rb"
assert_file "app/mailers/notifier_mailer.rb"
+
+ assert_no_file "app/views/notifier_mailer_mailer/"
+ assert_file "app/views/notifier_mailer/"
+
+ assert_no_file "test/mailers/notifier_mailer_mailer_test.rb"
+ assert_file "test/mailers/notifier_mailer_test.rb"
+
+ assert_no_file "test/mailers/previews/notifier_mailer_mailer_preview.rb"
+ assert_file "test/mailers/previews/notifier_mailer_preview.rb"
end
end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index 4199e00b0d..18a26fde05 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -2,16 +2,6 @@ require 'generators/generators_test_helper'
require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
require 'mocha/setup' # FIXME: stop using mocha
-# Mock out what we need from AR::Base.
-module ActiveRecord
- class Base
- class << self
- attr_accessor :pluralize_table_names
- end
- self.pluralize_table_names = true
- end
-end
-
class NamedBaseTest < Rails::Generators::TestCase
include GeneratorsTestHelper
tests Rails::Generators::ScaffoldControllerGenerator
@@ -59,11 +49,13 @@ class NamedBaseTest < Rails::Generators::TestCase
end
def test_named_generator_attributes_without_pluralized
+ original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
ActiveRecord::Base.pluralize_table_names = false
+
g = generator ['admin/foo']
assert_name g, 'admin_foo', :table_name
ensure
- ActiveRecord::Base.pluralize_table_names = true
+ ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names
end
def test_scaffold_plural_names
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index d0ea01dfb0..e839b67960 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -173,20 +173,20 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
def test_invokes_default_template_engine
run_generator
- assert_file "app/views/test_app/notifier/foo.text.erb" do |view|
- assert_match(%r(app/views/test_app/notifier/foo\.text\.erb), view)
+ assert_file "app/views/test_app/notifier_mailer/foo.text.erb" do |view|
+ assert_match(%r(app/views/test_app/notifier_mailer/foo\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
- assert_file "app/views/test_app/notifier/bar.text.erb" do |view|
- assert_match(%r(app/views/test_app/notifier/bar\.text\.erb), view)
+ assert_file "app/views/test_app/notifier_mailer/bar.text.erb" do |view|
+ assert_match(%r(app/views/test_app/notifier_mailer/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
end
def test_invokes_default_template_engine_even_with_no_action
run_generator ["notifier"]
- assert_file "app/views/test_app/notifier"
+ assert_file "app/views/test_app/notifier_mailer"
end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 318ea5b2cb..a0f244da28 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -28,11 +28,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase
include SharedGeneratorTests
def test_invalid_plugin_name_raises_an_error
- content = capture(:stderr){ run_generator [File.join(destination_root, "things-43")] }
- assert_equal "Invalid plugin name things-43. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+ content = capture(:stderr){ run_generator [File.join(destination_root, "my_plugin-31fr-extension")] }
+ assert_equal "Invalid plugin name my_plugin-31fr-extension. Please give a name which does not contain a namespace starting with numeric characters.\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] }
- assert_equal "Invalid plugin name things4.3. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+ assert_equal "Invalid plugin name things4.3. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters.\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
@@ -44,7 +44,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content
end
- def test_camelcase_plugin_name_underscores_filenames
+ def test_correct_file_in_lib_folder_of_hyphenated_plugin_name
+ run_generator [File.join(destination_root, "hyphenated-name")]
+ assert_no_file "hyphenated-name/lib/hyphenated-name.rb"
+ assert_no_file "hyphenated-name/lib/hyphenated_name.rb"
+ assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here...\n end\nend/
+ end
+
+ def test_correct_file_in_lib_folder_of_camelcase_plugin_name
run_generator [File.join(destination_root, "CamelCasedName")]
assert_no_file "CamelCasedName/lib/CamelCasedName.rb"
assert_file "CamelCasedName/lib/camel_cased_name.rb", /module CamelCasedName/
@@ -112,7 +119,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_ensure_that_test_dummy_can_be_generated_from_a_template
FileUtils.cd(Rails.root)
- run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test-unit"])
+ run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test"])
assert_file "spec/dummy"
assert_no_file "test"
end
@@ -185,13 +192,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_generation_runs_bundle_install_with_full_and_mountable
result = run_generator [destination_root, "--mountable", "--full", "--dev"]
+ assert_match(/run bundle install/, result)
+ assert $?.success?, "Command failed: #{result}"
assert_file "#{destination_root}/Gemfile.lock" do |contents|
assert_match(/bukkits/, contents)
end
- assert_match(/run bundle install/, result)
- assert_match(/Using bukkits \(?0\.0\.1\)?/, result)
- assert_match(/Your bundle is complete/, result)
- assert_equal 1, result.scan("Your bundle is complete").size
end
def test_skipping_javascripts_without_mountable_option
@@ -252,6 +257,40 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "lib/bukkits.rb", /require "bukkits\/engine"/
end
+ def test_creating_engine_with_hyphenated_name_in_full_mode
+ run_generator [File.join(destination_root, "hyphenated-name"), "--full"]
+ assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/images/hyphenated/name"
+ assert_file "hyphenated-name/app/models"
+ assert_file "hyphenated-name/app/controllers"
+ assert_file "hyphenated-name/app/views"
+ assert_file "hyphenated-name/app/helpers"
+ assert_file "hyphenated-name/app/mailers"
+ assert_file "hyphenated-name/bin/rails"
+ assert_file "hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/
+ assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
+ assert_file "hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/hyphenated\/name\/engine/
+ end
+
+ def test_creating_engine_with_hyphenated_and_underscored_name_in_full_mode
+ run_generator [File.join(destination_root, "my_hyphenated-name"), "--full"]
+ assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/models"
+ assert_file "my_hyphenated-name/app/controllers"
+ assert_file "my_hyphenated-name/app/views"
+ assert_file "my_hyphenated-name/app/helpers"
+ assert_file "my_hyphenated-name/app/mailers"
+ assert_file "my_hyphenated-name/bin/rails"
+ assert_file "my_hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
+ assert_file "my_hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/my_hyphenated\/name\/engine/
+ end
+
def test_being_quiet_while_creating_dummy_application
assert_no_match(/create\s+config\/application.rb/, run_generator)
end
@@ -277,6 +316,63 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_create_mountable_application_with_mountable_option_and_hypenated_name
+ run_generator [File.join(destination_root, "hyphenated-name"), "--mountable"]
+ assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/images/hyphenated/name"
+ assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine.routes.draw do/
+ assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
+ assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/
+ assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
+ assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
+ assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents|
+ assert_match "<title>Hyphenated name</title>", contents
+ assert_match(/stylesheet_link_tag\s+['"]hyphenated\/name\/application['"]/, contents)
+ assert_match(/javascript_include_tag\s+['"]hyphenated\/name\/application['"]/, contents)
+ end
+ end
+
+ def test_create_mountable_application_with_mountable_option_and_hypenated_and_underscored_name
+ run_generator [File.join(destination_root, "my_hyphenated-name"), "--mountable"]
+ assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name"
+ assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine.routes.draw do/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
+ assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/
+ assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
+ assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
+ assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents|
+ assert_match "<title>My hyphenated name</title>", contents
+ assert_match(/stylesheet_link_tag\s+['"]my_hyphenated\/name\/application['"]/, contents)
+ assert_match(/javascript_include_tag\s+['"]my_hyphenated\/name\/application['"]/, contents)
+ end
+ end
+
+ def test_create_mountable_application_with_mountable_option_and_multiple_hypenates_in_name
+ run_generator [File.join(destination_root, "deep-hyphenated-name"), "--mountable"]
+ assert_file "deep-hyphenated-name/app/assets/javascripts/deep/hyphenated/name"
+ assert_file "deep-hyphenated-name/app/assets/stylesheets/deep/hyphenated/name"
+ assert_file "deep-hyphenated-name/app/assets/images/deep/hyphenated/name"
+ assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine.routes.draw do/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\n end\nend/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/
+ assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/
+ assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents|
+ assert_match "<title>Deep hyphenated name</title>", contents
+ assert_match(/stylesheet_link_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents)
+ assert_match(/javascript_include_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents)
+ end
+ end
+
def test_creating_gemspec
run_generator
assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
@@ -321,7 +417,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_creating_dummy_without_tests_but_with_dummy_path
- run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"]
+ run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test"]
assert_file "spec/dummy"
assert_file "spec/dummy/config/application.rb"
assert_no_file "test"
@@ -333,14 +429,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path
FileUtils.cd(Rails.root)
- run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"])
+ run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test"])
assert_file ".gitignore" do |contents|
assert_match(/spec\/dummy/, contents)
end
end
- def test_skipping_test_unit
- run_generator [destination_root, "--skip-test-unit"]
+ def test_skipping_test_files
+ run_generator [destination_root, "--skip-test"]
assert_no_file "test"
assert_file "bukkits.gemspec" do |contents|
assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents)
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index ca972a3bdd..34e752cea1 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -106,8 +106,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/users_controller_test.rb" do |content|
assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content)
- assert_match(/patch :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content)
+ assert_match(/post :create, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_match(/patch :update, params: \{ id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
end
end
@@ -117,8 +117,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/users_controller_test.rb" do |content|
assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, user: \{ \}/, content)
- assert_match(/patch :update, id: @user, user: \{ \}/, content)
+ assert_match(/post :create, params: \{ user: \{ \} \}/, content)
+ assert_match(/patch :update, params: \{ id: @user, user: \{ \} \}/, content)
end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 3b545328b5..ee06802874 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -58,8 +58,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/product_lines_controller_test.rb" do |test|
assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test)
- assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test)
- assert_match(/patch :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test)
+ assert_match(/post :create, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
+ assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
end
# Views
@@ -93,8 +93,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/product_lines_controller_test.rb" do |content|
assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, product_line: \{ \}/, content)
- assert_match(/patch :update, id: @product_line, product_line: \{ \}/, content)
+ assert_match(/post :create, params: \{ product_line: \{ \} \}/, content)
+ assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ \} \}/, content)
end
end
@@ -245,6 +245,29 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/
end
+ def test_scaffold_generator_on_revoke_does_not_mutilate_routes
+ run_generator
+
+ route_path = File.expand_path("config/routes.rb", destination_root)
+ content = File.read(route_path)
+
+ # Remove all of the comments and blank lines from the routes file
+ content.gsub!(/^ \#.*\n/, '')
+ content.gsub!(/^\n/, '')
+
+ File.open(route_path, "wb") { |file| file.write(content) }
+ assert_file "config/routes.rb", /\.routes\.draw do\n resources :product_lines\nend\n\z/
+
+ run_generator ["product_line"], :behavior => :revoke
+
+ assert_file "config/routes.rb", /\.routes\.draw do\nend\n\z/
+ end
+
+ def test_scaffold_generator_ignores_commented_routes
+ run_generator ["product"]
+ assert_file "config/routes.rb", /\.routes\.draw do\n resources :products\n/
+ end
+
def test_scaffold_generator_no_assets_with_switch_no_assets
run_generator [ "posts", "--no-assets" ]
assert_no_file "app/assets/stylesheets/scaffold.css"
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index b998fef42e..68f07f29d7 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -47,8 +47,8 @@ module SharedGeneratorTests
assert_match(/Invalid value for \-\-database option/, content)
end
- def test_test_unit_is_skipped_if_required
- run_generator [destination_root, "--skip-test-unit"]
+ def test_test_files_are_skipped_if_required
+ run_generator [destination_root, "--skip-test"]
assert_no_file "test"
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 2c4d9c5995..63209559d7 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -11,6 +11,7 @@ require 'fileutils'
require 'bundler/setup' unless defined?(Bundler)
require 'active_support'
require 'active_support/testing/autorun'
+require 'active_support/testing/stream'
require 'active_support/test_case'
RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
@@ -276,12 +277,6 @@ module TestHelpers
end
end
- def gsub_app_file(path, regexp, *args, &block)
- path = "#{app_path}/#{path}"
- content = File.read(path).gsub(regexp, *args, &block)
- File.open(path, 'wb') { |f| f.write(content) }
- end
-
def remove_file(path)
FileUtils.rm_rf "#{app_path}/#{path}"
end
@@ -310,45 +305,10 @@ class ActiveSupport::TestCase
include TestHelpers::Paths
include TestHelpers::Rack
include TestHelpers::Generation
+ include ActiveSupport::Testing::Stream
self.test_order = :sorted
- private
-
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
-
- def quietly
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
-
- def silence_stream(stream)
- old_stream = stream.dup
- stream.reopen(IO::NULL)
- stream.sync = true
- yield
- ensure
- stream.reopen(old_stream)
- old_stream.close
- end
end
# Create a scope and build a fixture rails app
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
index 13bf29d3c3..e3dfcdfb7d 100644
--- a/railties/test/path_generation_test.rb
+++ b/railties/test/path_generation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/object/json'
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index d336a2e6c0..25b0e761cb 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -16,7 +16,7 @@ class InfoControllerTest < ActionController::TestCase
end
@routes = Rails.application.routes
- Rails::InfoController.send(:include, @routes.url_helpers)
+ Rails::InfoController.include(@routes.url_helpers)
@request.env["REMOTE_ADDR"] = "127.0.0.1"
end