aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml24
-rw-r--r--Gemfile13
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md2
-rw-r--r--RELEASING_RAILS.rdoc3
-rw-r--r--actionmailer/CHANGELOG.md56
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/actionmailer.gemspec4
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb41
-rw-r--r--actionmailer/lib/action_mailer/delivery_job.rb4
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb6
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb21
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb2
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb3
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb4
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb3
-rw-r--r--actionmailer/test/base_test.rb27
-rw-r--r--actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb1
-rw-r--r--actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb1
-rw-r--r--actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb1
-rw-r--r--actionmailer/test/message_delivery_test.rb19
-rw-r--r--actionmailer/test/url_test.rb60
-rw-r--r--actionpack/CHANGELOG.md408
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/actionpack.gemspec6
-rw-r--r--actionpack/lib/abstract_controller/base.rb25
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb11
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb15
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb10
-rw-r--r--actionpack/lib/action_controller/metal.rb5
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_template_digest.rb4
-rw-r--r--actionpack/lib/action_controller/metal/head.rb6
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb23
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb13
-rw-r--r--actionpack/lib/action_controller/metal/live.rb6
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb21
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb3
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb11
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb12
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb2
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb6
-rw-r--r--actionpack/lib/action_controller/model_naming.rb12
-rw-r--r--actionpack/lib/action_controller/test_case.rb10
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb21
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb113
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb47
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb9
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/fsm.css4
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb32
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb2
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb51
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb15
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb95
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/tag.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb78
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb6
-rw-r--r--actionpack/test/abstract_unit.rb5
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb7
-rw-r--r--actionpack/test/controller/filters_test.rb28
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb23
-rw-r--r--actionpack/test/controller/integration_test.rb24
-rw-r--r--actionpack/test/controller/localized_templates_test.rb4
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb20
-rw-r--r--actionpack/test/controller/mime/responders_test.rb32
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb6
-rw-r--r--actionpack/test/controller/render_test.rb17
-rw-r--r--actionpack/test/controller/routing_test.rb13
-rw-r--r--actionpack/test/controller/test_case_test.rb51
-rw-r--r--actionpack/test/controller/url_for_test.rb21
-rw-r--r--actionpack/test/controller/webservice_test.rb10
-rw-r--r--actionpack/test/dispatch/cookies_test.rb32
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb50
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb4
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request_test.rb11
-rw-r--r--actionpack/test/dispatch/response_test.rb18
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb44
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb56
-rw-r--r--actionpack/test/dispatch/routing_test.rb62
-rw-r--r--actionpack/test/dispatch/test_request_test.rb2
-rw-r--r--actionpack/test/fixtures/localized/hello_world.de.html2
-rw-r--r--actionpack/test/journey/path/pattern_test.rb2
-rw-r--r--actionpack/test/journey/router_test.rb2
-rw-r--r--actionpack/test/routing/helper_test.rb14
-rw-r--r--actionview/CHANGELOG.md203
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/actionview.gemspec4
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/base.rb4
-rw-r--r--actionview/lib/action_view/gem_version.rb6
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb40
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/file_field.rb15
-rw-r--r--actionview/lib/action_view/helpers/tags/search_field.rb1
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb28
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb5
-rw-r--r--actionview/lib/action_view/layouts.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb9
-rw-r--r--actionview/lib/action_view/record_identifier.rb61
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb26
-rw-r--r--actionview/lib/action_view/rendering.rb4
-rw-r--r--actionview/lib/action_view/routing_url_for.rb33
-rw-r--r--actionview/lib/action_view/template/error.rb2
-rw-r--r--actionview/lib/action_view/template/handlers.rb8
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb2
-rw-r--r--actionview/lib/action_view/template/resolver.rb33
-rw-r--r--actionview/lib/action_view/template/types.rb2
-rw-r--r--actionview/lib/action_view/view_paths.rb22
-rw-r--r--actionview/test/abstract_unit.rb2
-rw-r--r--actionview/test/actionpack/abstract/abstract_controller_test.rb18
-rw-r--r--actionview/test/actionpack/controller/view_paths_test.rb2
-rw-r--r--actionview/test/activerecord/debug_helper_test.rb (renamed from actionview/test/template/debug_helper_test.rb)0
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb20
-rw-r--r--actionview/test/fixtures/blog_public/.gitignore1
-rw-r--r--actionview/test/fixtures/blog_public/blog.html1
-rw-r--r--actionview/test/fixtures/blog_public/index.html1
-rw-r--r--actionview/test/fixtures/blog_public/subdir/index.html1
-rw-r--r--actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb3
-rw-r--r--actionview/test/fixtures/happy_path/render_action/hello_world.erb1
-rw-r--r--actionview/test/fixtures/scope/test/modgreet.erb1
-rw-r--r--actionview/test/fixtures/test/_label_with_block.erb2
-rw-r--r--actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb3
-rw-r--r--actionview/test/lib/controller/fake_models.rb25
-rw-r--r--actionview/test/template/date_helper_test.rb4
-rw-r--r--actionview/test/template/erb_util_test.rb2
-rw-r--r--actionview/test/template/form_helper_test.rb66
-rw-r--r--actionview/test/template/form_tag_helper_test.rb6
-rw-r--r--actionview/test/template/record_identifier_test.rb44
-rw-r--r--actionview/test/template/render_test.rb27
-rw-r--r--actionview/test/template/tag_helper_test.rb10
-rw-r--r--actionview/test/template/translation_helper_test.rb6
-rw-r--r--actionview/test/template/url_helper_test.rb4
-rw-r--r--activejob/CHANGELOG.md28
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/Rakefile4
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/arguments.rb2
-rw-r--r--activejob/lib/active_job/callbacks.rb4
-rw-r--r--activejob/lib/active_job/core.rb32
-rw-r--r--activejob/lib/active_job/gem_version.rb6
-rw-r--r--activejob/lib/active_job/logging.rb7
-rw-r--r--activejob/lib/active_job/queue_adapter.rb6
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb17
-rw-r--r--activejob/lib/active_job/test_helper.rb38
-rw-r--r--activejob/test/adapters/test.rb3
-rw-r--r--activejob/test/cases/adapter_test.rb3
-rw-r--r--activejob/test/cases/logging_test.rb9
-rw-r--r--activejob/test/cases/test_helper_test.rb36
-rw-r--r--activejob/test/helper.rb11
-rw-r--r--activejob/test/support/delayed_job/delayed/backend/test.rb4
-rw-r--r--activejob/test/support/integration/adapters/resque.rb2
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb3
-rw-r--r--activemodel/CHANGELOG.md66
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb10
-rw-r--r--activemodel/lib/active_model/callbacks.rb3
-rw-r--r--activemodel/lib/active_model/conversion.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb31
-rw-r--r--activemodel/lib/active_model/errors.rb4
-rw-r--r--activemodel/lib/active_model/gem_version.rb8
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/secure_password.rb2
-rw-r--r--activemodel/lib/active_model/serializers/json.rb8
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/active_model/validations/absence.rb2
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb5
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb11
-rw-r--r--activemodel/lib/active_model/validator.rb2
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/callbacks_test.rb29
-rw-r--r--activemodel/test/cases/dirty_test.rb17
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb31
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations_test.rb39
-rw-r--r--activerecord/CHANGELOG.md1312
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc10
-rw-r--r--activerecord/activerecord.gemspec4
-rw-r--r--activerecord/examples/performance.rb4
-rw-r--r--activerecord/lib/active_record.rb4
-rw-r--r--activerecord/lib/active_record/aggregations.rb4
-rw-r--r--activerecord/lib/active_record/association_relation.rb4
-rw-r--r--activerecord/lib/active_record/associations.rb15
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb43
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb126
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb60
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb12
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb44
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb9
-rw-r--r--activerecord/lib/active_record/associations/foreign_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb37
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb6
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb16
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb11
-rw-r--r--activerecord/lib/active_record/attribute.rb18
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb24
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb18
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb58
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb15
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb35
-rw-r--r--activerecord/lib/active_record/attribute_set.rb8
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb4
-rw-r--r--activerecord/lib/active_record/attributes.rb5
-rw-r--r--activerecord/lib/active_record/autosave_association.rb16
-rw-r--r--activerecord/lib/active_record/base.rb4
-rw-r--r--activerecord/lib/active_record/callbacks.rb8
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb199
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb61
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb106
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb11
-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/type_map_initializer.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb35
-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.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb60
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb50
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb27
-rw-r--r--activerecord/lib/active_record/counter_cache.rb17
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb21
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb21
-rw-r--r--activerecord/lib/active_record/fixtures.rb18
-rw-r--r--activerecord/lib/active_record/gem_version.rb6
-rw-r--r--activerecord/lib/active_record/inheritance.rb12
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb18
-rw-r--r--activerecord/lib/active_record/migration.rb17
-rw-r--r--activerecord/lib/active_record/model_schema.rb6
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb39
-rw-r--r--activerecord/lib/active_record/no_touching.rb2
-rw-r--r--activerecord/lib/active_record/null_relation.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb54
-rw-r--r--activerecord/lib/active_record/querying.rb7
-rw-r--r--activerecord/lib/active_record/railtie.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake18
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb144
-rw-r--r--activerecord/lib/active_record/relation.rb49
-rw-r--r--activerecord/lib/active_record/relation/batches.rb12
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb41
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb1
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb12
-rw-r--r--activerecord/lib/active_record/relation/merger.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb143
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb28
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb58
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/base_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/class_handler.rb27
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb56
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb37
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb23
-rw-r--r--activerecord/lib/active_record/secure_token.rb49
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/statement_cache.rb33
-rw-r--r--activerecord/lib/active_record/store.rb6
-rw-r--r--activerecord/lib/active_record/table_metadata.rb53
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb43
-rw-r--r--activerecord/lib/active_record/timestamp.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb44
-rw-r--r--activerecord/lib/active_record/type.rb1
-rw-r--r--activerecord/lib/active_record/type/boolean.rb13
-rw-r--r--activerecord/lib/active_record/type/date_time.rb6
-rw-r--r--activerecord/lib/active_record/type/decimal.rb10
-rw-r--r--activerecord/lib/active_record/type/hash_lookup_type_map.rb28
-rw-r--r--activerecord/lib/active_record/type/integer.rb8
-rw-r--r--activerecord/lib/active_record/type/numeric.rb2
-rw-r--r--activerecord/lib/active_record/type/serialized.rb2
-rw-r--r--activerecord/lib/active_record/type/string.rb4
-rw-r--r--activerecord/lib/active_record/type/type_map.rb30
-rw-r--r--activerecord/lib/active_record/type/unsigned_integer.rb15
-rw-r--r--activerecord/lib/active_record/type/value.rb8
-rw-r--r--activerecord/lib/active_record/type_caster.rb7
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb34
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb19
-rw-r--r--activerecord/lib/active_record/validations.rb8
-rw-r--r--activerecord/lib/active_record/validations/length.rb21
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb15
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb2
-rw-r--r--activerecord/test/cases/adapter_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/datetime_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/unsigned_type_test.rb30
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb30
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb52
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/change_schema_test.rb31
-rw-r--r--activerecord/test/cases/adapters/postgresql/cidr_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb28
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb81
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/infinity_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/integer_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb29
-rw-r--r--activerecord/test/cases/adapters/postgresql/numbers_test.rb49
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb32
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb43
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb7
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb47
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb14
-rw-r--r--activerecord/test/cases/ar_schema_test.rb64
-rw-r--r--activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb26
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb74
-rw-r--r--activerecord/test/cases/associations/extension_test.rb3
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb179
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb38
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb1
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb4
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb8
-rw-r--r--activerecord/test/cases/associations_test.rb7
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb18
-rw-r--r--activerecord/test/cases/attribute_set_test.rb8
-rw-r--r--activerecord/test/cases/attribute_test.rb1
-rw-r--r--activerecord/test/cases/autosave_association_test.rb22
-rw-r--r--activerecord/test/cases/base_test.rb100
-rw-r--r--activerecord/test/cases/calculations_test.rb22
-rw-r--r--activerecord/test/cases/callbacks_test.rb198
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb38
-rw-r--r--activerecord/test/cases/date_time_test.rb18
-rw-r--r--activerecord/test/cases/defaults_test.rb125
-rw-r--r--activerecord/test/cases/dirty_test.rb36
-rw-r--r--activerecord/test/cases/finder_test.rb65
-rw-r--r--activerecord/test/cases/fixtures_test.rb14
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb52
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb4
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb12
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb4
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb22
-rw-r--r--activerecord/test/cases/migration/index_test.rb14
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb101
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb38
-rw-r--r--activerecord/test/cases/migration_test.rb10
-rw-r--r--activerecord/test/cases/migrator_test.rb4
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb12
-rw-r--r--activerecord/test/cases/persistence_test.rb41
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb32
-rw-r--r--activerecord/test/cases/primary_keys_test.rb62
-rw-r--r--activerecord/test/cases/query_cache_test.rb32
-rw-r--r--activerecord/test/cases/reaper_test.rb2
-rw-r--r--activerecord/test/cases/reflection_test.rb30
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb6
-rw-r--r--activerecord/test/cases/relation/predicate_builder_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb53
-rw-r--r--activerecord/test/cases/relations_test.rb50
-rw-r--r--activerecord/test/cases/sanitize_test.rb11
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb95
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb72
-rw-r--r--activerecord/test/cases/secure_token_test.rb39
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb26
-rw-r--r--activerecord/test/cases/test_case.rb2
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb45
-rw-r--r--activerecord/test/cases/transactions_test.rb76
-rw-r--r--activerecord/test/cases/type/decimal_test.rb13
-rw-r--r--activerecord/test/cases/type/integer_test.rb10
-rw-r--r--activerecord/test/cases/type/type_map_test.rb47
-rw-r--r--activerecord/test/cases/type/unsigned_integer_test.rb17
-rw-r--r--activerecord/test/cases/types_test.rb10
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb18
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb17
-rw-r--r--activerecord/test/cases/validations_repair_helper.rb8
-rw-r--r--activerecord/test/cases/validations_test.rb13
-rw-r--r--activerecord/test/fixtures/bulbs.yml5
-rw-r--r--activerecord/test/fixtures/naked/csv/accounts.csv1
-rw-r--r--activerecord/test/models/author.rb6
-rw-r--r--activerecord/test/models/bird.rb2
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/company.rb1
-rw-r--r--activerecord/test/models/customer.rb2
-rw-r--r--activerecord/test/models/image.rb3
-rw-r--r--activerecord/test/models/organization.rb2
-rw-r--r--activerecord/test/models/owner.rb2
-rw-r--r--activerecord/test/models/parrot.rb2
-rw-r--r--activerecord/test/models/pirate.rb6
-rw-r--r--activerecord/test/models/post.rb10
-rw-r--r--activerecord/test/models/ship.rb4
-rw-r--r--activerecord/test/models/tyre.rb8
-rw-r--r--activerecord/test/models/user.rb4
-rw-r--r--activerecord/test/schema/oracle_specific_schema.rb5
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb96
-rw-r--r--activerecord/test/schema/schema.rb14
-rw-r--r--activesupport/CHANGELOG.md419
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/activesupport.gemspec6
-rwxr-xr-xactivesupport/bin/generate_tables2
-rw-r--r--activesupport/lib/active_support.rb12
-rw-r--r--activesupport/lib/active_support/cache.rb37
-rw-r--r--activesupport/lib/active_support/callbacks.rb240
-rw-r--r--activesupport/lib/active_support/core_ext.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb80
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/method_transplanting.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/name_error.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/bytes.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/itself.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/struct.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb86
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb17
-rw-r--r--activesupport/lib/active_support/dependencies.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb2
-rw-r--r--activesupport/lib/active_support/duration.rb9
-rw-r--r--activesupport/lib/active_support/gem_version.rb6
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb8
-rw-r--r--activesupport/lib/active_support/json/encoding.rb49
-rw-r--r--activesupport/lib/active_support/message_verifier.rb68
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb3
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb12
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb2
-rw-r--r--activesupport/lib/active_support/railtie.rb7
-rw-r--r--activesupport/lib/active_support/rescuable.rb4
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb4
-rw-r--r--activesupport/lib/active_support/test_case.rb32
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb76
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb4
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb2
-rw-r--r--activesupport/test/caching_test.rb26
-rw-r--r--activesupport/test/callbacks_test.rb129
-rw-r--r--activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb11
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb4
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/kernel_test.rb71
-rw-r--r--activesupport/test/core_ext/load_error_test.rb23
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb11
-rw-r--r--activesupport/test/core_ext/object/itself_test.rb9
-rw-r--r--activesupport/test/core_ext/object/try_test.rb4
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb18
-rw-r--r--activesupport/test/core_ext/struct_test.rb10
-rw-r--r--activesupport/test/core_ext/thread_test.rb75
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb2
-rw-r--r--activesupport/test/dependencies_test.rb6
-rw-r--r--activesupport/test/json/encoding_test.rb40
-rw-r--r--activesupport/test/message_encryptor_test.rb11
-rw-r--r--activesupport/test/message_verifier_test.rb47
-rw-r--r--activesupport/test/multibyte_chars_test.rb2
-rw-r--r--activesupport/test/multibyte_conformance_test.rb2
-rw-r--r--activesupport/test/rescuable_test.rb33
-rw-r--r--activesupport/test/safe_buffer_test.rb5
-rw-r--r--activesupport/test/tagged_logging_test.rb13
-rw-r--r--activesupport/test/test_case_test.rb24
-rw-r--r--activesupport/test/time_zone_test.rb13
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb20
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb20
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb14
-rwxr-xr-xci/travis.rb2
-rw-r--r--guides/CHANGELOG.md28
-rw-r--r--guides/Rakefile6
-rw-r--r--guides/assets/stylesheets/main.css2
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/bug_report_templates/action_controller_master.rb2
-rw-r--r--guides/bug_report_templates/active_record_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_master.rb2
-rw-r--r--guides/rails_guides.rb4
-rw-r--r--guides/rails_guides/kindle.rb2
-rw-r--r--guides/rails_guides/markdown/renderer.rb5
-rw-r--r--guides/source/2_2_release_notes.md2
-rw-r--r--guides/source/2_3_release_notes.md2
-rw-r--r--guides/source/3_0_release_notes.md4
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_0_release_notes.md2
-rw-r--r--guides/source/4_1_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md480
-rw-r--r--guides/source/_welcome.html.erb9
-rw-r--r--guides/source/action_controller_overview.md70
-rw-r--r--guides/source/action_mailer_basics.md57
-rw-r--r--guides/source/action_view_overview.md3
-rw-r--r--guides/source/active_job_basics.md31
-rw-r--r--guides/source/active_model_basics.md2
-rw-r--r--guides/source/active_record_basics.md4
-rw-r--r--guides/source/active_record_callbacks.md2
-rw-r--r--guides/source/active_record_migrations.md10
-rw-r--r--guides/source/active_record_postgresql.md14
-rw-r--r--guides/source/active_record_querying.md67
-rw-r--r--guides/source/active_record_validations.md4
-rw-r--r--guides/source/active_support_core_extensions.md28
-rw-r--r--guides/source/active_support_instrumentation.md2
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/asset_pipeline.md29
-rw-r--r--guides/source/association_basics.md51
-rw-r--r--guides/source/autoloading_and_reloading_constants.md1300
-rw-r--r--guides/source/caching_with_rails.md4
-rw-r--r--guides/source/command_line.md41
-rw-r--r--guides/source/configuring.md16
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md13
-rw-r--r--guides/source/debugging_rails_applications.md14
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/documents.yaml30
-rw-r--r--guides/source/engines.md43
-rw-r--r--guides/source/form_helpers.md12
-rw-r--r--guides/source/generators.md10
-rw-r--r--guides/source/getting_started.md92
-rw-r--r--guides/source/i18n.md4
-rw-r--r--guides/source/initialization.md17
-rw-r--r--guides/source/layouts_and_rendering.md46
-rw-r--r--guides/source/maintenance_policy.md2
-rw-r--r--guides/source/nested_model_forms.md2
-rw-r--r--guides/source/plugins.md4
-rw-r--r--guides/source/profiling.md16
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md9
-rw-r--r--guides/source/routing.md2
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md2
-rw-r--r--guides/source/security.md23
-rw-r--r--guides/source/testing.md720
-rw-r--r--guides/source/upgrading_ruby_on_rails.md95
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--rails.gemspec4
-rw-r--r--railties/CHANGELOG.md194
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/lib/rails.rb10
-rw-r--r--railties/lib/rails/application.rb27
-rw-r--r--railties/lib/rails/application/bootstrap.rb13
-rw-r--r--railties/lib/rails/application/configuration.rb15
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb2
-rw-r--r--railties/lib/rails/application/routes_reloader.rb4
-rw-r--r--railties/lib/rails/commands/console.rb26
-rw-r--r--railties/lib/rails/commands/dbconsole.rb19
-rw-r--r--railties/lib/rails/commands/plugin.rb2
-rw-r--r--railties/lib/rails/commands/server.rb14
-rw-r--r--railties/lib/rails/configuration.rb4
-rw-r--r--railties/lib/rails/deprecation.rb19
-rw-r--r--railties/lib/rails/engine.rb8
-rw-r--r--railties/lib/rails/engine/configuration.rb5
-rw-r--r--railties/lib/rails/gem_version.rb6
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb48
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb27
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/layout.html.erb5
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/layout.text.erb1
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb8
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/new.html.erb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb4
-rw-r--r--railties/lib/rails/generators/named_base.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile13
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore3
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile10
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb4
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb1
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb4
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml2
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/paths.rb10
-rw-r--r--railties/lib/rails/rack.rb4
-rw-r--r--railties/lib/rails/rack/debugger.rb25
-rw-r--r--railties/lib/rails/rack/log_tailer.rb38
-rw-r--r--railties/lib/rails/ruby_version_check.rb6
-rw-r--r--railties/lib/rails/tasks.rb2
-rw-r--r--railties/lib/rails/tasks/statistics.rake6
-rw-r--r--railties/lib/rails/tasks/tmp.rake14
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb2
-rw-r--r--railties/lib/rails/test_unit/testing.rake26
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/application/assets_test.rb2
-rw-r--r--railties/test/application/configuration_test.rb135
-rw-r--r--railties/test/application/initializers/frameworks_test.rb1
-rw-r--r--railties/test/application/loading_test.rb29
-rw-r--r--railties/test/application/mailer_previews_test.rb63
-rw-r--r--railties/test/application/middleware/sendfile_test.rb2
-rw-r--r--railties/test/application/middleware_test.rb4
-rw-r--r--railties/test/application/rake/dbs_test.rb34
-rw-r--r--railties/test/application/routing_test.rb20
-rw-r--r--railties/test/application/test_test.rb94
-rw-r--r--railties/test/commands/console_test.rb22
-rw-r--r--railties/test/generators/actions_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb83
-rw-r--r--railties/test/generators/generators_test_helper.rb10
-rw-r--r--railties/test/generators/job_generator_test.rb29
-rw-r--r--railties/test/generators/mailer_generator_test.rb39
-rw-r--r--railties/test/generators/migration_generator_test.rb28
-rw-r--r--railties/test/generators/model_generator_test.rb43
-rw-r--r--railties/test/generators/namespaced_generators_test.rb4
-rw-r--r--railties/test/generators/plugin_generator_test.rb34
-rw-r--r--railties/test/generators/scaffold_generator_test.rb30
-rw-r--r--railties/test/isolation/abstract_unit.rb13
-rw-r--r--railties/test/rails_info_controller_test.rb9
-rw-r--r--railties/test/railties/engine_test.rb23
-rw-r--r--tasks/release.rb4
-rw-r--r--version.rb6
735 files changed, 10096 insertions, 8153 deletions
diff --git a/.travis.yml b/.travis.yml
index 2823a5456f..ab7d968852 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,37 +1,36 @@
language: ruby
+sudo: false
script: 'ci/travis.rb'
before_install:
- - travis_retry gem install bundler
- "rvm current | grep 'jruby' && export AR_JDBC=true || echo"
+before_script:
+ - bundle update
+cache: bundler
env:
global:
- JRUBY_OPTS='-J-Xmx1024M'
matrix:
- "GEM=railties"
- "GEM=ap"
- - "GEM=am,amo,as,av,aj"
+ - "GEM=aj"
+ - "GEM=am,amo,as,av"
- "GEM=ar:mysql"
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
- "GEM=ar:postgresql"
- "GEM=aj:integration"
rvm:
- - 1.9.3
- - 2.0.0
- - 2.1
+ - 2.2
- ruby-head
- rbx-2
- jruby
matrix:
allow_failures:
- - rvm: 1.9.3
- env: "GEM=ar:mysql"
- - rvm: 2.0.0
- env: "GEM=ar:mysql"
- - rvm: ruby-head
- env: "GEM=ar:mysql"
+ - env: "GEM=ar:mysql"
- rvm: rbx-2
- rvm: jruby
+ - env: "GEM=aj"
+ - env: "GEM=aj:integration"
fast_finish: true
notifications:
email: false
@@ -45,11 +44,10 @@ notifications:
on_failure: always
rooms:
- secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
-bundler_args: --path vendor/bundle --without test
+bundler_args: --without test
services:
- memcached
- redis
- rabbitmq
addons:
postgresql: "9.3"
-
diff --git a/Gemfile b/Gemfile
index 60b668401d..eb0a324ab2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,11 +11,10 @@ gem 'rake', '>= 10.3'
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'jquery-rails', '~> 4.0.0.beta2'
+gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
gem 'coffee-rails', '~> 4.1.0'
gem 'turbolinks'
-gem 'arel', github: 'rails/arel'
-gem 'rails-dom-testing', github: 'rails/rails-dom-testing'
+gem 'arel', github: 'rails/arel', branch: 'master'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
@@ -28,7 +27,7 @@ gem 'uglifier', '>= 1.3.0', require: false
group :doc do
gem 'sdoc', '~> 0.4.0'
- gem 'redcarpet', '~> 3.1.2', platforms: :ruby
+ gem 'redcarpet', '~> 3.2.2', platforms: :ruby
gem 'w3c_validators'
gem 'kindlerb'
end
@@ -49,7 +48,7 @@ group :job do
gem 'backburner', require: false
gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
gem 'qu-redis', require: false
- gem 'delayed_job_active_record', require: false
+ # gem 'delayed_job_active_record', require: false
gem 'sequel', require: false
end
@@ -69,8 +68,8 @@ group :test do
gem 'stackprof'
end
- # platforms :mri_19, :mri_20 do
- # gem 'debugger'
+ # platforms :mri do
+ # gem 'byebug'
# end
gem 'benchmark-ips'
diff --git a/RAILS_VERSION b/RAILS_VERSION
index b86dc8c0c6..2b915d7d5c 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-4.2.0.beta4
+5.0.0.alpha
diff --git a/README.md b/README.md
index 7e1bcd02ea..bbe3a6731d 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the
## Code Status
-* [![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)
+[![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)
## License
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index c80a5816a7..ee5e6a91c1 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -33,7 +33,6 @@ after some refactoring or bug fix, so it is important to check if the following
are working with the versions that will be released:
* https://github.com/rails/protected_attributes
-* https://github.com/rails/activerecord-deprecated_finders
Do not release red plugins tests.
@@ -54,7 +53,7 @@ addressed, and that can impact your release date.
Ruby implementors have high stakes in making sure Rails works. Be kind and
give them a heads up that Rails will be released soonish.
-This only need to be done for major and minor releases, bugfix releases aren't a
+This is only required for major and minor releases, bugfix releases aren't a
big enough deal, and are supposed to be backwards compatible.
Send an email just giving a heads up about the upcoming release to these
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 5685871ac9..787dc46f00 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,50 +1,32 @@
-* Attachments can be added while rendering the mail template.
+* Remove deprecate `*_path` helpers in email views.
- Fixes #16974.
+ *Rafael Mendonça França*
- *Christian Felder*
+* Remove deprecated `deliver` and `deliver!` methods.
-* Added `#deliver_later`, `#deliver_now` and deprecate `#deliver` in favour of
- `#deliver_now`. `#deliver_later` will enqueue a job to render and deliver
- the mail instead of delivering it right at that moment. The job is enqueued
- using the new Active Job framework in Rails, and will use whatever queue is
- configured for Rails.
+ *claudiob*
- *DHH*, *Abdelkader Boudih*, *Cristian Bica*
+* Template lookup now respects default locale and I18n fallbacks.
-* Make `ActionMailer::Previews` methods class methods. Previously they were
- instance methods and `ActionMailer` tries to render a message when they
- are called.
+ Given the following templates:
- *Cristian Bica*
+ mailer/demo.html.erb
+ mailer/demo.en.html.erb
+ mailer/demo.pt.html.erb
-* Deprecate `*_path` helpers in email views. When used they generate
- non-working links and are not the intention of most developers. Instead
- we recommend to use `*_url` helper.
+ Before this change, for a locale that doesn't have its associated file, the
+ `mailer/demo.html.erb` would be rendered even if `en` was the default locale.
- *Richard Schneeman*
+ Now `mailer/demo.en.html.erb` has precedence over the file without locale.
-* Raise an exception when attachments are added after `mail` was called.
- This is a safeguard to prevent invalid emails.
+ Also, it is possible to give a fallback.
- Fixes #16163.
+ mailer/demo.pt.html.erb
+ mailer/demo.pt-BR.html.erb
- *Yves Senn*
+ So if the locale is `pt-PT`, `mailer/demo.pt.html.erb` will be rendered given
+ the right I18n fallback configuration.
-* Add `config.action_mailer.show_previews` configuration option.
+ *Rafael Mendonça França*
- This config option can be used to enable the mail preview in environments
- other than development (such as staging).
-
- Defaults to `true` in development and false elsewhere.
-
- *Leonard Garvey*
-
-* Allow preview interceptors to be registered through
- `config.action_mailer.preview_interceptors`.
-
- See #15739.
-
- *Yves Senn*
-
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index abc047d339..513c217733 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activejob', version
s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4']
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
end
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index b994ef3182..17d8dcc208 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index a70bf1544a..53cc1fdb31 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -15,11 +15,17 @@ module ActionMailer
#
# $ rails generate mailer Notifier
#
- # The generated model inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
+ # The generated model inherits from <tt>ApplicationMailer</tt> which in turn
+ # inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
# used to generate an email message. In these methods, you can setup variables to be used in
# the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
#
- # class Notifier < ActionMailer::Base
+ # class ApplicationMailer < ActionMailer::Base
+ # default from: 'from@exmaple.com'
+ # layout 'mailer'
+ # end
+ #
+ # class Notifier < ApplicationMailer
# default from: 'no-reply@example.com',
# return_path: 'system@example.com'
#
@@ -84,7 +90,7 @@ module ActionMailer
# name as the method in your mailer model. For example, in the mailer defined above, the template at
# <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email.
#
- # Variables defined in the methods of your mailer model are accessible as instance variables in their
+ # Variables defined in the methods of your mailer model are accessible as instance variables in their
# corresponding view.
#
# Emails by default are sent in plain text, so a sample view for our model example might look like this:
@@ -126,11 +132,6 @@ module ActionMailer
#
# config.action_mailer.default_url_options = { host: "example.com" }
#
- # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
- # <tt>only_path: false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
- # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
- # <tt>only_path: false</tt> will ensure that absolute URLs are generated.
- #
# = Sending mail
#
# Once a mailer action and template are defined, you can deliver your message or create it and save it
@@ -178,7 +179,7 @@ module ActionMailer
#
# Sending attachment in emails is easy:
#
- # class ApplicationMailer < ActionMailer::Base
+ # class Notifier < ApplicationMailer
# def welcome(recipient)
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
# mail(to: recipient, subject: "New account information")
@@ -194,7 +195,7 @@ module ActionMailer
# If you need to send attachments with no content, you need to create an empty view for it,
# or add an empty body parameter like this:
#
- # class ApplicationMailer < ActionMailer::Base
+ # class Notifier < ApplicationMailer
# def welcome(recipient)
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
# mail(to: recipient, subject: "New account information", body: "")
@@ -206,7 +207,7 @@ module ActionMailer
# You can also specify that a file should be displayed inline with other HTML. This is useful
# if you want to display a corporate logo or a photo.
#
- # class ApplicationMailer < ActionMailer::Base
+ # class Notifier < ApplicationMailer
# def welcome(recipient)
# attachments.inline['photo.png'] = File.read('path/to/photo.png')
# mail(to: recipient, subject: "Here is what we look like")
@@ -245,7 +246,7 @@ module ActionMailer
# Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
# default method inside the class definition:
#
- # class Notifier < ActionMailer::Base
+ # class Notifier < ApplicationMailer
# default sender: 'system@example.com'
# end
#
@@ -263,7 +264,7 @@ module ActionMailer
# As you can pass in any header, you need to either quote the header as a string, or pass it in as
# an underscored symbol, so the following will work:
#
- # class Notifier < ActionMailer::Base
+ # class Notifier < ApplicationMailer
# default 'Content-Transfer-Encoding' => '7bit',
# content_description: 'This is a description'
# end
@@ -271,7 +272,7 @@ module ActionMailer
# Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
# can define methods that evaluate as the message is being generated:
#
- # class Notifier < ActionMailer::Base
+ # class Notifier < ApplicationMailer
# default 'X-Special-Header' => Proc.new { my_method }
#
# private
@@ -296,7 +297,7 @@ module ActionMailer
# This may be useful, for example, when you want to add default inline attachments for all
# messages sent out by a certain mailer class:
#
- # class Notifier < ActionMailer::Base
+ # class Notifier < ApplicationMailer
# before_action :add_inline_attachment!
#
# def welcome
@@ -492,7 +493,7 @@ module ActionMailer
# Sets the defaults through app configuration:
#
- # config.action_mailer.default { from: "no-reply@example.org" }
+ # config.action_mailer.default(from: "no-reply@example.org")
#
# Aliased by ::default_options=
def default(value = nil)
@@ -585,8 +586,6 @@ module ActionMailer
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
- lookup_context.skip_default_locale!
-
super
@_message = NullMail.new unless @_mail_was_called
end
@@ -703,7 +702,7 @@ module ActionMailer
# The main method that creates the message and renders the email templates. There are
# two ways to call this method, with a block, or without a block.
#
- # It accepts a headers hash. This hash allows you to specify
+ # It accepts a headers hash. This hash allows you to specify
# the most used headers in an email message, these are:
#
# * +:subject+ - The subject of the message, if this is omitted, Action Mailer will
@@ -853,7 +852,7 @@ module ActionMailer
when user_content_type.present?
user_content_type
when m.has_attachments?
- if m.attachments.detect { |a| a.inline? }
+ if m.attachments.detect(&:inline?)
["multipart", "related", params]
else
["multipart", "mixed", params]
@@ -908,7 +907,7 @@ module ActionMailer
if templates.empty?
raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer')
else
- templates.uniq { |t| t.formats }.each(&block)
+ templates.uniq(&:formats).each(&block)
end
end
diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb
index 95231411fb..e864ab7a4d 100644
--- a/actionmailer/lib/action_mailer/delivery_job.rb
+++ b/actionmailer/lib/action_mailer/delivery_job.rb
@@ -3,10 +3,10 @@ require 'active_job'
module ActionMailer
# The <tt>ActionMailer::DeliveryJob</tt> class is used when you
# want to send emails outside of the request-response cycle.
- class DeliveryJob < ActiveJob::Base #:nodoc:
+ class DeliveryJob < ActiveJob::Base # :nodoc:
queue_as :mailers
- def perform(mailer, mail_method, delivery_method, *args) #:nodoc#
+ def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
mailer.constantize.public_send(mail_method, *args).send(delivery_method)
end
end
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index e568b8f6f2..ac79788cf0 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -5,10 +5,10 @@ module ActionMailer
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index b5dc2d7497..ff2cb0fd01 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -1,5 +1,4 @@
require 'delegate'
-require 'active_support/core_ext/string/filters'
module ActionMailer
@@ -85,26 +84,6 @@ module ActionMailer
message.deliver
end
- def deliver! #:nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#deliver!` is deprecated and will be removed in Rails 5. Use
- `#deliver_now!` to deliver immediately or `#deliver_later!` to
- deliver through Active Job.
- MSG
-
- deliver_now!
- end
-
- def deliver #:nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#deliver` is deprecated and will be removed in Rails 5. Use
- `#deliver_now` to deliver immediately or `#deliver_later` to
- deliver through Active Job.
- MSG
-
- deliver_now
- end
-
private
def enqueue_delivery(delivery_method, options={})
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 05a3ac6a21..bebcf4de01 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -41,7 +41,7 @@ module ActionMailer
options.each { |k,v| send("#{k}=", v) }
if options.show_previews
- app.routes.append do
+ app.routes.prepend do
get '/rails/mailers' => "rails/mailers#index"
get '/rails/mailers/*path' => "rails/mailers#preview"
end
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index d5bf864595..83f8a67da7 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -8,6 +8,9 @@ module Rails
def create_mailer_file
template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb")
+ if self.behavior == :invoke
+ template "application_mailer.rb", 'app/mailers/application_mailer.rb'
+ end
end
hook_for :template_engine, :test_framework
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
new file mode 100644
index 0000000000..d25d8892dd
--- /dev/null
+++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: "from@example.com"
+ layout 'mailer'
+end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
index edcfb4233d..bce64a5e6e 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
@@ -1,6 +1,5 @@
<% module_namespacing do -%>
-class <%= class_name %> < ActionMailer::Base
- default from: "from@example.com"
+class <%= class_name %> < ApplicationMailer
<% actions.each do |action| -%>
# Subject can be set in your I18n file at config/locales/en.yml
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 396d0a95b5..5d9eda2555 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -353,10 +353,35 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("text/plain", email.parts[0].mime_type)
assert_equal("Implicit with locale PL TEXT", email.parts[0].body.encoded)
assert_equal("text/html", email.parts[1].mime_type)
- assert_equal("Implicit with locale HTML", email.parts[1].body.encoded)
+ assert_equal("Implicit with locale EN HTML", email.parts[1].body.encoded)
end
end
+ test "implicit multipart with fallback locale" do
+ fallback_backend = Class.new(I18n::Backend::Simple) do
+ include I18n::Backend::Fallbacks
+ end
+
+ begin
+ backend = I18n.backend
+ I18n.backend = fallback_backend.new
+ I18n.fallbacks[:"de-AT"] = [:de]
+
+ swap I18n, locale: 'de-AT' do
+ email = BaseMailer.implicit_with_locale
+ assert_equal(2, email.parts.size)
+ assert_equal("multipart/alternative", email.mime_type)
+ assert_equal("text/plain", email.parts[0].mime_type)
+ assert_equal("Implicit with locale DE-AT TEXT", email.parts[0].body.encoded)
+ assert_equal("text/html", email.parts[1].mime_type)
+ assert_equal("Implicit with locale DE HTML", email.parts[1].body.encoded)
+ end
+ ensure
+ I18n.backend = backend
+ end
+ end
+
+
test "implicit multipart with several view paths uses the first one with template" do
old = BaseMailer.view_paths
begin
diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb
new file mode 100644
index 0000000000..e97505fad9
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb
@@ -0,0 +1 @@
+Implicit with locale DE-AT TEXT \ No newline at end of file
diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb
new file mode 100644
index 0000000000..0536b5d3e2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb
@@ -0,0 +1 @@
+Implicit with locale DE HTML \ No newline at end of file
diff --git a/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb b/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb
new file mode 100644
index 0000000000..0322c1191e
--- /dev/null
+++ b/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb
@@ -0,0 +1 @@
+<%= url_for(@options) %> <%= @url %>
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index 9abf8b225c..55ee00602a 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -32,25 +32,6 @@ class MessageDeliveryTest < ActiveSupport::TestCase
assert_equal Mail::Message , @mail.message.class
end
- test 'should respond to .deliver' do
- assert_respond_to @mail, :deliver
- end
-
- test 'should respond to .deliver!' do
- assert_respond_to @mail, :deliver!
- end
-
- test '.deliver is deprecated' do
- assert_deprecated do
- @mail.deliver
- end
- end
- test '.deliver! is deprecated' do
- assert_deprecated do
- @mail.deliver!
- end
- end
-
test 'should respond to .deliver_later' do
assert_respond_to @mail, :deliver_later
end
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index be7532d42f..7928fe9542 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -23,9 +23,32 @@ class UrlTestMailer < ActionMailer::Base
mail(to: recipient, subject: "[Signed up] Welcome #{recipient}",
from: "system@loudthinking.com", date: Time.local(2004, 12, 12))
end
+
+ def exercise_url_for(options)
+ @options = options
+ @url = url_for(@options)
+ mail(from: 'from@example.com', to: 'to@example.com', subject: 'subject')
+ end
end
class ActionMailerUrlTest < ActionMailer::TestCase
+ class DummyModel
+ def self.model_name
+ OpenStruct.new(route_key: 'dummy_model')
+ end
+
+ def persisted?
+ false
+ end
+
+ def model_name
+ self.class.model_name
+ end
+
+ def to_model
+ self
+ end
+ end
def encode( text, charset="UTF-8" )
quoted_printable( text, charset )
@@ -40,10 +63,47 @@ class ActionMailerUrlTest < ActionMailer::TestCase
mail
end
+ def assert_url_for(expected, options, relative = false)
+ expected = "http://www.basecamphq.com#{expected}" if expected.start_with?('/') && !relative
+ urls = UrlTestMailer.exercise_url_for(options).body.to_s.chomp.split
+
+ assert_equal expected, urls.first
+ assert_equal expected, urls.second
+ end
+
def setup
@recipient = 'test@localhost'
end
+ def test_url_for
+ UrlTestMailer.delivery_method = :test
+
+ AppRoutes.draw do
+ get ':controller(/:action(/:id))'
+ get '/welcome' => 'foo#bar', as: 'welcome'
+ get '/dummy_model' => 'foo#baz', as: 'dummy_model'
+ end
+
+ # string
+ assert_url_for 'http://foo/', 'http://foo/'
+
+ # symbol
+ assert_url_for '/welcome', :welcome
+
+ # hash
+ assert_url_for '/a/b/c', controller: 'a', action: 'b', id: 'c'
+ assert_url_for '/a/b/c', {controller: 'a', action: 'b', id: 'c', only_path: true}, true
+
+ # model
+ assert_url_for '/dummy_model', DummyModel.new
+
+ # class
+ assert_url_for '/dummy_model', DummyModel
+
+ # array
+ assert_url_for '/dummy_model' , [DummyModel]
+ end
+
def test_signed_up_with_url
UrlTestMailer.delivery_method = :test
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 158b22c0cc..6c4ce6195e 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,396 +1,94 @@
-* Deprecate the `only_path` option on `*_path` helpers.
+* Remove `respond_to`/`respond_with` placeholder methods, this functionality
+ has been extracted to the `responders` gem.
- In cases where this option is set to `true`, the option is redundant and can
- be safely removed; otherwise, the corresponding `*_url` helper should be
- used instead.
+ *Carlos Antonio da Silva*
- Fixes #17294.
+* Remove deprecated assertion files.
- *Dan Olson*, *Godfrey Chan*
-
-* Improve Journey compliance to RFC 3986.
-
- The scanner in Journey failed to recognize routes that use literals
- from the sub-delims section of RFC 3986. It's now able to parse those
- authorized delimiters and route as expected.
-
- Fixes #17212.
-
- *Nicolas Cavigneaux*
-
-* Deprecate implicit Array conversion for Response objects. It was added
- (using `#to_ary`) so we could conveniently use implicit splatting:
-
- status, headers, body = response
-
- But it also means `response + response` works and `[response].flatten`
- cascades down to the Rack body. Nonsense behavior. Instead, rely on
- explicit conversion and splatting with `#to_a`:
-
- status, header, body = *response
-
- *Jeremy Kemper*
-
-* Don't rescue `IPAddr::InvalidAddressError`.
-
- `IPAddr::InvalidAddressError` does not exist in Ruby 1.9.3
- and fails for JRuby in 1.9 mode.
-
- *Peter Suschlik*
-
-* Fix bug where the router would ignore any constraints added to redirect
- routes.
-
- Fixes #16605.
-
- *Agis Anastasopoulos*
-
-* Allow `config.action_dispatch.trusted_proxies` to accept an IPAddr object.
-
- Example:
-
- # config/environments/production.rb
- config.action_dispatch.trusted_proxies = IPAddr.new('4.8.15.0/16')
-
- *Sam Aarons*
-
-* Avoid duplicating routes for HEAD requests.
-
- Instead of duplicating the routes, we will first match the HEAD request to
- HEAD routes. If no match is found, we will then map the HEAD request to
- GET routes.
-
- *Guo Xiang Tan*, *Andrew White*
-
-* Requests that hit `ActionDispatch::Static` can now take advantage
- of gzipped assets on disk. By default a gzip asset will be served if
- the client supports gzip and a compressed file is on disk.
-
- *Richard Schneeman*
-
-* `ActionController::Parameters` will stop inheriting from `Hash` and
- `HashWithIndifferentAccess` in the next major release. If you use any method
- that is not available on `ActionController::Parameters` you should consider
- calling `#to_h` to convert it to a `Hash` first before calling that method.
-
- *Prem Sichanugrist*
-
-* `ActionController::Parameters#to_h` now returns a `Hash` with unpermitted
- keys removed. This change is to reflect on a security concern where some
- method performed on an `ActionController::Parameters` may yield a `Hash`
- object which does not maintain `permitted?` status. If you would like to
- get a `Hash` with all the keys intact, duplicate and mark it as permitted
- before calling `#to_h`.
-
- params = ActionController::Parameters.new({
- name: 'Senjougahara Hitagi',
- oddity: 'Heavy stone crab'
- })
- params.to_h
- # => {}
-
- unsafe_params = params.dup.permit!
- unsafe_params.to_h
- # => {"name"=>"Senjougahara Hitagi", "oddity"=>"Heavy stone crab"}
-
- safe_params = params.permit(:name)
- safe_params.to_h
- # => {"name"=>"Senjougahara Hitagi"}
-
- This change is consider a stopgap as we cannot change the code to stop
- `ActionController::Parameters` to inherit from `HashWithIndifferentAccess`
- in the next minor release.
-
- *Prem Sichanugrist*
-
-* Deprecated `TagAssertions`.
-
- *Kasper Timm Hansen*
-
-* Use the Active Support JSON encoder for cookie jars using the `:json` or
- `:hybrid` serializer. This allows you to serialize custom Ruby objects into
- cookies by defining the `#as_json` hook on such objects.
-
- Fixes #16520.
-
- *Godfrey Chan*
-
-* Add `config.action_dispatch.cookies_digest` option for setting custom
- digest. The default remains the same - 'SHA1'.
-
- *Łukasz Strzałkowski*
-
-* Move `respond_with` (and the class-level `respond_to`) to
- the `responders` gem.
-
- *José Valim*
-
-* When your templates change, browser caches bust automatically.
-
- New default: the template digest is automatically included in your ETags.
- When you call `fresh_when @post`, the digest for `posts/show.html.erb`
- is mixed in so future changes to the HTML will blow HTTP caches for you.
- This makes it easy to HTTP-cache many more of your actions.
-
- If you render a different template, you can now pass the `:template`
- option to include its digest instead:
-
- fresh_when @post, template: 'widgets/show'
-
- Pass `template: false` to skip the lookup. To turn this off entirely, set:
-
- config.action_controller.etag_with_template_digest = false
-
- *Jeremy Kemper*
-
-* Remove deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError`
- in favor of `AbstractController::Helpers::MissingHelperError`.
-
- *Yves Senn*
-
-* Fix `assert_template` not being able to assert that no files were rendered.
-
- *Guo Xiang Tan*
-
-* Extract source code for the entire exception stack trace for
- better debugging and diagnosis.
-
- *Ryan Dao*
-
-* Allows ActionDispatch::Request::LOCALHOST to match any IPv4 127.0.0.0/8
- loopback address.
-
- *Earl St Sauver*, *Sven Riedel*
-
-* Preserve original path in `ShowExceptions` middleware by stashing it as
- `env["action_dispatch.original_path"]`
-
- `ActionDispatch::ShowExceptions` overwrites `PATH_INFO` with the status code
- for the exception defined in `ExceptionWrapper`, so the path
- the user was visiting when an exception occurred was not previously
- available to any custom exceptions_app. The original `PATH_INFO` is now
- stashed in `env["action_dispatch.original_path"]`.
-
- *Grey Baker*
-
-* Use `String#bytesize` instead of `String#size` when checking for cookie
- overflow.
-
- *Agis Anastasopoulos*
-
-* `render nothing: true` or rendering a `nil` body no longer add a single
- space to the response body.
-
- The old behavior was added as a workaround for a bug in an early version of
- Safari, where the HTTP headers are not returned correctly if the response
- body has a 0-length. This is been fixed since and the workaround is no
- longer necessary.
-
- Use `render body: ' '` if the old behavior is desired.
-
- See #14883 for details.
-
- *Godfrey Chan*
-
-* Prepend a JS comment to JSONP callbacks. Addresses CVE-2014-4671
- ("Rosetta Flash").
-
- *Greg Campbell*
-
-* Because URI paths may contain non US-ASCII characters we need to force
- the encoding of any unescaped URIs to UTF-8 if they are US-ASCII.
- This essentially replicates the functionality of the monkey patch to
- URI.parser.unescape in active_support/core_ext/uri.rb.
-
- Fixes #16104.
-
- *Karl Entwistle*
-
-* Generate shallow paths for all children of shallow resources.
-
- Fixes #15783.
-
- *Seb Jacobs*
-
-* JSONP responses are now rendered with the `text/javascript` content type
- when rendering through a `respond_to` block.
-
- Fixes #15081.
-
- *Lucas Mazza*
-
-* Add `config.action_controller.always_permitted_parameters` to configure which
- parameters are permitted globally. The default value of this configuration is
- `['controller', 'action']`.
-
- *Gary S. Weaver*, *Rafael Chacon*
-
-* Fix env['PATH_INFO'] missing leading slash when a rack app mounted at '/'.
-
- Fixes #15511.
-
- *Larry Lv*
-
-* ActionController::Parameters#require now accepts `false` values.
-
- Fixes #15685.
-
- *Sergio Romano*
-
-* With authorization header `Authorization: Token token=`, `authenticate` now
- recognize token as nil, instead of "token".
-
- Fixes #14846.
-
- *Larry Lv*
-
-* Ensure the controller is always notified as soon as the client disconnects
- during live streaming, even when the controller is blocked on a write.
-
- *Nicholas Jakobsen*, *Matthew Draper*
-
-* Routes specifying 'to:' must be a string that contains a "#" or a rack
- application. Use of a symbol should be replaced with `action: symbol`.
- Use of a string without a "#" should be replaced with `controller: string`.
-
- *Aaron Patterson*
-
-* Fix URL generation with `:trailing_slash` such that it does not add
- a trailing slash after `.:format`
-
- *Dan Langevin*
-
-* Build full URI as string when processing path in integration tests for
- performance reasons.
-
- *Guo Xiang Tan*
-
-* Fix `'Stack level too deep'` when rendering `head :ok` in an action method
- called 'status' in a controller.
-
- Fixes #13905.
-
- *Christiaan Van den Poel*
-
-* Add MKCALENDAR HTTP method (RFC 4791).
-
- *Sergey Karpesh*
-
-* Instrument fragment cache metrics.
-
- Adds `:controller`: and `:action` keys to the instrumentation payload
- for the `*_fragment.action_controller` notifications. This allows tracking
- e.g. the fragment cache hit rates for each controller action.
-
- *Daniel Schierbeck*
-
-* Always use the provided port if the protocol is relative.
-
- Fixes #15043.
-
- *Guilherme Cavalcanti*, *Andrew White*
-
-* Moved `params[request_forgery_protection_token]` into its own method
- and improved tests.
-
- Fixes #11316.
-
- *Tom Kadwill*
+ *Rafael Mendonça França*
-* Added verification of route constraints given as a Proc or an object responding
- to `:matches?`. Previously, when given an non-complying object, it would just
- silently fail to enforce the constraint. It will now raise an `ArgumentError`
- when setting up the routes.
+* Remove deprecated usage of string keys in URL helpers.
- *Xavier Defrang*
+ *Rafael Mendonça França*
-* Properly treat the entire IPv6 User Local Address space as private for
- purposes of remote IP detection. Also handle uppercase private IPv6
- addresses.
+* Remove deprecated `only_path` option on `*_path` helpers.
- Fixes #12638.
+ *Rafael Mendonça França*
- *Caleb Spare*
+* Remove deprecated `NamedRouteCollection#helpers`.
-* Fixed an issue with migrating legacy json cookies.
+ *Rafael Mendonça França*
- Previously, the `VerifyAndUpgradeLegacySignedMessage` assumes all incoming
- cookies are marshal-encoded. This is not the case when `secret_token` is
- used in conjunction with the `:json` or `:hybrid` serializer.
+* Remove deprecated support to define routes with `:to` option that doesn't contain `#`.
- In those case, when upgrading to use `secret_key_base`, this would cause a
- `TypeError: incompatible marshal file format` and a 500 error for the user.
+ *Rafael Mendonça França*
- Fixes #14774.
+* Remove deprecated `ActionDispatch::Response#to_ary`.
- *Godfrey Chan*
+ *Rafael Mendonça França*
-* Make URL escaping more consistent:
+* Remove deprecated `ActionDispatch::Request#deep_munge`.
- 1. Escape '%' characters in URLs - only unescaped data should be passed to URL helpers
- 2. Add an `escape_segment` helper to `Router::Utils` that escapes '/' characters
- 3. Use `escape_segment` rather than `escape_fragment` in optimized URL generation
- 4. Use `escape_segment` rather than `escape_path` in URL generation
+ *Rafael Mendonça França*
- For point 4 there are two exceptions. Firstly, when a route uses wildcard segments
- (e.g. `*foo`) then we use `escape_path` as the value may contain '/' characters. This
- means that wildcard routes can't be optimized. Secondly, if a `:controller` segment
- is used in the path then this uses `escape_path` as the controller may be namespaced.
+* Remove deprecated `ActionDispatch::Http::Parameters#symbolized_path_parameters`.
- Fixes #14629, #14636 and #14070.
+ *Rafael Mendonça França*
- *Andrew White*, *Edho Arief*
+* Remove deprecated option `use_route` in controller tests.
-* Add alias `ActionDispatch::Http::UploadedFile#to_io` to
- `ActionDispatch::Http::UploadedFile#tempfile`.
+ *Rafael Mendonça França*
- *Tim Linquist*
+* Ensure `append_info_to_payload` is called even if an exception is raised.
-* Returns null type format when format is not know and controller is using `any`
- format block.
+ Fixes an issue where when an exception is raised in the request the additonal
+ payload data is not available.
- Fixes #14462.
+ See:
+ * #14903
+ * https://github.com/roidrage/lograge/issues/37
- *Rafael Mendonça França*
+ *Dieter Komendera*, *Margus Pärt*
-* Improve routing error page with fuzzy matching search.
+* Correctly rely on the response's status code to handle calls to `head`.
- *Winston*
+ *Robin Dupret*
-* Only make deeply nested routes shallow when parent is shallow.
+* Using `head` method returns empty response_body instead
+ of returning a single space " ".
- Fixes #14684.
+ The old behavior was added as a workaround for a bug in an early
+ version of Safari, where the HTTP headers are not returned correctly
+ if the response body has a 0-length. This is been fixed since and
+ the workaround is no longer necessary.
- *Andrew White*, *James Coglan*
+ Fixes #18253.
-* Append link to bad code to backtrace when exception is `SyntaxError`.
+ *Prathamesh Sonpatki*
- *Boris Kuznetsov*
+* Fix how polymorphic routes works with objects that implement `to_model`.
-* Swapped the parameters of assert_equal in `assert_select` so that the
- proper values were printed correctly.
+ *Travis Grathwell*
- Fixes #14422.
+* Stop converting empty arrays in `params` to `nil`
- *Vishal Lal*
+ This behaviour was introduced in response to CVE-2012-2660, CVE-2012-2694
+ and CVE-2013-0155
-* The method `shallow?` returns false if the parent resource is a singleton so
- we need to check if we're not inside a nested scope before copying the :path
- and :as options to their shallow equivalents.
+ ActiveRecord now issues a safe query when passing an empty array into
+ a where clause, so there is no longer a need to defend against this type
+ of input (any nils are still stripped from the array).
- Fixes #14388.
+ *Chris Sinjakli*
- *Andrew White*
+* Fixed usage of optional scopes in url helpers.
-* Make logging of CSRF failures optional (but on by default) with the
- `log_warning_on_csrf_failure` configuration setting in
- `ActionController::RequestForgeryProtection`.
+ *Alex Robbin*
- *John Barton*
+* Fixed handling of positional url helper arguments when `format: false`.
-* Fix URL generation in controller tests with request-dependent
- `default_url_options` methods.
+ Fixes #17819.
- *Tony Wooster*
+ *Andrew White*, *Tatiana Soukiassian*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 5196e67e55..f83823dd75 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).'
s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
@@ -21,10 +21,10 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
- s.add_dependency 'rack', '~> 1.6.0.beta'
+ s.add_dependency 'rack', '~> 1.6.0'
s.add_dependency 'rack-test', '~> 0.6.2'
s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1'
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
s.add_dependency 'actionview', version
s.add_development_dependency 'activemodel', version
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 4026dab2ce..8c7cec3561 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -12,7 +12,7 @@ module AbstractController
class ActionNotFound < StandardError
end
- # <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
+ # AbstractController::Base is a low-level API. Nobody should be
# using it directly, and subclasses (like ActionController::Base) are
# expected to provide their own +render+ method, since rendering means
# different things depending on the context.
@@ -69,9 +69,9 @@ module AbstractController
# A list of method names that should be considered actions. This
# includes all public instance methods on a controller, less
- # any internal methods (see #internal_methods), adding back in
+ # any internal methods (see internal_methods), adding back in
# any methods that are internal, but still exist on the class
- # itself. Finally, #hidden_actions are removed.
+ # itself. Finally, hidden_actions are removed.
#
# ==== Returns
# * <tt>Set</tt> - A set of all methods that should be considered actions.
@@ -82,25 +82,28 @@ module AbstractController
# Except for public instance methods of Base and its ancestors
internal_methods +
# Be sure to include shadowed public instance methods of this class
- public_instance_methods(false)).uniq.map { |x| x.to_s } -
+ public_instance_methods(false)).uniq.map(&:to_s) -
# And always exclude explicitly hidden actions
hidden_actions.to_a
- # Clear out AS callback method pollution
- Set.new(methods.reject { |method| method =~ /_one_time_conditions/ })
+ methods.to_set
end
end
# action_methods are cached and there is sometimes need to refresh
- # them. clear_action_methods! allows you to do that, so next time
+ # them. ::clear_action_methods! allows you to do that, so next time
# you run action_methods, they will be recalculated
def clear_action_methods!
@action_methods = nil
end
# Returns the full controller name, underscored, without the ending Controller.
- # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
- # controller_path.
+ #
+ # class MyApp::MyPostsController < AbstractController::Base
+ # end
+ # end
+ #
+ # MyApp::MyPostsController.controller_path # => "my_app/my_posts"
#
# ==== Returns
# * <tt>String</tt>
@@ -137,12 +140,12 @@ module AbstractController
process_action(action_name, *args)
end
- # Delegates to the class' #controller_path
+ # Delegates to the class' ::controller_path
def controller_path
self.class.controller_path
end
- # Delegates to the class' #action_methods
+ # Delegates to the class' ::action_methods
def action_methods
self.class.action_methods
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index ca5c80cd71..32de82780f 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -9,7 +9,7 @@ module AbstractController
included do
define_callbacks :process_action,
- terminator: ->(controller,_) { controller.response_body },
+ terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.response_body },
skip_after_callbacks_if_terminated: true
end
@@ -22,10 +22,11 @@ module AbstractController
end
module ClassMethods
- # If :only or :except are used, convert the options into the
- # :unless and :if options of ActiveSupport::Callbacks.
- # The basic idea is that :only => :index gets converted to
- # :if => proc {|c| c.action_name == "index" }.
+ # If +:only+ or +:except+ are used, convert the options into the
+ # +:if+ and +:unless+ options of ActiveSupport::Callbacks.
+ #
+ # The basic idea is that <tt>:only => :index</tt> gets converted to
+ # <tt>:if => proc {|c| c.action_name == "index" }</tt>.
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index df7382f02d..109eff10eb 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -184,7 +184,7 @@ module AbstractController
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
- rescue MissingSourceFile => e
+ rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 9d10140ed2..5514213ad8 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -17,8 +17,8 @@ module AbstractController
extend ActiveSupport::Concern
include ActionView::ViewPaths
- # Normalize arguments, options and then delegates render_to_body and
- # sticks the result in self.response_body.
+ # Normalizes arguments, options and then delegates render_to_body and
+ # sticks the result in <tt>self.response_body</tt>.
# :api: public
def render(*args, &block)
options = _normalize_render(*args, &block)
@@ -30,11 +30,11 @@ module AbstractController
# Raw rendering of a template to a string.
#
# It is similar to render, except that it does not
- # set the response_body and it should be guaranteed
+ # set the +response_body+ and it should be guaranteed
# to always return a string.
#
- # If a component extends the semantics of response_body
- # (as Action Controller extends it to be anything that
+ # If a component extends the semantics of +response_body+
+ # (as ActionController extends it to be anything that
# responds to the method each), this method needs to be
# overridden in order to still return a string.
# :api: plugin
@@ -73,8 +73,9 @@ module AbstractController
}
end
- # Normalize args by converting render "foo" to render :action => "foo" and
- # render "foo/bar" to render :file => "foo/bar".
+ # Normalize args by converting <tt>render "foo"</tt> to
+ # <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to
+ # <tt>render :file => "foo/bar"</tt>.
# :api: plugin
def _normalize_args(action=nil, options={})
if action.is_a? Hash
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 7bbf938987..5cb11bc479 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -44,7 +44,7 @@ module ActionController
# The full request object is available via the request accessor and is primarily used to query for HTTP headers:
#
# def server_ip
- # location = request.env["SERVER_ADDR"]
+ # location = request.env["REMOTE_ADDR"]
# render plain: "This server hosted at #{location}"
# end
#
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 89fa75f025..87609d8aa7 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -1,4 +1,3 @@
-
module ActionController
class LogSubscriber < ActiveSupport::LogSubscriber
INTERNAL_PARAMS = %w(controller action format _method only_path)
@@ -54,15 +53,6 @@ module ActionController
end
end
- def deep_munge(event)
- debug do
- "Value for params[:#{event.payload[:keys].join('][:')}] was set "\
- "to nil, because it was one of [], [null] or [null, null, ...]. "\
- "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\
- "for more information."\
- end
- end
-
%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 6dd213b2f7..993f8e150d 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -173,6 +173,7 @@ module ActionController
def status
@_status
end
+ alias :response_code :status # :nodoc:
def status=(status)
@_status = Rack::Utils.status_code(status)
@@ -236,9 +237,5 @@ module ActionController
lambda { |env| new.dispatch(name, klass.new(env)) }
end
end
-
- def _status_code #:nodoc:
- @_status
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
index 3ca0c6837a..f9303efe6c 100644
--- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
@@ -7,8 +7,8 @@ module ActionController
#
# config.action_controller.etag_with_template_digest = false
#
- # Override the template to digest by passing `:template` to `fresh_when`
- # and `stale?` calls. For example:
+ # Override the template to digest by passing +:template+ to +fresh_when+
+ # and +stale?+ calls. For example:
#
# # We're going to render widgets/show, not posts/show
# fresh_when @post, template: 'widgets/show'
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 3d2badf9c2..0d93e2f7aa 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -29,14 +29,14 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- if include_content?(self._status_code)
+ self.response_body = ""
+
+ if include_content?(self.response_code)
self.content_type = content_type || (Mime[formats.first] if formats)
self.response.charset = false if self.response
- self.response_body = " "
else
headers.delete('Content-Type')
headers.delete('Content-Length')
- self.response_body = ""
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 25c123edf7..a219d35b25 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -53,10 +53,8 @@ module ActionController
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
- # get(
- # "/notes/1.xml", nil,
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
- # )
+ # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
+ # get "/notes/1.xml"
#
# assert_equal 200, status
# end
@@ -397,6 +395,7 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
+ TOKEN_KEY = 'token='
TOKEN_REGEX = /^Token /
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
@@ -462,16 +461,22 @@ module ActionController
raw_params.map { |param| param.split %r/=(.+)?/ }
end
- # This removes the `"` characters wrapping the value.
+ # This removes the <tt>"</tt> characters wrapping the value.
def rewrite_param_values(array_params)
array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
end
# This method takes an authorization body and splits up the key-value
- # pairs by the standardized `:`, `;`, or `\t` delimiters defined in
- # `AUTHN_PAIR_DELIMITERS`.
+ # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
+ # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
def raw_params(auth)
- auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+ _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+
+ if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
+ _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
+ end
+
+ _raw_params
end
# Encodes the given token and options into an Authorization header value.
@@ -481,7 +486,7 @@ module ActionController
#
# Returns String.
def encode_credentials(token, options = {})
- values = ["token=#{token.to_s.inspect}"] + options.map do |key, value|
+ values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
"#{key}=#{value.to_s.inspect}"
end
"Token #{values * ", "}"
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index b0e164bc57..a3e1a71b0a 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -21,17 +21,20 @@ module ActionController
:action => self.action_name,
:params => request.filtered_parameters,
:format => request.format.try(:ref),
- :method => request.method,
+ :method => request.request_method,
:path => (request.fullpath rescue "unknown")
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
- result = super
- payload[:status] = response.status
- append_info_to_payload(payload)
- result
+ begin
+ result = super
+ payload[:status] = response.status
+ result
+ ensure
+ append_info_to_payload(payload)
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 1e13b3761f..7590fb6843 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -189,12 +189,6 @@ module ActionController
!@aborted
end
- def await_close
- synchronize do
- @cv.wait_until { @closed }
- end
- end
-
def on_error(&block)
@error_callback = block
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index ac1f209232..7dae171215 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,28 +1,7 @@
-require 'active_support/core_ext/array/extract_options'
require 'abstract_controller/collector'
module ActionController #:nodoc:
module MimeResponds
- extend ActiveSupport::Concern
-
- module ClassMethods
- def respond_to(*)
- raise NoMethodError, "The controller-level `respond_to' feature has " \
- "been extracted to the `responders` gem. Add it to your Gemfile to " \
- "continue using this feature:\n" \
- " gem 'responders', '~> 2.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
- end
-
- def respond_with(*)
- raise NoMethodError, "The `respond_with' feature has been extracted " \
- "to the `responders` gem. Add it to your Gemfile to continue using " \
- "this feature:\n" \
- " gem 'responders', '~> 2.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
-
# Without web-service support, an action which collects the data for displaying a list of people
# might look something like this:
#
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 09c7a6f946..a7e734db42 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
-require 'active_support/core_ext/struct'
require 'action_dispatch/http/mime_type'
module ActionController
@@ -86,7 +85,7 @@ module ActionController
new name, format, include, exclude, nil, nil
end
- def initialize(name, format, include, exclude, klass, model) # nodoc
+ def initialize(name, format, include, exclude, klass, model) # :nodoc:
super
@include_set = include
@name_set = name
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index fd20682f8f..0932f03916 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -29,14 +29,7 @@ module ActionController #:nodoc:
# you're building an API you'll need something like:
#
# class ApplicationController < ActionController::Base
- # protect_from_forgery
- # skip_before_action :verify_authenticity_token, if: :json_request?
- #
- # protected
- #
- # def json_request?
- # request.format.json?
- # end
+ # protect_from_forgery unless: -> { request.format.json? }
# end
#
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
@@ -93,6 +86,7 @@ module ActionController #:nodoc:
# Valid Options:
#
# * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
+ # * <tt>:if/:unless</tt> - Passed to the <tt>before_action</tt> call. Set when actions are verified.
# * <tt>:with</tt> - Set the method to handle unverified request.
#
# Valid unverified request handling methods are:
@@ -209,6 +203,7 @@ module ActionController #:nodoc:
forgery_protection_strategy.new(self).handle_unverified_request
end
+ #:nodoc:
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
"<script> tag on another site requested protected JavaScript. " \
"If you know what you're doing, go ahead and disable forgery " \
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index a5ee1e2159..01bbd749c1 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -92,7 +92,11 @@ module ActionController
# params.permit(:c)
# # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
#
- # <tt>ActionController::Parameters</tt> is inherited from
+ # Please note that these options *are not thread-safe*. In a multi-threaded
+ # environment they should only be set once at boot-time and never mutated at
+ # runtime.
+ #
+ # <tt>ActionController::Parameters</tt> inherits from
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
#
@@ -163,6 +167,12 @@ module ActionController
end
end
+ # Returns an unsafe, unfiltered +Hash+ representation of this parameter.
+ def to_unsafe_h
+ to_hash
+ end
+ alias_method :to_unsafe_hash, :to_unsafe_h
+
# Convert all hashes in values into parameters, then yield each pair like
# the same way as <tt>Hash#each_pair</tt>
def each_pair(&block)
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index dd8da4b5dc..d01927b7cb 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -24,7 +24,7 @@ module ActionController
module ClassMethods
def before_filters
- _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
+ _process_action_callbacks.find_all{|x| x.kind == :before}.map(&:name)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 0f2fa5fb08..572d1770f7 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -30,9 +30,9 @@ module ActionController
:_recall => request.path_parameters
}.merge!(super).freeze
- if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) ||
- (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze])
+ if (same_origin = _routes.equal?(request.routes)) ||
+ (script_name = request.engine_script_name(_routes)) ||
+ (original_script_name = request.original_script_name)
options = @_url_options.dup
if original_script_name
diff --git a/actionpack/lib/action_controller/model_naming.rb b/actionpack/lib/action_controller/model_naming.rb
deleted file mode 100644
index 2b33f67263..0000000000
--- a/actionpack/lib/action_controller/model_naming.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-module ActionController
- module ModelNaming
- # Converts the given object to an ActiveModel compliant one.
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
- end
-
- def model_name_from_record_or_class(record_or_class)
- convert_to_model(record_or_class).model_name
- end
- end
-end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 30eae41f60..d30615fade 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -66,7 +66,10 @@ module ActionController
def reset_template_assertion
RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- instance_variable_get("@_#{instance_variable}").clear
+ ivar_name = "@_#{instance_variable}"
+ if instance_variable_defined?(ivar_name)
+ instance_variable_get(ivar_name).clear
+ end
end
end
@@ -144,6 +147,8 @@ module ActionController
assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
when nil, false
assert(@_layouts.empty?, msg)
+ else
+ raise ArgumentError, "assert_template only accepts a String, Symbol, Regexp, nil or false for :layout"
end
end
@@ -710,8 +715,7 @@ module ActionController
:relative_url_root => nil,
:_recall => @request.path_parameters)
- route_name = options.delete :use_route
- url, query_string = @routes.path_for(options, route_name).split("?", 2)
+ url, query_string = @routes.path_for(options).split("?", 2)
@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
@request.env["PATH_INFO"] = url
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 11b5e6be33..dcd3ee0644 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 9c8f65deac..53a98c5d0a 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -72,11 +72,12 @@ module ActionDispatch
end
end
end
+
# Sets the \variant for template.
def variant=(variant)
if variant.is_a?(Symbol)
@variant = [variant]
- elsif variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
+ elsif variant.nil? || variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
@variant = variant
else
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index b9d5009683..047a17937a 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -6,7 +6,7 @@ require 'active_support/core_ext/string/starts_ends_with'
module Mime
class Mimes < Array
def symbols
- @symbols ||= map { |m| m.to_sym }
+ @symbols ||= map(&:to_sym)
end
%w(<< concat shift unshift push pop []= clear compact! collect!
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index b655a54865..df4b073a17 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -56,7 +56,7 @@ module ActionDispatch
elsif value.is_a?(Array)
value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
elsif blocks.any?
- key = key.dup
+ key = key.dup if key.duplicable?
value = value.dup if value.duplicable?
blocks.each { |b| b.call(key, value) }
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index a5cd26a3c1..c2f05ecc86 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/deprecation'
module ActionDispatch
module Http
@@ -25,13 +24,6 @@ module ActionDispatch
@env[PARAMETERS_KEY] = parameters
end
- def symbolized_path_parameters
- ActiveSupport::Deprecation.warn(
- '`symbolized_path_parameters` is deprecated. Please use `path_parameters`.'
- )
- path_parameters
- end
-
# Returns a hash with the \parameters used to form the \path of the request.
# Returned hash keys are strings:
#
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 2a7bb374a5..cadbfc88cb 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -105,6 +105,18 @@ module ActionDispatch
@request_method ||= check_method(env["REQUEST_METHOD"])
end
+ def routes # :nodoc:
+ env["action_dispatch.routes".freeze]
+ end
+
+ def original_script_name # :nodoc:
+ env['ORIGINAL_SCRIPT_NAME'.freeze]
+ end
+
+ def engine_script_name(_routes) # :nodoc:
+ env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]
+ end
+
def request_method=(request_method) #:nodoc:
if check_method(request_method)
@request_method = env["REQUEST_METHOD"] = request_method
@@ -325,15 +337,6 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
- # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility.
- def deep_munge(hash)
- ActiveSupport::Deprecation.warn(
- 'This method has been extracted into `ActionDispatch::Request::Utils.deep_munge`. Please start using that instead.'
- )
-
- Utils.deep_munge(hash)
- end
-
protected
def parse_query(qs)
Utils.deep_munge(super)
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 33de2f8b5f..4061ea71a3 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,6 +1,4 @@
require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/string/filters'
-require 'active_support/deprecation'
require 'action_dispatch/http/filter_redirect'
require 'monitor'
@@ -284,20 +282,6 @@ module ActionDispatch # :nodoc:
end
alias prepare! to_a
- # Be super clear that a response object is not an Array. Defining this
- # would make implicit splatting work, but it also makes adding responses
- # as arrays work, and "flattening" responses, cascading to the rack body!
- # Not sensible behavior.
- def to_ary
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `ActionDispatch::Response#to_ary` no longer performs implicit conversion
- to an array. Please use `response.to_a` instead, or a splat like `status,
- headers, body = *response`.
- MSG
-
- to_a
- end
-
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
# assert_equal 'AuthorOfNewPage', r.cookies['author']
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 6b8dcaf497..001b14ec97 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -12,10 +12,22 @@ module ActionDispatch
self.tld_length = 1
class << self
+ # Returns the domain part of a host given the domain level.
+ #
+ # # Top-level domain example
+ # extract_domain('www.example.com', 1) # => "example.com"
+ # # Second-level domain example
+ # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
def extract_domain(host, tld_length)
extract_domain_from(host, tld_length) if named_host?(host)
end
+ # Returns the subdomains of a host as an Array given the domain level.
+ #
+ # # Top-level domain example
+ # extract_subdomains('www.example.com', 1) # => ["www"]
+ # # Second-level domain example
+ # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
def extract_subdomains(host, tld_length)
if named_host?(host)
extract_subdomains_from(host, tld_length)
@@ -24,6 +36,12 @@ module ActionDispatch
end
end
+ # Returns the subdomains of a host as a String given the domain level.
+ #
+ # # Top-level domain example
+ # extract_subdomain('www.example.com', 1) # => "www"
+ # # Second-level domain example
+ # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
def extract_subdomain(host, tld_length)
extract_subdomains(host, tld_length).join('.')
end
@@ -68,7 +86,9 @@ module ActionDispatch
end
def add_anchor(path, anchor)
- path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param.to_s)}"
+ if anchor
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
+ end
end
def extract_domain_from(host, tld_length)
@@ -171,16 +191,43 @@ module ActionDispatch
end
# Returns the complete URL used for this request.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.url # => "http://example.com"
def url
protocol + host_with_port + fullpath
end
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.protocol # => "http://"
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
+ # req.protocol # => "https://"
def protocol
@protocol ||= ssl? ? 'https://' : 'http://'
end
# Returns the \host for this request, such as "example.com".
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.raw_host_with_port # => "example.com"
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.raw_host_with_port # => "example.com:8080"
def raw_host_with_port
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
@@ -190,17 +237,44 @@ module ActionDispatch
end
# Returns the host for this request, such as example.com.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.host # => "example.com"
def host
raw_host_with_port.sub(/:\d+$/, '')
end
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.host_with_port # => "example.com"
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.host_with_port # => "example.com:8080"
def host_with_port
"#{host}#{port_string}"
end
# Returns the port number of this request as an integer.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.port # => 80
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.port # => 8080
def port
@port ||= begin
if raw_host_with_port =~ /:(\d+)$/
@@ -212,6 +286,13 @@ module ActionDispatch
end
# Returns the standard \port number for this request's protocol.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.standard_port # => 80
def standard_port
case protocol
when 'https://' then 443
@@ -220,18 +301,48 @@ module ActionDispatch
end
# Returns whether this request is using the standard port
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.standard_port? # => true
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.standard_port? # => false
def standard_port?
port == standard_port
end
# Returns a number \port suffix like 8080 if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.optional_port # => nil
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.optional_port # => 8080
def optional_port
standard_port? ? nil : port
end
# Returns a string \port suffix, including colon, like ":8080" if the \port
# number of this request is not the default HTTP \port 80 or HTTPS \port 443.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.port_string # => ""
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.port_string # => ":8080"
def port_string
standard_port? ? '' : ":#{port}"
end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 1b914f0637..d7ce6042c2 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -109,7 +109,7 @@ module ActionDispatch
svg = to_svg
javascripts = [states, fsm_js]
- # Annoying hack for 1.9 warnings
+ # Annoying hack warnings
fun_routes = fun_routes
stylesheets = stylesheets
svg = svg
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
index 66e414213a..0ccab21801 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -45,51 +45,6 @@ module ActionDispatch
(@table.keys + @table.values.flat_map(&:keys)).uniq
end
- # Returns a generalized transition graph with reduced states. The states
- # are reduced like a DFA, but the table must be simulated like an NFA.
- #
- # Edges of the GTG are regular expressions.
- def generalized_table
- gt = GTG::TransitionTable.new
- marked = {}
- state_id = Hash.new { |h,k| h[k] = h.length }
- alphabet = self.alphabet
-
- stack = [eclosure(0)]
-
- until stack.empty?
- state = stack.pop
- next if marked[state] || state.empty?
-
- marked[state] = true
-
- alphabet.each do |alpha|
- next_state = eclosure(following_states(state, alpha))
- next if next_state.empty?
-
- gt[state_id[state], state_id[next_state]] = alpha
- stack << next_state
- end
- end
-
- final_groups = state_id.keys.find_all { |s|
- s.sort.last == accepting
- }
-
- final_groups.each do |states|
- id = state_id[states]
-
- gt.add_accepting(id)
- save = states.find { |s|
- @memos.key?(s) && eclosure(s).sort.last == accepting
- }
-
- gt.add_memo(id, memo(save))
- end
-
- gt
- end
-
# Returns set of NFA states to which there is a transition on ast symbol
# +a+ from some state +s+ in +t+.
def following_states(t, a)
@@ -107,7 +62,7 @@ module ActionDispatch
end
def alphabet
- inverted.values.flat_map(&:keys).compact.uniq.sort_by { |x| x.to_s }
+ inverted.values.flat_map(&:keys).compact.uniq.sort_by(&:to_s)
end
# Returns a set of NFA states reachable from some NFA state +s+ in set
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 3af940a02f..64b48ca45f 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -42,7 +42,7 @@ module ActionDispatch
end
def names
- @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
+ @names ||= spec.grep(Nodes::Symbol).map(&:name)
end
def required_names
@@ -52,7 +52,7 @@ module ActionDispatch
def optional_names
@optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
group.grep(Nodes::Symbol)
- }.map { |n| n.name }.uniq
+ }.map(&:name).uniq
end
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
@@ -122,6 +122,11 @@ module ActionDispatch
re = @matchers[node.left.to_sym] || '.+'
"(#{re})"
end
+
+ def visit_OR(node)
+ children = node.children.map { |n| visit n }
+ "(?:#{children.join(?|)})"
+ end
end
class UnanchoredRegexp < AnchoredRegexp # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 9f0a3af902..4d5c18984a 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -60,7 +60,7 @@ module ActionDispatch
end
def parts
- @parts ||= segments.map { |n| n.to_sym }
+ @parts ||= segments.map(&:to_sym)
end
alias :segment_keys :parts
@@ -68,12 +68,8 @@ module ActionDispatch
@path_formatter.evaluate path_options
end
- def optional_parts
- path.optional_names.map { |n| n.to_sym }
- end
-
def required_parts
- @required_parts ||= path.required_names.map { |n| n.to_sym }
+ @required_parts ||= path.required_names.map(&:to_sym)
end
def required_default?(key)
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 9131b65380..2b036796ab 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -68,8 +68,8 @@ module ActionDispatch
def visualizer
tt = GTG::Builder.new(ast).transition_table
- groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
- asts = groups.values.map { |v| v.first }
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
+ asts = groups.values.map(&:first)
tt.visualizer(asts)
end
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/fsm.css b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
index 50caebaa18..403e16a7bb 100644
--- a/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
+++ b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
@@ -16,10 +16,6 @@ h2 {
font-size: 0.5em;
}
-div#chart-2 {
- height: 350px;
-}
-
.clearfix {display: inline-block; }
.input { overflow: show;}
.instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em}
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index baf9d5779e..f80df78582 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,6 +1,6 @@
module ActionDispatch
- # Provide callbacks to be executed before and after the request dispatch.
+ # Provides callbacks to be executed before and after dispatching the request.
class Callbacks
include ActiveSupport::Callbacks
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 9037bf0e0a..8d3ce24612 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -71,11 +71,13 @@ module ActionDispatch
# restrict to the domain level. If you use a schema like www.example.com
# and want to share session with user.example.com set <tt>:domain</tt>
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
- # <tt>:all</tt> again when deleting cookies.
+ # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
#
# domain: nil # Does not sets cookie domain. (default)
# domain: :all # Allow the cookie for the top most level
# # domain and subdomains.
+ # domain: %w(.example.com .example.org) # Allow the cookie
+ # # for concrete domain names.
#
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
@@ -281,7 +283,7 @@ module ActionDispatch
def handle_options(options) #:nodoc:
options[:path] ||= "/"
- if options[:domain] == :all
+ if options[:domain] == :all || options[:domain] == 'all'
# if there is a provided tld length then we use it otherwise default domain regexp
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
@@ -408,7 +410,7 @@ module ActionDispatch
@options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
end
- def serialize(name, value)
+ def serialize(value)
serializer.dump(value)
end
@@ -461,9 +463,9 @@ module ActionDispatch
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
- options[:value] = @verifier.generate(serialize(name, options[:value]))
+ options[:value] = @verifier.generate(serialize(options[:value]))
else
- options = { :value => @verifier.generate(serialize(name, options)) }
+ options = { :value => @verifier.generate(serialize(options)) }
end
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@@ -522,7 +524,7 @@ module ActionDispatch
options = { :value => options }
end
- options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@parent_jar[name] = options
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index c1562fcc0d..9082aac271 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -1,6 +1,10 @@
require 'action_dispatch/http/request'
require 'action_dispatch/middleware/exception_wrapper'
require 'action_dispatch/routing/inspector'
+require 'action_view'
+require 'action_view/base'
+
+require 'pp'
module ActionDispatch
# This middleware is responsible for logging exceptions and
@@ -8,6 +12,32 @@ module ActionDispatch
class DebugExceptions
RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
+ class DebugView < ActionView::Base
+ def debug_params(params)
+ clean_params = params.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ if clean_params.empty?
+ 'None'
+ else
+ PP.pp(clean_params, "", 200)
+ end
+ end
+
+ def debug_headers(headers)
+ if headers.present?
+ headers.inspect.gsub(',', ",\n")
+ else
+ 'None'
+ end
+ end
+
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ end
+ end
+
def initialize(app, routes_app = nil)
@app = app
@routes_app = routes_app
@@ -46,7 +76,7 @@ module ActionDispatch
source_to_show_id = source_to_show[:id]
end
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
+ template = DebugView.new([RESCUES_TEMPLATE_PATH],
request: request,
exception: wrapper.exception,
traces: traces,
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 7a91674c3c..a7f95150a4 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -79,7 +79,7 @@ module ActionDispatch
class FlashHash
include Enumerable
- def self.from_session_value(value)
+ 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))
@@ -91,8 +91,11 @@ module ActionDispatch
flash.tap(&:sweep)
end
-
- def to_session_value
+
+ # Builds a hash containing the discarded values and the hashes
+ # representing the flashes.
+ # If there are no values in @flashes, returns nil.
+ def to_session_value #:nodoc:
return nil if empty?
{'discard' => @discard.to_a, 'flashes' => @flashes}
end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index b426183488..29d43faeed 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -47,7 +47,7 @@ module ActionDispatch
else
false
end
- rescue Exception => e # JSON or Ruby code block errors
+ rescue => e # JSON or Ruby code block errors
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise ParseError.new(e.message, e)
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
index db219c8fa9..49b1e83551 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
@@ -5,20 +5,8 @@
<pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
<% end %>
-<%
- clean_params = @request.filtered_parameters.clone
- clean_params.delete("action")
- clean_params.delete("controller")
-
- request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
-
- def debug_hash(object)
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
- end unless self.class.method_defined?(:debug_hash)
-%>
-
<h2 style="margin-top: 30px">Request</h2>
-<p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
+<p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
<div class="details">
<div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
@@ -31,4 +19,4 @@
</div>
<h2 style="margin-top: 30px">Response</h2>
-<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
+<p><b>Headers</b>:</p> <pre><%= debug_headers(defined?(@response) ? @response.headers : {}) %></pre>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
index 5c016e544e..2a65fd06ad 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
@@ -4,4 +4,8 @@
<div id="container">
<h2><%= h @exception.message %></h2>
+
+ <%= render template: "rescues/_source" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
index 7e9cedb95e..55dd5ddc7b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
@@ -27,4 +27,6 @@
<%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %>
<% end %>
+
+ <%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 6ffa242da4..5cee0b5932 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -1,6 +1,6 @@
<% content_for :style do %>
#route_table {
- margin: 0 auto 0;
+ margin: 0;
border-collapse: collapse;
}
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 9d4f1aa3c5..1c9371d89c 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -16,10 +16,6 @@ module ActionDispatch
when Array
v.grep(Hash) { |x| deep_munge(x, keys) }
v.compact!
- if v.empty?
- hash[k] = nil
- ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys)
- end
when Hash
deep_munge(v, keys)
end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index cfe2237512..df5ebb6751 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -114,9 +114,7 @@ module ActionDispatch
def collect_routes(routes)
routes.collect do |route|
RouteWrapper.new(route)
- end.reject do |route|
- route.internal?
- end.collect do |route|
+ end.reject(&:internal?).collect do |route|
collect_engine_routes(route)
{ name: route.name,
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ac03ecb2c8..8b04dfaa45 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -4,11 +4,9 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/string/filters'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
require 'action_dispatch/routing/endpoint'
-require 'active_support/deprecation'
module ActionDispatch
module Routing
@@ -244,12 +242,10 @@ module ActionDispatch
def app(blocks)
if to.respond_to?(:call)
Constraints.new(to, blocks, false)
+ elsif blocks.any?
+ Constraints.new(dispatcher(defaults), blocks, true)
else
- if blocks.any?
- Constraints.new(dispatcher(defaults), blocks, true)
- else
- dispatcher(defaults)
- end
+ dispatcher(defaults)
end
end
@@ -281,22 +277,8 @@ module ActionDispatch
end
def split_to(to)
- case to
- when Symbol
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Defining a route where `to` is a symbol is deprecated.
- Please change `to: :#{to}` to `action: :#{to}`.
- MSG
-
- [nil, to.to_s]
- when /#/ then to.split('#')
- when String
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Defining a route where `to` is a controller without an action is deprecated.
- Please change `to: :#{to}` to `controller: :#{to}`.
- MSG
-
- [to, nil]
+ if to =~ /#/
+ to.split('#')
else
[]
end
@@ -391,7 +373,7 @@ module ActionDispatch
# Matches a url pattern to one or more routes.
#
- # You should not use the `match` method in your router
+ # You should not use the +match+ method in your router
# without specifying an HTTP method.
#
# If you want to expose your action to both GET and POST, use:
@@ -402,7 +384,7 @@ module ActionDispatch
# Note that +:controller+, +:action+ and +:id+ are interpreted as url
# query parameters and thus available through +params+ in an action.
#
- # If you want to expose your action to GET, use `get` in the router:
+ # If you want to expose your action to GET, use +get+ in the router:
#
# Instead of:
#
@@ -457,7 +439,7 @@ module ActionDispatch
# The route's action.
#
# [:param]
- # Overrides the default resource identifier `:id` (name of the
+ # Overrides the default resource identifier +:id+ (name of the
# dynamic segment used to generate the routes).
# You can access that segment from your controller using
# <tt>params[<:param>]</tt>.
@@ -582,13 +564,7 @@ module ActionDispatch
raise "A rack application must be specified" unless path
rails_app = rails_app? app
-
- if rails_app
- options[:as] ||= app.railtie_name
- else
- # non rails apps can't have an :as
- options[:as] = nil
- end
+ options[:as] ||= app_name(app, rails_app)
target_as = name_for_action(options[:as], path)
options[:via] ||= :all
@@ -620,6 +596,15 @@ module ActionDispatch
app.is_a?(Class) && app < Rails::Railtie
end
+ def app_name(app, rails_app)
+ if rails_app
+ app.railtie_name
+ elsif app.is_a?(Class)
+ class_name = app.name
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
+ end
+ end
+
def define_generate_prefix(app, name)
_route = @set.named_routes.get name
_routes = @set
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 0847842fa2..2e116ea9cd 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -1,5 +1,3 @@
-require 'action_controller/model_naming'
-
module ActionDispatch
module Routing
# Polymorphic URL helpers are methods for smart resolution to a named route call when
@@ -55,8 +53,6 @@ module ActionDispatch
# form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
- include ActionController::ModelNaming
-
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
#
@@ -251,7 +247,7 @@ module ActionDispatch
args = []
model = record.to_model
- name = if record.persisted?
+ name = if model.persisted?
args << model
model.model_name.singular_route_key
else
@@ -294,11 +290,12 @@ module ActionDispatch
when Class
@key_strategy.call record.model_name
else
- if record.persisted?
- args << record.to_model
- record.to_model.model_name.singular_route_key
+ model = record.to_model
+ if model.persisted?
+ args << model
+ model.model_name.singular_route_key
else
- @key_strategy.call record.to_model.model_name
+ @key_strategy.call model.model_name
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index a641ea3ea9..b4c861d306 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -6,7 +6,6 @@ require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/string/filters'
require 'action_controller/metal/exceptions'
require 'action_dispatch/http/request'
require 'action_dispatch/routing/endpoint'
@@ -87,7 +86,7 @@ module ActionDispatch
# named routes.
class NamedRouteCollection #:nodoc:
include Enumerable
- attr_reader :routes, :url_helpers_module
+ attr_reader :routes, :url_helpers_module, :path_helpers_module
def initialize
@routes = {}
@@ -102,14 +101,6 @@ module ActionDispatch
@path_helpers.include?(key) || @url_helpers.include?(key)
end
- def helpers
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `named_routes.helpers` is deprecated, please use `route_defined?(route_name)`
- to see if a named route was defined.
- MSG
- @path_helpers + @url_helpers
- end
-
def helper_names
@path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
end
@@ -138,7 +129,7 @@ module ActionDispatch
@url_helpers_module.send :undef_method, url_name
end
routes[key] = route
- define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY
+ define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN
@path_helpers << path_name
@@ -170,25 +161,6 @@ module ActionDispatch
routes.length
end
- def path_helpers_module(warn = false)
- if warn
- mod = @path_helpers_module
- helpers = @path_helpers
- Module.new do
- include mod
-
- helpers.each do |meth|
- define_method(meth) do |*args, &block|
- ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead")
- super(*args, &block)
- end
- end
- end
- else
- @path_helpers_module
- end
- end
-
class UrlHelper # :nodoc:
def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
@@ -280,14 +252,20 @@ module ActionDispatch
end
def handle_positional_args(controller_options, inner_options, args, result, path_params)
-
if args.size > 0
- if args.size < path_params.size - 1 # take format into account
+ # take format into account
+ if path_params.include?(:format)
+ path_params_size = path_params.size - 1
+ else
+ path_params_size = path_params.size
+ end
+
+ if args.size < path_params_size
path_params -= controller_options.keys
path_params -= result.keys
end
path_params.each { |param|
- result[param] = inner_options[param] || args.shift
+ result[param] = inner_options.fetch(param) { args.shift }
}
end
@@ -324,34 +302,7 @@ module ActionDispatch
# :stopdoc:
# strategy for building urls to send to the client
PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
- FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) }
UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
- LEGACY = ->(options) {
- if options.key?(:only_path)
- if options[:only_path]
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You are calling a `*_path` helper with the `only_path` option
- explicitly set to `true`. This option will stop working on
- path helpers in Rails 5. Simply remove the `only_path: true`
- argument from your call as it is redundant when applied to a
- path helper.
- MSG
-
- PATH.call(options)
- else
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You are calling a `*_path` helper with the `only_path` option
- explicitly set to `false`. This option will stop working on
- path helpers in Rails 5. Use the corresponding `*_url` helper
- instead.
- MSG
-
- FULL.call(options)
- end
- else
- PATH.call(options)
- end
- }
# :startdoc:
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
@@ -457,7 +408,7 @@ module ActionDispatch
RUBY
end
- def url_helpers(include_path_helpers = true)
+ def url_helpers(supports_path = true)
routes = self
Module.new do
@@ -484,14 +435,12 @@ module ActionDispatch
# named routes...
include url_helpers
- if include_path_helpers
+ if supports_path
path_helpers = routes.named_routes.path_helpers_module
- else
- path_helpers = routes.named_routes.path_helpers_module(true)
- end
- include path_helpers
- extend path_helpers
+ include path_helpers
+ extend path_helpers
+ end
# plus a singleton class method called _routes ...
included do
@@ -502,6 +451,12 @@ module ActionDispatch
# UrlFor (included in this module) add extra
# conveniences for working with @_routes.
define_method(:_routes) { @_routes || routes }
+
+ define_method(:_generate_paths_by_default) do
+ supports_path
+ end
+
+ private :_generate_paths_by_default
end
end
@@ -523,7 +478,7 @@ module ActionDispatch
path = conditions.delete :path_info
ast = conditions.delete :parsed_path_info
path = build_path(path, ast, requirements, anchor)
- conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
+ conditions = build_conditions(conditions, path.names.map(&:to_sym))
route = @set.add_route(app, path, conditions, defaults, name)
named_routes[name] = route if name
@@ -585,7 +540,7 @@ module ActionDispatch
if name == :controller
value
elsif value.is_a?(Array)
- value.map { |v| v.to_param }.join('/')
+ value.map(&:to_param).join('/')
elsif param = value.to_param
param
end
@@ -729,7 +684,7 @@ module ActionDispatch
end
def find_script_name(options)
- options.delete(:script_name) { '' }
+ options.delete(:script_name) || ''
end
def path_for(options, route_name = nil) # :nodoc:
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index eb554ec383..dca86858cc 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -184,6 +184,12 @@ module ActionDispatch
def _routes_context
self
end
+
+ private
+
+ def _generate_paths_by_default
+ true
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
deleted file mode 100644
index fb579b52fe..0000000000
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::DomAssertions has been extracted to the rails-dom-testing gem.") \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index e06f7037c6..28dc88d317 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -144,12 +144,6 @@ module ActionDispatch
old_controller, @controller = @controller, @controller.clone
_routes = @routes
- # Unfortunately, there is currently an abstraction leak between AC::Base
- # and AV::Base which requires having the URL helpers in both AC and AV.
- # To do this safely at runtime for tests, we need to bump up the helper serial
- # to that the old AV subclass isn't cached.
- #
- # TODO: Make this unnecessary
@controller.singleton_class.send(:include, _routes.url_helpers)
@controller.view_context_class = Class.new(@controller.view_context_class) do
include _routes.url_helpers
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
deleted file mode 100644
index 7361e6c44b..0000000000
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been extracted to the rails-dom-testing gem.")
diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
deleted file mode 100644
index da98b1d6ce..0000000000
--- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn('`ActionDispatch::Assertions::TagAssertions` has been extracted to the rails-dom-testing gem.')
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index a9a1576fed..f0e2c5becc 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -480,6 +480,84 @@ module ActionDispatch
# end
# end
# end
+ #
+ # Another longer example would be:
+ #
+ # A simple integration test that exercises multiple controllers:
+ #
+ # require 'test_helper'
+ #
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
+ # test "login and browse site" do
+ # # login via https
+ # https!
+ # get "/login"
+ # assert_response :success
+ #
+ # post_via_redirect "/login", username: users(:david).username, password: users(:david).password
+ # assert_equal '/welcome', path
+ # assert_equal 'Welcome david!', flash[:notice]
+ #
+ # https!(false)
+ # get "/articles/all"
+ # assert_response :success
+ # assert assigns(:articles)
+ # end
+ # end
+ #
+ # As you can see the integration test involves multiple controllers and
+ # exercises the entire stack from database to dispatcher. In addition you can
+ # have multiple session instances open simultaneously in a test and extend
+ # those instances with assertion methods to create a very powerful testing
+ # DSL (domain-specific language) just for your application.
+ #
+ # Here's an example of multiple sessions and custom DSL in an integration test
+ #
+ # require 'test_helper'
+ #
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
+ # test "login and browse site" do
+ # # User david logs in
+ # david = login(:david)
+ # # User guest logs in
+ # guest = login(:guest)
+ #
+ # # Both are now available in different sessions
+ # assert_equal 'Welcome david!', david.flash[:notice]
+ # assert_equal 'Welcome guest!', guest.flash[:notice]
+ #
+ # # User david can browse site
+ # david.browses_site
+ # # User guest can browse site as well
+ # guest.browses_site
+ #
+ # # Continue with other assertions
+ # end
+ #
+ # private
+ #
+ # module CustomDsl
+ # def browses_site
+ # get "/products/all"
+ # assert_response :success
+ # assert assigns(:products)
+ # end
+ # end
+ #
+ # def login(user)
+ # open_session do |sess|
+ # sess.extend(CustomDsl)
+ # u = users(user)
+ # sess.https!
+ # sess.post "/login", username: u.username, password: u.password
+ # assert_equal '/welcome', sess.path
+ # sess.https!(false)
+ # end
+ # end
+ # end
+ #
+ # Consult the Rails Testing Guide for more.
+
class IntegrationTest < ActiveSupport::TestCase
include Integration::Runner
include ActionController::TemplateAssertions
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index de3dc5f924..4b9a088265 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -60,7 +60,7 @@ module ActionDispatch
def accept=(mime_types)
@env.delete('action_dispatch.request.accepts')
- @env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",")
+ @env['HTTP_ACCEPT'] = Array(mime_types).collect(&:to_s).join(",")
end
alias :rack_cookies :cookies
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 77f656d6f1..f664dab620 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 9b3ea30f69..255ac9f4ed 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -5,10 +5,10 @@ module ActionPack
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 63c9ab04c9..2d69ceed3a 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -21,6 +21,7 @@ PROCESS_COUNT = (ENV['N'] || 4).to_i
require 'active_support/testing/autorun'
require 'abstract_controller'
+require 'abstract_controller/railties/routes_helpers'
require 'action_controller'
require 'action_view'
require 'action_view/testing/resolvers'
@@ -53,7 +54,7 @@ I18n.enforce_available_locales = false
# Register danish language for testing
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
-ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
+ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
@@ -260,7 +261,7 @@ end
module ActionController
class Base
# This stub emulates the Railtie including the URL helpers from a Rails application
- include SharedTestRoutes.url_helpers
+ extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes)
include SharedTestRoutes.mounted_helpers
self.view_paths = FIXTURE_LOAD_PATH
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index f2a4503f13..d0dfbfbc74 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -575,6 +575,13 @@ class AssertTemplateTest < ActionController::TestCase
end
end
+ def test_fails_expecting_not_known_layout
+ get :render_with_layout
+ assert_raise(ArgumentError) do
+ assert_template :layout => 1
+ end
+ end
+
def test_passes_with_correct_layout
get :render_with_layout
assert_template :layout => "layouts/standard"
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index b2b01b3fa9..2e08a6af9f 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -10,7 +10,7 @@ class ActionController::Base
def before_actions
filters = _process_action_callbacks.select { |c| c.kind == :before }
- filters.map! { |c| c.raw_filter }
+ filters.map!(&:raw_filter)
end
end
@@ -504,7 +504,6 @@ class FilterTest < ActionController::TestCase
def non_yielding_action
@filters << "it didn't yield"
- @filter_return_value
end
def action_three
@@ -528,32 +527,15 @@ class FilterTest < ActionController::TestCase
end
end
- def test_non_yielding_around_actions_not_returning_false_do_not_raise
+ def test_non_yielding_around_actions_do_not_raise
controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", true
assert_nothing_raised do
test_process(controller, "index")
end
end
- def test_non_yielding_around_actions_returning_false_do_not_raise
- controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", false
- assert_nothing_raised do
- test_process(controller, "index")
- end
- end
-
- def test_after_actions_are_not_run_if_around_action_returns_false
- controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", false
- test_process(controller, "index")
- assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
- end
-
def test_after_actions_are_not_run_if_around_action_does_not_yield
controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", true
test_process(controller, "index")
assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
end
@@ -1021,21 +1003,21 @@ class YieldingAroundFiltersTest < ActionController::TestCase
def test_first_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_1')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 1, controller.instance_variable_get(:@try)
end
def test_second_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_2')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 2, controller.instance_variable_get(:@try)
end
def test_last_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_3')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 3, controller.instance_variable_get(:@try)
end
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index 8c6c8a0aa7..a758df2ec6 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -162,17 +162,36 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
assert_equal(expected, actual)
end
+ test "token_and_options returns right token when token key is not specified in header" do
+ token = "rcHu+HzSFw89Ypyhn/896A="
+
+ actual = ActionController::HttpAuthentication::Token.token_and_options(
+ sample_request_without_token_key(token)
+ ).first
+
+ expected = token
+ assert_equal(expected, actual)
+ end
+
private
def sample_request(token, options = {nonce: "def"})
authorization = options.inject([%{Token token="#{token}"}]) do |arr, (k, v)|
arr << "#{k}=\"#{v}\""
end.join(", ")
- @sample_request ||= OpenStruct.new authorization: authorization
+ mock_authorization_request(authorization)
end
def malformed_request
- @malformed_request ||= OpenStruct.new authorization: %{Token token=}
+ mock_authorization_request(%{Token token=})
+ end
+
+ def sample_request_without_token_key(token)
+ mock_authorization_request(%{Token #{token}})
+ end
+
+ def mock_authorization_request(authorization)
+ OpenStruct.new(authorization: authorization)
end
def encode_credentials(token, options = {})
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index d6219b7626..5535c7ae78 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -850,3 +850,27 @@ class IntegrationWithRoutingTest < ActionDispatch::IntegrationTest
end
end
end
+
+# to work in contexts like rspec before(:all)
+class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest
+ self._setup_callbacks = []
+ self._teardown_callbacks = []
+
+ class FooController < ActionController::Base
+ def ok
+ cookies[:key] = 'ok'
+ render plain: 'ok'
+ end
+ end
+
+ def test_request
+ with_routing do |routes|
+ routes.draw { get ':action' => FooController }
+ get '/ok'
+
+ assert_response 200
+ assert_equal 'ok', response.body
+ assert_equal 'ok', cookies['key']
+ end
+ end
+end
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index 27871ef351..2be947c648 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -19,7 +19,7 @@ class LocalizedTemplatesTest < ActionController::TestCase
def test_localized_template_is_used
I18n.locale = :de
get :hello_world
- assert_equal "Gutten Tag", @response.body
+ assert_equal "Guten Tag", @response.body
end
def test_default_locale_template_is_used_when_locale_is_missing
@@ -34,7 +34,7 @@ class LocalizedTemplatesTest < ActionController::TestCase
I18n.fallbacks[:"de-AT"] = [:de]
get :hello_world
- assert_equal "Gutten Tag", @response.body
+ assert_equal "Guten Tag", @response.body
end
def test_localized_template_has_correct_header_with_no_format_in_template_name
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 49be7caf38..864c6ee130 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -73,6 +73,16 @@ module Another
def with_action_not_found
raise AbstractController::ActionNotFound
end
+
+ def append_info_to_payload(payload)
+ super
+ payload[:test_key] = "test_value"
+ @last_payload = payload
+ end
+
+ def last_payload
+ @last_payload
+ end
end
end
@@ -163,6 +173,16 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_match(/\(Views: [\d.]+ms\)/, logs[1])
end
+ def test_append_info_to_payload_is_called_even_with_exception
+ begin
+ get :with_exception
+ wait
+ rescue Exception
+ end
+
+ assert_equal "test_value", @controller.last_payload[:test_key]
+ end
+
def test_process_action_with_filter_parameters
@request.env["action_dispatch.parameter_filter"] = [:lifo, :amount]
diff --git a/actionpack/test/controller/mime/responders_test.rb b/actionpack/test/controller/mime/responders_test.rb
deleted file mode 100644
index 032b4c0ab1..0000000000
--- a/actionpack/test/controller/mime/responders_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'abstract_unit'
-require 'controller/fake_models'
-
-class ResponderTest < ActionController::TestCase
- def test_class_level_respond_to
- e = assert_raises(NoMethodError) do
- Class.new(ActionController::Base) do
- respond_to :json
- end
- end
-
- assert_includes e.message, '`responders` gem'
- assert_includes e.message, '~> 2.0'
- end
-
- def test_respond_with
- klass = Class.new(ActionController::Base) do
- def index
- respond_with Customer.new("david", 13)
- end
- end
-
- @controller = klass.new
-
- e = assert_raises(NoMethodError) do
- get :index
- end
-
- assert_includes e.message, '`responders` gem'
- assert_includes e.message, '~> 2.0'
- end
-end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index ba98ad7605..2ed486516d 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -280,4 +280,10 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal({ "controller" => "users", "action" => "create" }, params.to_h)
end
+
+ test "to_unsafe_h returns unfiltered params" do
+ assert @params.to_h.is_a? Hash
+ assert_not @params.to_h.is_a? ActionController::Parameters
+ assert_equal @params.to_hash, @params.to_unsafe_h
+ end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index b036b6c08e..929b161eb6 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -217,6 +217,15 @@ class TestController < ActionController::Base
head :forbidden, :x_custom_header => "something"
end
+ def head_with_no_content
+ # Fill in the headers with dummy data to make
+ # sure they get removed during the testing
+ response.headers["Content-Type"] = "dummy"
+ response.headers["Content-Length"] = 42
+
+ head 204
+ end
+
private
def set_variable_for_layout
@@ -545,6 +554,14 @@ class HeadRenderTest < ActionController::TestCase
end
end
+ def test_head_with_no_content
+ get :head_with_no_content
+
+ assert_equal 204, @response.status
+ assert_nil @response.headers["Content-Type"]
+ assert_nil @response.headers["Content-Length"]
+ end
+
def test_head_with_string_status
get :head_with_string_status, :status => "404 Eat Dirt"
assert_equal 404, @response.response_code
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index c18914cc8e..9caa5cbe57 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -884,13 +884,13 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw { get ':controller/(:action(/:id))' }
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_extra_keys
set.draw { get ':controller/:action/:id' }
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_generate_extras_not_first
@@ -900,7 +900,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_generate_not_first
@@ -918,7 +918,7 @@ class RouteSetTest < ActiveSupport::TestCase
get ':controller/:action/:id'
end
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_draw
@@ -1001,6 +1001,9 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "http://test.host/people?baz=bar#location",
controller.send(:index_url, :baz => "bar", :anchor => 'location')
+
+ assert_equal "http://test.host/people", controller.send(:index_url, anchor: nil)
+ assert_equal "http://test.host/people", controller.send(:index_url, anchor: false)
end
def test_named_route_url_method_with_port
@@ -1383,7 +1386,7 @@ class RouteSetTest < ActiveSupport::TestCase
url = controller.url_for({ :controller => "connection", :only_path => true })
assert_equal "/connection/connection", url
- url = controller.url_for({ :use_route => :family_connection,
+ url = controller.url_for({ :use_route => "family_connection",
:controller => "connection", :only_path => true })
assert_equal "/connection", url
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 475af90032..2e1f21c645 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/json/decoding'
+require 'rails/engine'
class TestCaseTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -520,18 +521,6 @@ XML
end
end
- def test_use_route
- with_routing do |set|
- set.draw do
- get 'via_unnamed_route', to: 'test_case_test/test#test_uri'
- get 'via_named_route', as: :a_named_route, to: 'test_case_test/test#test_uri'
- end
-
- get :test_uri, use_route: :a_named_route
- assert_equal '/via_named_route', @response.body
- end
- end
-
def test_assert_realistic_path_parameters
get :test_params, :id => 20, :foo => Object.new
@@ -753,6 +742,44 @@ XML
end
end
+module EngineControllerTests
+ class Engine < ::Rails::Engine
+ isolate_namespace EngineControllerTests
+
+ routes.draw do
+ get '/' => 'bar#index'
+ end
+ end
+
+ class BarController < ActionController::Base
+ def index
+ render :text => 'bar'
+ end
+ end
+
+ class BarControllerTest < ActionController::TestCase
+ tests BarController
+
+ def test_engine_controller_route
+ get :index
+ assert_equal @response.body, 'bar'
+ end
+ end
+
+ class BarControllerTestWithExplicitRouteSet < ActionController::TestCase
+ tests BarController
+
+ def setup
+ @routes = Engine.routes
+ end
+
+ def test_engine_controller_route
+ get :index
+ assert_equal @response.body, 'bar'
+ end
+ end
+end
+
class InferringClassNameTest < ActionController::TestCase
def test_determine_controller_class
assert_equal ContentController, determine_class("ContentControllerTest")
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index c05cde87e4..0ffa2d2a03 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -54,6 +54,20 @@ module AbstractController
)
end
+ def test_nil_anchor
+ assert_equal(
+ '/c/a',
+ W.new.url_for(only_path: true, controller: 'c', action: 'a', anchor: nil)
+ )
+ end
+
+ def test_false_anchor
+ assert_equal(
+ '/c/a',
+ W.new.url_for(only_path: true, controller: 'c', action: 'a', anchor: false)
+ )
+ end
+
def test_anchor_should_call_to_param
assert_equal('/c/a#anchor',
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
@@ -276,6 +290,13 @@ module AbstractController
end
end
+ def test_using_nil_script_name_properly_concats_with_original_script_name
+ add_host!
+ assert_equal('https://www.basecamphq.com/subdir/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => nil, :original_script_name => '/subdir')
+ )
+ end
+
def test_only_path
with_routing do |set|
set.draw do
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index d80b0e2da0..2b109ff19e 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -83,6 +83,16 @@ class WebServiceTest < ActionDispatch::IntegrationTest
end
end
+ def test_parsing_json_doesnot_rescue_exception
+ with_test_route_set do
+ with_params_parsers Mime::JSON => Proc.new { |data| raise Interrupt } do
+ assert_raises(Interrupt) do
+ post "/", '{"title":"JSON"}}', 'CONTENT_TYPE' => 'application/json'
+ end
+ end
+ end
+ end
+
private
def with_params_parsers(parsers = {})
old_session = @integration_session
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 7dc6c37522..6223a52a76 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -1,12 +1,5 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::PKCS5
-rescue LoadError, NameError
- $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/key_generator'
require 'active_support/message_verifier'
@@ -152,11 +145,21 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def set_cookie_with_domain_all_as_string
+ cookies[:user_name] = {:value => "rizwanreza", :domain => 'all'}
+ head :ok
+ end
+
def delete_cookie_with_domain
cookies.delete(:user_name, :domain => :all)
head :ok
end
+ def delete_cookie_with_domain_all_as_string
+ cookies.delete(:user_name, :domain => 'all')
+ head :ok
+ end
+
def set_cookie_with_domain_and_tld
cookies[:user_name] = {:value => "rizwanreza", :domain => :all, :tld_length => 2}
head :ok
@@ -494,7 +497,7 @@ class CookiesTest < ActionController::TestCase
assert_nil @response.cookies["user_id"]
end
- def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
+ def test_accessing_nonexistent_signed_cookie_should_not_raise_an_invalid_signature
get :set_signed_cookie
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
end
@@ -612,7 +615,7 @@ class CookiesTest < ActionController::TestCase
assert_nil @response.cookies["foo"]
end
- def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
+ def test_accessing_nonexistent_encrypted_cookie_should_not_raise_invalid_message
get :set_encrypted_cookie
assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
end
@@ -984,6 +987,13 @@ class CookiesTest < ActionController::TestCase
assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
end
+ def test_cookie_with_all_domain_option_using_a_non_standard_2_letter_tld
+ @request.host = "admin.lvh.me"
+ get :set_cookie_with_domain_and_tld
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.lvh.me; path=/"
+ end
+
def test_cookie_with_all_domain_option_using_host_with_port_and_tld_length
@request.host = "nextangle.local:3000"
get :set_cookie_with_domain_and_tld
@@ -1155,5 +1165,3 @@ class CookiesTest < ActionController::TestCase
end
end
end
-
-end
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index f8851f0152..1e5ed60b09 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -43,6 +43,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
raise ActionController::InvalidAuthenticityToken
when "/not_found_original_exception"
raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ when "/missing_template"
+ raise ActionView::MissingTemplate.new(%w(foo), 'foo/index', %w(foo), false, 'mailer')
when "/bad_request"
raise ActionController::BadRequest
when "/missing_keys"
@@ -120,6 +122,15 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_no_match '&lt;|&gt;', routing_table, "there should not be escaped html in the output"
end
+ test 'displays request and response info when a RoutingError occurs' do
+ @app = DevelopmentApp
+
+ get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+
+ assert_select 'h2', /Request/
+ assert_select 'h2', /Response/
+ end
+
test "rescue with diagnostics message" do
@app = DevelopmentApp
@@ -230,6 +241,29 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_match(/RuntimeError\n\s+in FeaturedTileController/, body)
end
+ test "show formatted params" do
+ @app = DevelopmentApp
+
+ params = {
+ 'id' => 'unknown',
+ 'someparam' => {
+ 'foo' => 'bar',
+ 'abc' => 'goo'
+ }
+ }
+
+ get("/runtime_error", {}, {
+ 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.request.parameters' => {
+ 'action' => 'show',
+ 'controller' => 'featured_tile'
+ }.merge(params)
+ })
+ assert_response 500
+
+ assert_includes(body, CGI.escapeHTML(PP.pp(params, "", 200)))
+ end
+
test "sets the HTTP charset parameter" do
@app = DevelopmentApp
@@ -275,6 +309,22 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
+ test 'display backtrace on template missing errors' do
+ @app = DevelopmentApp
+
+ get "/missing_template", nil, {}
+
+ assert_select "header h1", /Template is missing/
+
+ assert_select "#container h2", /^Missing template/
+
+ assert_select '#Application-Trace'
+ assert_select '#Framework-Trace'
+ assert_select '#Full-Trace'
+
+ assert_select 'h2', /Request/
+ end
+
test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do
@app = DevelopmentApp
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index ad6335f132..3017a9c2d6 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -83,7 +83,7 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse broken acceptlines" do
accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL]
- assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s }
+ assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
# Accept header send with user HTTP_USER_AGENT: Mozilla/4.0
@@ -91,7 +91,7 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse other broken acceptlines" do
accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*"
expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::ALL]
- assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s }
+ assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
test "custom type" do
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index c609075e6b..b765a13fa1 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -39,7 +39,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test "nils are stripped from collections" do
assert_parses(
- {"person" => nil},
+ {"person" => []},
"{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' }
)
assert_parses(
@@ -47,7 +47,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
"{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' }
)
assert_parses(
- {"person" => nil},
+ {"person" => []},
"{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' }
)
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index 4e99c26e03..50daafbb54 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -95,8 +95,8 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
assert_parses({"action" => nil}, "action")
assert_parses({"action" => {"foo" => nil}}, "action[foo]")
assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]")
- assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar][]")
- assert_parses({"action" => {"foo" => nil }}, "action[foo][]")
+ assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]")
+ assert_parses({"action" => {"foo" => [] }}, "action[foo][]")
assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]")
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 940ebc0224..ee8e915610 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -997,8 +997,8 @@ class RequestParameterFilter < BaseRequestTest
}
parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
- before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
- after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
+ before_filter['barg'] = {:bargain=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
+ after_filter['barg'] = {:bargain=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
assert_equal after_filter, parameter_filter.filter(before_filter)
end
@@ -1143,6 +1143,13 @@ class RequestVariant < BaseRequestTest
end
end
+ test "reset variant" do
+ request = stub_request
+
+ request.variant = nil
+ assert_equal nil, request.variant
+ end
+
test "setting variant with non symbol value" do
request = stub_request
assert_raise ArgumentError do
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 48342e252a..c61423dce4 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -231,9 +231,9 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal ['Not Found'], body.each.to_a
end
- test "[response].flatten does not recurse infinitely" do
+ test "[response.to_a].flatten does not recurse infinitely" do
Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely
- status, headers, body = assert_deprecated { [@response].flatten }
+ status, headers, body = [@response.to_a].flatten
assert_equal @response.status, status
assert_equal @response.headers, headers
assert_equal @response.body, body.each.to_a.join
@@ -251,20 +251,6 @@ class ResponseTest < ActiveSupport::TestCase
status, headers, body = Rack::ContentLength.new(app).call(env)
assert_equal '5', headers['Content-Length']
end
-
- test "implicit destructuring and Array conversion is deprecated" do
- response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
-
- assert_deprecated do
- status, headers, body = response
-
- assert_equal 404, status
- assert_equal({ 'Content-Type' => 'text/plain' }, headers)
- assert_equal ['Not Found'], body.each.to_a
- end
-
- assert_deprecated { response.to_ary }
- end
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index ff33dd5652..3d3d4b74ae 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -2,6 +2,11 @@ require 'abstract_unit'
require 'rails/engine'
require 'action_dispatch/routing/inspector'
+class MountedRackApp
+ def self.call(env)
+ end
+end
+
module ActionDispatch
module Routing
class RoutesInspectorTest < ActiveSupport::TestCase
@@ -204,19 +209,36 @@ module ActionDispatch
], output
end
- class RackApp
- def self.call(env)
+ def test_rake_routes_shows_route_with_rack_app
+ output = draw do
+ get 'foo/:id' => MountedRackApp, :id => /[A-Z]\d{5}/
end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " GET /foo/:id(.:format) MountedRackApp {:id=>/[A-Z]\\d{5}/}"
+ ], output
end
- def test_rake_routes_shows_route_with_rack_app
+ def test_rake_routes_shows_named_route_with_mounted_rack_app
output = draw do
- get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
+ mount MountedRackApp => '/foo'
end
assert_equal [
- "Prefix Verb URI Pattern Controller#Action",
- " GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"
+ " Prefix Verb URI Pattern Controller#Action",
+ "mounted_rack_app /foo MountedRackApp"
+ ], output
+ end
+
+ def test_rake_routes_shows_overridden_named_route_with_mounted_rack_app_with_name
+ output = draw do
+ mount MountedRackApp => '/foo', as: 'blog'
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " blog /foo MountedRackApp"
], output
end
@@ -229,21 +251,21 @@ module ActionDispatch
output = draw do
scope :constraint => constraint.new do
- mount RackApp => '/foo'
+ mount MountedRackApp => '/foo'
end
end
assert_equal [
- "Prefix Verb URI Pattern Controller#Action",
- " /foo #{RackApp.name} {:constraint=>( my custom constraint )}"
+ " Prefix Verb URI Pattern Controller#Action",
+ "mounted_rack_app /foo MountedRackApp {:constraint=>( my custom constraint )}"
], output
end
def test_rake_routes_dont_show_app_mounted_in_assets_prefix
output = draw do
- get '/sprockets' => RackApp
+ get '/sprockets' => MountedRackApp
end
- assert_no_match(/RackApp/, output.first)
+ assert_no_match(/MountedRackApp/, output.first)
assert_no_match(/\/sprockets/, output.first)
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index a7acc0de41..fe52c50336 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -105,50 +105,6 @@ module ActionDispatch
assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false)
end
- test "only_path: true with *_path" do
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_equal '/foo', url_helpers.foo_path(only_path: true)
- end
- end
-
- test "only_path: false with *_path with global :host option" do
- @set.default_url_options = { host: 'example.com' }
-
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false)
- end
- end
-
- test "only_path: false with *_path with local :host option" do
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false, host: 'example.com')
- end
- end
-
- test "only_path: false with *_path with no :host option" do
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_raises ArgumentError do
- assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false)
- end
- end
- end
-
test "explicit keys win over implicit keys" do
draw do
resources :foo do
@@ -160,6 +116,18 @@ module ActionDispatch
assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1)
end
+ test "having an optional scope with resources" do
+ draw do
+ scope "(/:foo)" do
+ resources :users
+ end
+ end
+
+ assert_equal '/users/1', url_helpers.user_path(1)
+ assert_equal '/users/1', url_helpers.user_path(1, foo: nil)
+ assert_equal '/a/users/1', url_helpers.user_path(1, foo: 'a')
+ end
+
private
def draw(&block)
@set.draw(&block)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index d0a624784a..450681c356 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3331,30 +3331,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'comments#index', @response.body
end
- def test_mix_symbol_to_controller_action
- assert_deprecated do
- draw do
- get '/projects', controller: 'project_files',
- action: 'index',
- to: :show
- end
- end
- get '/projects'
- assert_equal 'project_files#show', @response.body
- end
-
- def test_mix_string_to_controller_action_no_hash
- assert_deprecated do
- draw do
- get '/projects', controller: 'project_files',
- action: 'index',
- to: 'show'
- end
- end
- get '/projects'
- assert_equal 'show#index', @response.body
- end
-
def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes
draw do
scope shallow_path: 'projects', shallow_prefix: 'project' do
@@ -3629,15 +3605,13 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
assert_match(/Missing :controller/, ex.message)
end
- def test_missing_action
+ def test_missing_controller_with_to
ex = assert_raises(ArgumentError) {
- assert_deprecated do
- draw do
- get '/foo/bar', :to => 'foo'
- end
+ draw do
+ get '/foo/bar', :to => 'foo'
end
}
- assert_match(/Missing :action/, ex.message)
+ assert_match(/Missing :controller/, ex.message)
end
def test_missing_action_on_hash
@@ -4433,3 +4407,31 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
assert_equal message, error.message
end
end
+
+class TestDefaultUrlOptions < ActionDispatch::IntegrationTest
+ class PostsController < ActionController::Base
+ def archive
+ render :text => "posts#archive"
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ default_url_options locale: 'en'
+ scope ':locale', format: false do
+ get '/posts/:year/:month/:day', to: 'posts#archive', as: 'archived_posts'
+ end
+ end
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def test_positional_args_with_format_false
+ assert_equal '/en/posts/2014/12/13', archived_posts_path(2014, 12, 13)
+ end
+end
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index 65ad8677f3..cc35d4594e 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal "0.0.0.0", env.delete("REMOTE_ADDR")
assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT")
- assert_equal [1, 2], env.delete("rack.version")
+ assert_equal [1, 3], env.delete("rack.version")
assert_equal "", env.delete("rack.input").string
assert_kind_of StringIO, env.delete("rack.errors")
assert_equal true, env.delete("rack.multithread")
diff --git a/actionpack/test/fixtures/localized/hello_world.de.html b/actionpack/test/fixtures/localized/hello_world.de.html
index 4727d7a7e0..a8fc612c60 100644
--- a/actionpack/test/fixtures/localized/hello_world.de.html
+++ b/actionpack/test/fixtures/localized/hello_world.de.html
@@ -1 +1 @@
-Gutten Tag \ No newline at end of file
+Guten Tag \ No newline at end of file
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index 9dfdfc23ed..6939b426f6 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -16,6 +16,7 @@ module ActionDispatch
'/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?\Z},
'/:controller/*foo' => %r{\A/(#{x})/(.+)\Z},
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar\Z},
+ '/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))\Z},
}.each do |path, expected|
define_method(:"test_to_regexp_#{path}") do
strexp = Router::Strexp.build(
@@ -39,6 +40,7 @@ module ActionDispatch
'/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?},
'/:controller/*foo' => %r{\A/(#{x})/(.+)},
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar},
+ '/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))},
}.each do |path, expected|
define_method(:"test_to_non_anchored_regexp_#{path}") do
strexp = Router::Strexp.build(
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index fbac86e8ca..19c61b5914 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -415,7 +415,7 @@ module ActionDispatch
def test_generate_with_name
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, {}, "tasks"
path, params = @formatter.generate(
"tasks",
diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb
index 09ca7ff73b..0028aaa629 100644
--- a/actionpack/test/routing/helper_test.rb
+++ b/actionpack/test/routing/helper_test.rb
@@ -26,20 +26,6 @@ module ActionDispatch
x.new.pond_duck_path Duck.new
end
end
-
- def test_path_deprecation
- rs = ::ActionDispatch::Routing::RouteSet.new
- rs.draw do
- resources :ducks
- end
-
- x = Class.new {
- include rs.url_helpers(false)
- }
- assert_deprecated do
- assert_equal '/ducks', x.new.ducks_path
- end
- end
end
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index e388e6ecd3..ab3cb7eb19 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,199 +1,32 @@
-* Update `select_tag` to work correctly with `:include_blank` option passing a string.
+* Change the default template handler from `ERB` to `Raw`.
- Fixes #16483.
+ Files without a template handler in their extension will be rended using the raw
+ handler instead of ERB.
- *Frank Groeneveld*
+ *Rafael Mendonça França*
-* Changed the meaning of `render "foo/bar"`.
+* Remove deprecated `AbstractController::Base::parent_prefixes`.
- Previously, calling `render "foo/bar"` in a controller action is equivalent
- to `render file: "foo/bar"`. In Rails 4.2, this has been changed to mean
- `render template: "foo/bar"` instead. If you need to render a file, please
- change your code to use the explicit form (`render file: "foo/bar"`) instead.
+ *Rafael Mendonça França*
- *Jeremy Jackson*
+* Default translations that have a lower precedence than a html safe default,
+ but are not themselves safe, should not be marked as html_safe.
-* Add support for ARIA attributes in tags.
+ *Justin Coyne*
- Example:
+* Make possible to use blocks with short version of `render "partial"` helper.
- <%= f.text_field :name, aria: { required: "true", hidden: "false" } %>
+ *Nikolay Shebanov*
- now generates:
+* Add a `hidden_field` on the `file_field` to avoid raise a error when the only
+ input on the form is the `file_field`.
- <input aria-hidden="false" aria-required="true" id="user_name" name="user[name]" type="text">
+ *Mauro George*
- *Paola Garcia Casadiego*
+* Add an explicit error message, in `ActionView::PartialRenderer` for partial
+ `rendering`, when the value of option `as` has invalid characters.
-* Provide a `builder` object when using the `label` form helper in block form.
+ *Angelo Capilleri*
- The new `builder` object responds to `translation`, allowing I18n fallback support
- when you want to customize how a particular label is presented.
- *Alex Robbin*
-
-* Add I18n support for input/textarea placeholder text.
-
- Placeholder I18n follows the same convention as `label` I18n.
-
- *Alex Robbin*
-
-* Fix that render layout: 'messages/layout' should also be added to the dependency tracker tree.
-
- *DHH*
-
-* Add `PartialIteration` object used when rendering collections.
-
- The iteration object is available as the local variable
- `#{template_name}_iteration` when rendering partials with collections.
-
- It gives access to the `size` of the collection being iterated over,
- the current `index` and two convenience methods `first?` and `last?`.
-
- *Joel Junström*, *Lucas Uyezu*
-
-* Return an absolute instead of relative path from an asset url in the case
- of the `asset_host` proc returning nil.
-
- *Jolyon Pawlyn*
-
-* Fix `html_escape_once` to properly handle hex escape sequences (e.g. &#x1a2b;).
-
- *John F. Douthat*
-
-* Added String support for min and max properties for date field helpers.
-
- *Todd Bealmear*
-
-* The `highlight` helper now accepts a block to be used instead of the `highlighter`
- option.
-
- *Lucas Mazza*
-
-* The `except` and `highlight` helpers now accept regular expressions.
-
- *Jan Szumiec*
-
-* Flatten the array parameter in `safe_join`, so it behaves consistently with
- `Array#join`.
-
- *Paul Grayson*
-
-* Honor `html_safe` on array elements in tag values, as we do for plain string
- values.
-
- *Paul Grayson*
-
-* Add `ActionView::Template::Handler.unregister_template_handler`.
-
- It performs the opposite of `ActionView::Template::Handler.register_template_handler`.
-
- *Zuhao Wan*
-
-* Bring `cache_digest` rake tasks up-to-date with the latest API changes.
-
- *Jiri Pospisil*
-
-* Allow custom `:host` option to be passed to `asset_url` helper that
- overwrites `config.action_controller.asset_host` for particular asset.
-
- *Hubert Łępicki*
-
-* Deprecate `AbstractController::Base.parent_prefixes`.
- Override `AbstractController::Base.local_prefixes` when you want to change
- where to find views.
-
- *Nick Sutterer*
-
-* Take label values into account when doing I18n lookups for model attributes.
-
- The following:
-
- # form.html.erb
- <%= form_for @post do |f| %>
- <%= f.label :type, value: "long" %>
- <% end %>
-
- # en.yml
- en:
- activerecord:
- attributes:
- post/long: "Long-form Post"
-
- Used to simply return "long", but now it will return "Long-form
- Post".
-
- *Joshua Cody*
-
-* Change `asset_path` to use File.join to create proper paths:
-
- Before:
-
- https://some.host.com//assets/some.js
-
- After:
-
- https://some.host.com/assets/some.js
-
- *Peter Schröder*
-
-* Change `favicon_link_tag` default mimetype from `image/vnd.microsoft.icon` to
- `image/x-icon`.
-
- Before:
-
- # => favicon_link_tag 'myicon.ico'
- <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
-
- After:
-
- # => favicon_link_tag 'myicon.ico'
- <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
-
- *Geoffroy Lorieux*
-
-* Remove wrapping div with inline styles for hidden form fields.
-
- We are dropping HTML 4.01 and XHTML strict compliance since input tags directly
- inside a form are valid HTML5, and the absence of inline styles help in validating
- for Content Security Policy.
-
- *Joost Baaij*
-
-* `collection_check_boxes` respects `:index` option for the hidden field name.
-
- Fixes #14147.
-
- *Vasiliy Ermolovich*
-
-* `date_select` helper with option `with_css_classes: true` does not overwrite other classes.
-
- *Izumi Wong-Horiuchi*
-
-* `number_to_percentage` does not crash with `Float::NAN` or `Float::INFINITY`
- as input.
-
- Fixes #14405.
-
- *Yves Senn*
-
-* Add `include_hidden` option to `collection_check_boxes` helper.
-
- *Vasiliy Ermolovich*
-
-* Fixed a problem where the default options for the `button_tag` helper are not
- applied correctly.
-
- Fixes #14254.
-
- *Sergey Prikhodko*
-
-* Take variants into account when calculating template digests in ActionView::Digestor.
-
- The arguments to ActionView::Digestor#digest are now being passed as a hash
- to support variants and allow more flexibility in the future. The support for
- regular (required) arguments is deprecated and will be removed in Rails 5.0 or later.
-
- *Piotr Chmolowski, Łukasz Strzałkowski*
-
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionview/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 70efe7e8c2..8f9194cda7 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Rendering framework putting the V in MVC (part of Rails).'
s.description = 'Simple, battle-tested conventions and helpers for building web pages.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
s.add_dependency 'builder', '~> 3.1'
s.add_dependency 'erubis', '~> 2.7.0'
s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1'
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
s.add_development_dependency 'actionpack', version
s.add_development_dependency 'activemodel', version
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 6a1837c6e2..c3bbac27fd 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 3071a13655..1feafc1094 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -126,8 +126,8 @@ module ActionView #:nodoc:
# end
# end
#
- # For more information on Builder please consult the [source
- # code](https://github.com/jimweirich/builder).
+ # For more information on Builder please consult the {source
+ # code}[https://github.com/jimweirich/builder].
class Base
include Helpers, ::ERB::Util, Context
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 752eafee3c..4f45f5b8c8 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -5,10 +5,10 @@ module ActionView
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 75d1634b2e..5a3223968f 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -31,7 +31,8 @@ module ActionView
# <head><title><%= @greeting %></title></head>
# <body>
# <b><%= @greeting %></b>
- # </body></html>
+ # </body>
+ # </html>
#
def capture(*args)
value = nil
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 01a9747035..4b4f0ae577 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -177,7 +177,7 @@ 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>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if
+ # * <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.
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
@@ -898,7 +898,7 @@ module ActionView
def translated_date_order
date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
- date_order = date_order.map { |element| element.to_sym }
+ date_order = date_order.map(&:to_sym)
forbidden_elements = date_order - [:year, :month, :day]
if forbidden_elements.any?
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 03f80ff360..8d78ba13d5 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -164,6 +164,23 @@ module ActionView
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
# id attributes on form elements. The namespace attribute will be prefixed
# with underscore on the generated HTML id.
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
+ # either "get" or "post". If "patch", "put", "delete", or another verb
+ # is used, a hidden input with name <tt>_method</tt> is added to
+ # simulate the verb over post.
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
+ # Use only if you need to pass custom authenticity token string, or to
+ # not add authenticity_token field at all (by passing <tt>false</tt>).
+ # Remote forms may omit the embedded authenticity token by setting
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
+ # This is helpful when you're fragment-caching the form. Remote forms
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
+ # unnecessary unless you support browsers without JavaScript.
+ # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
+ # JavaScript drivers to control the submit behavior. By default this
+ # behavior is an ajax submit.
+ # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
+ # utf8 is not output.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
@@ -420,6 +437,7 @@ module ActionView
html_options[:data] = options.delete(:data) if options.has_key?(:data)
html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
html_options[:method] = options.delete(:method) if options.has_key?(:method)
+ html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
html_options[:authenticity_token] = options.delete(:authenticity_token)
builder = instantiate_builder(object_name, object, options)
@@ -836,6 +854,24 @@ module ActionView
#
# file_field(:attachment, :file, class: 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
+ #
+ # ==== Gotcha
+ #
+ # The HTML specification says that when a file field is empty, web browsers
+ # do not send any value to the server. Unfortunately this introduces a
+ # gotcha: if a +User+ model has an +avatar+ field, and no file is selected,
+ # then the +avatar+ parameter is empty. Thus, any mass-assignment idiom like
+ #
+ # @user.update(params[:user])
+ #
+ # wouldn't update the +avatar+ field.
+ #
+ # To prevent this, the helper generates an auxiliary hidden field before
+ # every file field. The hidden field has the same name as the file one and
+ # a blank value.
+ #
+ # In case you don't want the helper to generate this hidden field you can
+ # specify the <tt>include_hidden: false</tt> option.
def file_field(object_name, method, options = {})
Tags::FileField.new(object_name, method, self, options).render
end
@@ -1917,6 +1953,8 @@ module ActionView
end
ActiveSupport.on_load(:action_view) do
- cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
+ cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do
+ ::ActionView::Helpers::FormBuilder
+ end
end
end
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 83b07a00d4..bbfbf482a4 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -351,12 +351,12 @@ module ActionView
return container if String === container
selected, disabled = extract_selected_and_disabled(selected).map do |r|
- Array(r).map { |item| item.to_s }
+ Array(r).map(&:to_s)
end
container.map do |element|
html_attributes = option_html_attributes(element)
- text, value = option_text_and_value(element).map { |item| item.to_s }
+ text, value = option_text_and_value(element).map(&:to_s)
html_attributes[:selected] ||= option_value_selected?(value, selected)
html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index c0218fd55d..93c04fbec6 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -140,7 +140,9 @@ module ActionView
include_blank = ''
end
- option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags)
+ if include_blank
+ option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags)
+ end
end
if prompt = options.delete(:prompt)
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index e11670e00d..827932d8e2 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -32,7 +32,7 @@ module ActionView
view_renderer.render(self, options)
end
else
- view_renderer.render_partial(self, :partial => options, :locals => locals)
+ view_renderer.render_partial(self, :partial => options, :locals => locals, &block)
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 7cb55cc214..e72e85ee5f 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/object/try'
-require 'active_support/deprecation'
require 'rails-html-sanitizer'
module ActionView
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index b2038576a2..a87c223a71 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -18,7 +18,7 @@ module ActionView
itemscope allowfullscreen default inert sortable
truespeed typemustmatch).to_set
- BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb
index 476b820d84..e6a1d9c62d 100644
--- a/actionview/lib/action_view/helpers/tags/file_field.rb
+++ b/actionview/lib/action_view/helpers/tags/file_field.rb
@@ -2,6 +2,21 @@ module ActionView
module Helpers
module Tags # :nodoc:
class FileField < TextField # :nodoc:
+
+ def render
+ options = @options.stringify_keys
+
+ if options.fetch("include_hidden", true)
+ add_default_name_and_id(options)
+ options[:type] = "file"
+ tag("input", name: options["name"], type: "hidden", value: "") + tag("input", options)
+ else
+ options.delete("include_hidden")
+ @options = options
+
+ super
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb
index c09e2f1be7..a848aeabfa 100644
--- a/actionview/lib/action_view/helpers/tags/search_field.rb
+++ b/actionview/lib/action_view/helpers/tags/search_field.rb
@@ -16,6 +16,7 @@ module ActionView
options["incremental"] = true unless options.has_key?("incremental")
end
+ @options = options
super
end
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index a9f1631586..2c40ed1832 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -309,7 +309,7 @@ module ActionView
# <table>
# <% @items.each do |item| %>
# <tr class="<%= cycle("odd", "even") -%>">
- # <td>item</td>
+ # <td><%= item %></td>
# </tr>
# <% end %>
# </table>
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index c691aa0acb..342361217c 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -37,7 +37,8 @@ module ActionView
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
options = options.dup
- options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
+ remaining_defaults = Array(options.delete(:default))
+ options[:default] = remaining_defaults.shift if remaining_defaults.first.kind_of? String
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
# Otherwise, tell I18n to raise an exception, which we rescue further in this method.
@@ -64,10 +65,14 @@ module ActionView
I18n.translate(scope_key_by_partial(key), options)
end
rescue I18n::MissingTranslationData => e
- raise e if raise_error
+ if remaining_defaults.present?
+ translate remaining_defaults.shift, options.merge(default: remaining_defaults)
+ else
+ raise e if raise_error
- keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
- content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
+ keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
+ content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
+ end
end
alias :t :translate
@@ -96,21 +101,6 @@ module ActionView
def html_safe_translation_key?(key)
key.to_s =~ /(\b|_|\.)html$/
end
-
- def wrap_translate_defaults(defaults)
- new_defaults = []
- defaults = Array(defaults)
- while key = defaults.shift
- if key.is_a?(Symbol)
- new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
- break
- else
- new_defaults << key
- end
- end
-
- new_defaults
- 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 364414da05..8c2d5705f1 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -428,6 +428,7 @@ module ActionView
# * <tt>:body</tt> - Preset the body of the email.
# * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
+ # * <tt>:reply_to</tt> - Preset the Reply-To field of the email.
#
# ==== Obfuscation
# Prior to Rails 4.0, +mail_to+ provided options for encoding the address
@@ -457,9 +458,9 @@ module ActionView
html_options, name = name, nil if block_given?
html_options = (html_options || {}).stringify_keys
- extras = %w{ cc bcc body subject }.map! { |item|
+ extras = %w{ cc bcc body subject reply_to }.map! { |item|
option = html_options.delete(item) || next
- "#{item}=#{Rack::Utils.escape_path(option)}"
+ "#{item.dasherize}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + extras.join('&')
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index 9ee05bd816..0b5c0b9991 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -262,7 +262,7 @@ module ActionView
def layout(layout, conditions = {})
include LayoutConditions unless conditions.empty?
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
+ conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) }
self._layout_conditions = conditions
self._layout = layout
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index ea687d9cca..36855ec3d0 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -191,7 +191,6 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = {}, nil
- @skip_default_locale = false
@cache = true
@prefixes = prefixes
@rendered_format = nil
@@ -213,12 +212,6 @@ module ActionView
super(values)
end
- # Do not use the default locale on template lookup.
- def skip_default_locale!
- @skip_default_locale = true
- self.locale = nil
- end
-
# Override locale to return a symbol instead of array.
def locale
@details[:locale].first
@@ -233,7 +226,7 @@ module ActionView
config.locale = value
end
- super(@skip_default_locale ? I18n.locale : default_locale)
+ super(default_locale)
end
# Uses the first format in the formats array for layout lookup.
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index 63f645431a..c8484bed34 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -2,29 +2,54 @@ require 'active_support/core_ext/module'
require 'action_view/model_naming'
module ActionView
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
- # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
- # a higher logical level.
+ # RecordIdentifier encapsulates methods used by various ActionView helpers
+ # to associate records with DOM elements.
#
- # # routes
- # resources :posts
+ # Consider for example the following code that displays the body of a post:
#
- # # view
- # <%= div_for(post) do %> <div id="post_45" class="post">
- # <%= post.body %> What a wonderful world!
- # <% end %> </div>
+ # <%= div_for(post) do %>
+ # <%= post.body %>
+ # <% end %>
#
- # # controller
- # def update
- # post = Post.find(params[:id])
- # post.update(params[:post])
+ # When +post+ is a new, unsaved ActiveRecord::Base intance, the resulting HTML
+ # is:
#
- # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
- # end
+ # <div id="new_post" class="post">
+ # </div>
+ #
+ # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
+ # is:
+ #
+ # <div id="post_42" class="post">
+ # What a wonderful world!
+ # </div>
+ #
+ # In both cases, the +id+ and +class+ of the wrapping DOM element are
+ # automatically generated, following naming conventions encapsulated by the
+ # RecordIdentifier methods #dom_id and #dom_class:
+ #
+ # dom_id(Post.new) # => "new_post"
+ # dom_class(Post.new) # => "post"
+ # dom_id(Post.find 42) # => "post_42"
+ # dom_class(Post.find 42) # => "post"
#
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
- # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
- # same naming convention and allows you to write less code if you follow it.
+ # Note that these methods do not strictly require +Post+ to be a subclass of
+ # ActiveRecord::Base.
+ # Any +Post+ class will work as long as its instances respond to +to_key+
+ # and +model_name+, given that +model_name+ responds to +param_key+.
+ # For instance:
+ #
+ # class Post
+ # attr_accessor :to_key
+ #
+ # def model_name
+ # OpenStruct.new param_key: 'post'
+ # end
+ #
+ # def self.find(id)
+ # new.tap { |post| post.to_key = [id] }
+ # end
+ # end
module RecordIdentifier
extend self
extend ModelNaming
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 0407632435..6c3015180a 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -73,7 +73,7 @@ module ActionView
#
# <%= render partial: "account", locals: { user: @buyer } %>
#
- # == Rendering a collection of partials
+ # == \Rendering a collection of partials
#
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
# render a sub template for each of the elements. This pattern has been implemented as a single method that
@@ -105,7 +105,7 @@ module ActionView
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
# just keep domain objects, like Active Records, in there.
#
- # == Rendering shared partials
+ # == \Rendering shared partials
#
# Two controllers can share a set of partials and render them like this:
#
@@ -113,7 +113,7 @@ module ActionView
#
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
- # == Rendering objects that respond to `to_partial_path`
+ # == \Rendering objects that respond to `to_partial_path`
#
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
# and pick the proper path by checking `to_partial_path` method.
@@ -127,7 +127,7 @@ module ActionView
# # <%= render partial: "posts/post", collection: @posts %>
# <%= render partial: @posts %>
#
- # == Rendering the default case
+ # == \Rendering the default case
#
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
# defaults of render to render partials. Examples:
@@ -147,7 +147,7 @@ module ActionView
# # <%= render partial: "posts/post", collection: @posts %>
# <%= render @posts %>
#
- # == Rendering partials with layouts
+ # == \Rendering partials with layouts
#
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
@@ -366,10 +366,12 @@ module ActionView
partial = options[:partial]
if String === partial
+ @has_object = options.key?(:object)
@object = options[:object]
@collection = collection_from_options
@path = partial
else
+ @has_object = true
@object = partial
@collection = collection_from_object || collection_from_options
@@ -382,7 +384,7 @@ module ActionView
end
if as = options[:as]
- raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
+ raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
as = as.to_sym
end
@@ -506,7 +508,7 @@ module ActionView
def retrieve_template_keys
keys = @locals.keys
- keys << @variable if @object || @collection
+ keys << @variable if @has_object || @collection
if @collection
keys << @variable_counter
keys << @variable_iteration
@@ -528,11 +530,19 @@ module ActionView
end
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
+ "and is followed by any combination of letters, numbers and underscores."
+
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
"and is followed by any combination of letters, numbers and underscores."
def raise_invalid_identifier(path)
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
end
+
+ def raise_invalid_option_as(as)
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
+ end
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 5cbdfdf6c0..abd3b77c67 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -35,13 +35,13 @@ module ActionView
module ClassMethods
def view_context_class
@view_context_class ||= begin
- include_path_helpers = supports_path?
+ supports_path = supports_path?
routes = respond_to?(:_routes) && _routes
helpers = respond_to?(:_helpers) && _helpers
Class.new(ActionView::Base) do
if routes
- include routes.url_helpers(include_path_helpers)
+ include routes.url_helpers(supports_path)
include routes.mounted_helpers
end
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 75febb8652..f281333a41 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -80,21 +80,38 @@ module ActionView
when String
options
when nil
- super({:only_path => true})
+ super(only_path: _generate_paths_by_default)
when Hash
options = options.symbolize_keys
- options[:only_path] = options[:host].nil? unless options.key?(:only_path)
+ unless options.key?(:only_path)
+ if options[:host].nil?
+ options[:only_path] = _generate_paths_by_default
+ else
+ options[:only_path] = false
+ end
+ end
+
super(options)
when :back
_back_url
- when Symbol
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_string_call self, options
when Array
- polymorphic_path(options, options.extract_options!)
- when Class
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_class_call self, options
+ if _generate_paths_by_default
+ polymorphic_path(options, options.extract_options!)
+ else
+ polymorphic_url(options, options.extract_options!)
+ end
else
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call self, options
+ method = _generate_paths_by_default ? :path : :url
+ builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
+
+ case options
+ when Symbol
+ builder.handle_string_call(self, options)
+ when Class
+ builder.handle_class_call(self, options)
+ else
+ builder.handle_model_call(self, options)
+ end
end
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index 743ef6de0a..390bce98a2 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -75,7 +75,7 @@ module ActionView
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
- @sub_templates.collect { |template| template.inspect }.join(", ")
+ @sub_templates.collect(&:inspect).join(", ")
else
""
end
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index 33bfcb458c..0105e88a49 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -7,9 +7,9 @@ module ActionView #:nodoc:
autoload :Raw, 'action_view/template/handlers/raw'
def self.extended(base)
- base.register_default_template_handler :erb, ERB.new
+ base.register_default_template_handler :raw, Raw.new
+ base.register_template_handler :erb, ERB.new
base.register_template_handler :builder, Builder.new
- base.register_template_handler :raw, Raw.new
base.register_template_handler :ruby, :source.to_proc
end
@@ -22,7 +22,7 @@ module ActionView #:nodoc:
# Register an object that knows how to handle template files with the given
# extensions. This can be used to implement new template types.
- # The handler must respond to `:call`, which will be passed the template
+ # The handler must respond to +:call+, which will be passed the template
# and should return the rendered template as a String.
def register_template_handler(*extensions, handler)
raise(ArgumentError, "Extension is required") if extensions.empty?
@@ -42,7 +42,7 @@ module ActionView #:nodoc:
end
def template_handler_extensions
- @@template_handlers.keys.map {|key| key.to_s }.sort
+ @@template_handlers.keys.map(&:to_s).sort
end
def registered_template_handler(extension)
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 3c2224fbf5..85a100ed4c 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -35,7 +35,7 @@ module ActionView
end
end
- BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
flush_newline_if_pending(src)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index b65507f4a2..bc0db330ea 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -1,7 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
-require 'active_support/core_ext/string/filters'
require "action_view/template"
require "thread"
require "thread_safe"
@@ -139,7 +138,7 @@ module ActionView
# resolver is fresher before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
- locals = locals.map { |x| x.to_s }.sort!
+ locals = locals.map(&:to_s).sort!
if key
@cache.cache(key, name, prefix, partial, locals) do
@@ -197,24 +196,12 @@ module ActionView
}
end
- if RUBY_VERSION >= '2.2.0'
- def find_template_paths(query)
- Dir[query].reject { |filename|
- File.directory?(filename) ||
- # deals with case-insensitive file systems.
- !File.fnmatch(query, filename, File::FNM_EXTGLOB)
- }
- end
- else
- def find_template_paths(query)
- # deals with case-insensitive file systems.
- sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
-
- Dir[query].reject { |filename|
- File.directory?(filename) ||
- !sanitizer[File.dirname(filename)].include?(filename)
- }
- end
+ def find_template_paths(query)
+ Dir[query].reject { |filename|
+ File.directory?(filename) ||
+ # deals with case-insensitive file systems.
+ !File.fnmatch(query, filename, File::FNM_EXTGLOB)
+ }
end
# Helper for building query glob string based on resolver's pattern.
@@ -251,12 +238,6 @@ module ActionView
pieces.shift
extension = pieces.pop
- unless extension
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The file #{path} did not specify a template handler. The default is
- currently ERB, but will change to RAW in the future.
- MSG
- end
handler = Template.handler_for_extension(extension)
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index b84e0281ae..be45fcf742 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -9,7 +9,7 @@ module ActionView
self.types = Set.new
def self.register(*t)
- types.merge(t.map { |type| type.to_s })
+ types.merge(t.map(&:to_s))
end
register :html, :text, :js, :css, :xml, :json
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 2e203a7590..492f67f45d 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -16,14 +16,9 @@ module ActionView
module ClassMethods
def _prefixes # :nodoc:
@_prefixes ||= begin
- deprecated_prefixes = handle_deprecated_parent_prefixes
- if deprecated_prefixes
- deprecated_prefixes
- else
- return local_prefixes if superclass.abstract?
-
- local_prefixes + superclass._prefixes
- end
+ return local_prefixes if superclass.abstract?
+
+ local_prefixes + superclass._prefixes
end
end
@@ -34,17 +29,6 @@ module ActionView
def local_prefixes
[controller_path]
end
-
- def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0.
- return unless respond_to?(:parent_prefixes)
-
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Overriding `ActionController::Base::parent_prefixes` is deprecated,
- override `.local_prefixes` instead.
- MSG
-
- local_prefixes + parent_prefixes
- end
end
# The prefixes used in render "foo" shortcuts.
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 7096d588b6..4aa56f60f7 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -46,7 +46,7 @@ I18n.enforce_available_locales = false
# Register danish language for testing
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
-ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
+ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb
index e653b12d32..490932fef0 100644
--- a/actionview/test/actionpack/abstract/abstract_controller_test.rb
+++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb
@@ -168,7 +168,7 @@ module AbstractController
end
end
- class OverridingLocalPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3.
+ class OverridingLocalPrefixesTest < ActiveSupport::TestCase
test "overriding .local_prefixes adds prefix" do
@controller = OverridingLocalPrefixes.new
@controller.process(:index)
@@ -182,22 +182,6 @@ module AbstractController
end
end
- class DeprecatedParentPrefixes < OverridingLocalPrefixes
- def self.parent_prefixes
- ["abstract_controller/testing/me3"]
- end
- end
-
- class DeprecatedParentPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3.
- test "overriding .parent_prefixes is deprecated" do
- @controller = DeprecatedParentPrefixes.new
- assert_deprecated do
- @controller.process(:index)
- end
- assert_equal "Hello from me3/index.erb", @controller.response_body
- end
- end
-
# Test rendering with layouts
# ====
# self._layout is used when defined
diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index c6e7a523b9..7fba9ff8ff 100644
--- a/actionview/test/actionpack/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
@@ -39,7 +39,7 @@ class ViewLoadPathsTest < ActionController::TestCase
def assert_paths(*paths)
controller = paths.first.is_a?(Class) ? paths.shift : @controller
- assert_equal expand(paths), controller.view_paths.map { |p| p.to_s }
+ assert_equal expand(paths), controller.view_paths.map(&:to_s)
end
def test_template_load_path_was_set_correctly
diff --git a/actionview/test/template/debug_helper_test.rb b/actionview/test/activerecord/debug_helper_test.rb
index 5609694cd5..5609694cd5 100644
--- a/actionview/test/template/debug_helper_test.rb
+++ b/actionview/test/activerecord/debug_helper_test.rb
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 5842b775bb..8e1ed2776d 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -25,15 +25,17 @@ class Series < ActiveRecord::Base
self.table_name = 'projects'
end
-class ModelDelegator < ActiveRecord::Base
- self.table_name = 'projects'
-
+class ModelDelegator
def to_model
ModelDelegate.new
end
end
class ModelDelegate
+ def persisted?
+ true
+ end
+
def model_name
ActiveModel::Name.new(self.class)
end
@@ -605,13 +607,18 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- def test_routing_a_to_model_delegate
+ def test_routing_to_a_model_delegate
with_test_routes do
- @delegator.save
assert_url "http://example.com/model_delegates/overridden", @delegator
end
end
+ def test_nested_routing_to_a_model_delegate
+ with_test_routes do
+ assert_url "http://example.com/foo/model_delegates/overridden", [:foo, @delegator]
+ end
+ end
+
def with_namespaced_routes(name)
with_routing do |set|
set.draw do
@@ -645,6 +652,9 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
resources :series
resources :model_delegates
+ namespace :foo do
+ resources :model_delegates
+ end
end
extend @routes.url_helpers
diff --git a/actionview/test/fixtures/blog_public/.gitignore b/actionview/test/fixtures/blog_public/.gitignore
deleted file mode 100644
index 312e635ee6..0000000000
--- a/actionview/test/fixtures/blog_public/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-absolute/*
diff --git a/actionview/test/fixtures/blog_public/blog.html b/actionview/test/fixtures/blog_public/blog.html
deleted file mode 100644
index 79ad44c010..0000000000
--- a/actionview/test/fixtures/blog_public/blog.html
+++ /dev/null
@@ -1 +0,0 @@
-/blog/blog.html \ No newline at end of file
diff --git a/actionview/test/fixtures/blog_public/index.html b/actionview/test/fixtures/blog_public/index.html
deleted file mode 100644
index 2de3825481..0000000000
--- a/actionview/test/fixtures/blog_public/index.html
+++ /dev/null
@@ -1 +0,0 @@
-/blog/index.html \ No newline at end of file
diff --git a/actionview/test/fixtures/blog_public/subdir/index.html b/actionview/test/fixtures/blog_public/subdir/index.html
deleted file mode 100644
index 517bded335..0000000000
--- a/actionview/test/fixtures/blog_public/subdir/index.html
+++ /dev/null
@@ -1 +0,0 @@
-/blog/subdir/index.html \ No newline at end of file
diff --git a/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb b/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
deleted file mode 100644
index 3125583a28..0000000000
--- a/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<body>
-<%= cache 'nodigest', skip_digest: true do %><p>ERB</p><% end %>
-</body>
diff --git a/actionview/test/fixtures/happy_path/render_action/hello_world.erb b/actionview/test/fixtures/happy_path/render_action/hello_world.erb
deleted file mode 100644
index 6769dd60bd..0000000000
--- a/actionview/test/fixtures/happy_path/render_action/hello_world.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello world! \ No newline at end of file
diff --git a/actionview/test/fixtures/scope/test/modgreet.erb b/actionview/test/fixtures/scope/test/modgreet.erb
deleted file mode 100644
index 8947726e89..0000000000
--- a/actionview/test/fixtures/scope/test/modgreet.erb
+++ /dev/null
@@ -1 +0,0 @@
-<p>Beautiful modules!</p> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/_label_with_block.erb b/actionview/test/fixtures/test/_label_with_block.erb
index 40117e594e..94089ea93d 100644
--- a/actionview/test/fixtures/test/_label_with_block.erb
+++ b/actionview/test/fixtures/test/_label_with_block.erb
@@ -1,4 +1,4 @@
-<%= label 'post', 'message' do %>
+<%= label('post', 'message')do %>
Message
<%= text_field 'post', 'message' %>
<% end %>
diff --git a/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb b/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb
new file mode 100644
index 0000000000..352128f3ba
--- /dev/null
+++ b/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb
@@ -0,0 +1,3 @@
+<%= render "test/layout_for_block_with_args" do |arg_1, arg_2| %>
+ Yielded: <%= arg_1 %>/<%= arg_2 %>
+<% end %>
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index a463a08bb6..789b1d198b 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -111,19 +111,6 @@ class CommentRelevance
end
end
-class Sheep
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- def to_key; id ? [id] : nil end
- def save; @id = 1 end
- def new_record?; @id.nil? end
- def name
- @id.nil? ? 'new sheep' : "sheep ##{@id}"
- end
-end
-
class TagRelevance
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -183,3 +170,15 @@ end
class Car < Struct.new(:color)
end
+
+class Plane
+ attr_reader :to_key
+
+ def model_name
+ OpenStruct.new param_key: 'airplane'
+ end
+
+ def save
+ @to_key = [1]
+ end
+end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 0cdb130710..bfb073680e 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -1504,7 +1504,7 @@ class DateHelperTest < ActionView::TestCase
expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
- assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true, :include_seconds => true,
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true,
:prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
end
@@ -2330,7 +2330,7 @@ class DateHelperTest < ActionView::TestCase
# The love zone is UTC+0
mytz = Class.new(ActiveSupport::TimeZone) {
attr_accessor :now
- }.create('tenderlove', 0)
+ }.create('tenderlove', 0, ActiveSupport::TimeZone.find_tzinfo('UTC'))
now = Time.mktime(2004, 6, 15, 16, 35, 0)
mytz.now = now
diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb
index 3bb84cbc50..3e72be31de 100644
--- a/actionview/test/template/erb_util_test.rb
+++ b/actionview/test/template/erb_util_test.rb
@@ -84,7 +84,7 @@ class ErbUtilTest < ActiveSupport::TestCase
end
def test_rest_in_ascii
- (0..127).to_a.map {|int| int.chr }.each do |chr|
+ (0..127).to_a.map(&:chr).each do |chr|
next if %('"&<>).include?(chr)
assert_equal chr, html_escape(chr)
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 4bbbdf4fb1..fff1e1e572 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -472,18 +472,33 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, text_field(object_name, "title")
end
- def test_file_field_has_no_size
+ def test_file_field_does_generate_a_hidden_field
+ expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar")
+ end
+
+ def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false
expected = '<input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar", include_hidden: false)
+ end
+
+ def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false_with_key_as_string
+ expected = '<input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar", "include_hidden" => false)
+ end
+
+ def test_file_field_has_no_size
+ expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />'
assert_dom_equal expected, file_field("user", "avatar")
end
def test_file_field_with_multiple_behavior
- expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
+ expected = '<input name="import[file][]" type="hidden" value="" /><input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
assert_dom_equal expected, file_field("import", "file", :multiple => true)
end
def test_file_field_with_multiple_behavior_and_explicit_name
- expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />'
+ expected = '<input name="custom" type="hidden" value="" /><input id="import_file" multiple="multiple" name="custom" type="file" />'
assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom")
end
@@ -928,6 +943,11 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, search_field("contact", "notes_query"))
end
+ def test_search_field_with_onsearch_value
+ expected = %{<input onsearch="true" type="search" name="contact[notes_query]" id="contact_notes_query" incremental="true" />}
+ assert_dom_equal(expected, search_field("contact", "notes_query", onsearch: true))
+ end
+
def test_telephone_field
expected = %{<input id="user_cell" name="user[cell]" type="tel" />}
assert_dom_equal(expected, telephone_field("user", "cell"))
@@ -1714,7 +1734,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch", multipart: true) do
- "<input name='post[file]' type='file' id='post_file' />"
+ "<input name='post[file]' type='hidden' value='' /><input name='post[file]' type='file' id='post_file' />"
end
assert_dom_equal expected, output_buffer
@@ -1730,7 +1750,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch", multipart: true) do
- "<input name='post[comment][file]' type='file' id='post_comment_file' />"
+ "<input name='post[comment][file]' type='hidden' value='' /><input name='post[comment][file]' type='file' id='post_comment_file' />"
end
assert_dom_equal expected, output_buffer
@@ -1864,6 +1884,30 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_enforce_utf8_true
+ form_for(:post, enforce_utf8: true) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", nil, nil, enforce_utf8: true) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_enforce_utf8_false
+ form_for(:post, enforce_utf8: false) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", nil, nil, enforce_utf8: false) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_remote_in_html
form_for(@post, url: '/', html: { remote: true, id: 'create-post', method: :patch }) do |f|
concat f.text_field(:title)
@@ -3313,8 +3357,14 @@ class FormHelperTest < ActionView::TestCase
protected
- def hidden_fields(method = nil)
- txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ def hidden_fields(options = {})
+ method = options[:method]
+
+ if options.fetch(:enforce_utf8, true)
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ else
+ txt = ''
+ end
if method && !%w(get post).include?(method.to_s)
txt << %{<input name="_method" type="hidden" value="#{method}" />}
@@ -3338,7 +3388,7 @@ class FormHelperTest < ActionView::TestCase
method, remote, multipart = options.values_at(:method, :remote, :multipart)
- form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
+ form_text(action, id, html_class, remote, multipart, method) + hidden_fields(options.slice :method, :enforce_utf8) + contents + "</form>"
end
def protect_against_forgery?
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index f8fd642809..84a581b107 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -232,6 +232,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_select_tag_with_include_blank_false
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: false
+ expected = %(<select id="places" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
+ assert_dom_equal expected, actual
+ end
+
def test_select_tag_with_include_blank_string
actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: 'Choose'
expected = %(<select id="places" name="places"><option value="">Choose</option><option>Home</option><option>Work</option><option>Pub</option></select>)
diff --git a/actionview/test/template/record_identifier_test.rb b/actionview/test/template/record_identifier_test.rb
index 22038110a5..04898c0b0e 100644
--- a/actionview/test/template/record_identifier_test.rb
+++ b/actionview/test/template/record_identifier_test.rb
@@ -9,7 +9,6 @@ class RecordIdentifierTest < ActiveSupport::TestCase
@record = @klass.new
@singular = 'comment'
@plural = 'comments'
- @uncountable = Sheep
end
def test_dom_id_with_new_record
@@ -47,3 +46,46 @@ class RecordIdentifierTest < ActiveSupport::TestCase
assert_equal @singular, ActionView::RecordIdentifier.dom_class(@record)
end
end
+
+class RecordIdentifierWithoutActiveModelTest < ActiveSupport::TestCase
+ include ActionView::RecordIdentifier
+
+ def setup
+ @record = Plane.new
+ end
+
+ def test_dom_id_with_new_record
+ assert_equal "new_airplane", dom_id(@record)
+ end
+
+ def test_dom_id_with_new_record_and_prefix
+ assert_equal "custom_prefix_airplane", dom_id(@record, :custom_prefix)
+ end
+
+ def test_dom_id_with_saved_record
+ @record.save
+ assert_equal "airplane_1", dom_id(@record)
+ end
+
+ def test_dom_id_with_prefix
+ @record.save
+ assert_equal "edit_airplane_1", dom_id(@record, :edit)
+ end
+
+ def test_dom_class
+ assert_equal 'airplane', dom_class(@record)
+ end
+
+ def test_dom_class_with_prefix
+ assert_equal "custom_prefix_airplane", dom_class(@record, :custom_prefix)
+ end
+
+ def test_dom_id_as_singleton_method
+ @record.save
+ assert_equal "airplane_1", ActionView::RecordIdentifier.dom_id(@record)
+ end
+
+ def test_dom_class_as_singleton_method
+ assert_equal 'airplane', ActionView::RecordIdentifier.dom_class(@record)
+ end
+end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 85817119ba..dc4abca048 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -16,7 +16,7 @@ module RenderTestCases
I18n.backend.store_translations 'pt-BR', {}
# Ensure original are still the same since we are reindexing view paths
- assert_equal ORIGINAL_LOCALES, I18n.available_locales.map {|l| l.to_s }.sort
+ assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort
end
def test_render_without_options
@@ -175,14 +175,14 @@ module RenderTestCases
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 a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
"and is followed by any combination of letters, numbers and underscores.", e.message
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 a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
"and is followed by any combination of letters, numbers and underscores.", e.message
end
@@ -194,7 +194,21 @@ module RenderTestCases
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 a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_invalid_option_as
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/partial_only", :as => 'a-in') }
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_hyphen_and_invalid_option_as
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in", :as => 'a-in') }
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
"and is followed by any combination of letters, numbers and underscores.", e.message
end
@@ -466,6 +480,11 @@ module RenderTestCases
@view.render(:partial => 'test/partial_with_partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'})
end
+ def test_render_partial_shortcut_with_block_content
+ assert_equal %(Before (shortcut test)\nBefore\n\n Yielded: arg1/arg2\n\nAfter\nAfter),
+ @view.render(partial: "test/partial_shortcut_with_block_content", layout: "test/layout_for_partial", locals: { name: "shortcut test" })
+ end
+
def test_render_layout_with_a_nested_render_layout_call
assert_equal %(Before (Foo!)\nBefore (Bar!)\npartial html\nAfter\npartial with layout\n\nAfter),
@view.render(:partial => 'test/partial_with_layout', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'})
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index ce89d5728e..d037447567 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -50,6 +50,11 @@ class TagHelperTest < ActionView::TestCase
assert_dom_equal "<div>Hello world!</div>", buffer
end
+ def test_content_tag_with_block_in_erb_containing_non_displayed_erb
+ buffer = render_erb("<%= content_tag(:p) do %><% 1 %><% end %>")
+ assert_dom_equal "<p></p>", buffer
+ end
+
def test_content_tag_with_block_and_options_in_erb
buffer = render_erb("<%= content_tag(:div, :class => 'green') do %>Hello world!<% end %>")
assert_dom_equal %(<div class="green">Hello world!</div>), buffer
@@ -64,6 +69,11 @@ class TagHelperTest < ActionView::TestCase
content_tag("a", "href" => "create") { "Create" }
end
+ def test_content_tag_with_block_and_non_string_outside_out_of_erb
+ assert_equal content_tag("p"),
+ content_tag("p") { 3.times { "do_something" } }
+ end
+
def test_content_tag_nested_in_content_tag_out_of_erb
assert_equal content_tag("p", content_tag("b", "Hello")),
content_tag("p") { content_tag("b", "Hello") },
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 0eccfed8f6..7a3c0a0715 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -164,6 +164,12 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translation.html_safe?
end
+ def test_translate_with_last_default_not_named_html
+ translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.foo'])
+ assert_equal 'Foo', translation
+ assert_equal false, translation.html_safe?
+ end
+
def test_translate_with_string_default
translation = translate(:'translations.missing', default: 'A Generic String')
assert_equal 'A Generic String', translation
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index e0678ae1f7..0d6f31af9b 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -493,8 +493,8 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_with_options
assert_dom_equal(
- %{<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">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.")
+ %{<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")
)
end
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index b04883413e..afdd42be33 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1 +1,27 @@
-* Started project. \ No newline at end of file
+* `ActiveJob::Base.deserialize` delegates to the job class
+
+
+ Since `ActiveJob::Base#deserialize` can be overridden by subclasses (like
+ `ActiveJob::Base#serialize`) this allows jobs to attach arbitrary metadata
+ when they get serialized and read it back when they get performed. Example:
+
+ class DeliverWebhookJob < ActiveJob::Base
+ def serialize
+ super.merge('attempt_number' => (@attempt_number || 0) + 1)
+ end
+
+ def deserialize(job_data)
+ super
+ @attempt_number = job_data['attempt_number']
+ end
+
+ rescue_from(TimeoutError) do |exception|
+ raise exception if @attempt_number > 5
+ retry_job(wait: 10)
+ end
+ end
+
+ *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/MIT-LICENSE b/activejob/MIT-LICENSE
index 8b1e97b776..0cef8cdda0 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014 David Heinemeier Hansson
+Copyright (c) 2014-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 7e66860b36..1922f256ec 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,7 +1,7 @@
require 'rake/testtask'
require 'rubygems/package_task'
-ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner)
+ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test)
ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION)
task default: :test
@@ -20,7 +20,7 @@ namespace :test do
desc 'Run integration tests for all adapters'
task :integration do
- run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:integration:#{a}" }
+ run_without_aborting (ACTIVEJOB_ADAPTERS - ['test']).map { |a| "test:integration:#{a}" }
end
task 'env:integration' do
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index a9be2a8f00..5404ece804 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Job framework with pluggable queues.'
s.description = 'Declare job classes that can be run by a variety of queueing backends.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 1b582f5877..3d4f63b261 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2014 David Heinemeier Hansson
+# Copyright (c) 2014-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index e2c076eb3f..752be6898e 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/indifferent_access'
+
module ActiveJob
# Raised when an exception is raised during job arguments deserialization.
#
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index c4ceb484cc..2b6149e84e 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -3,8 +3,8 @@ require 'active_support/callbacks'
module ActiveJob
# = Active Job Callbacks
#
- # Active Job provides hooks during the lifecycle of a job. Callbacks allow you
- # to trigger logic during the lifecycle of a job. Available callbacks are:
+ # Active Job provides hooks during the life cycle of a job. Callbacks allow you
+ # to trigger logic during the life cycle of a job. Available callbacks are:
#
# * <tt>before_enqueue</tt>
# * <tt>around_enqueue</tt>
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index a0e55a0028..ddd7d1361c 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -22,10 +22,8 @@ module ActiveJob
module ClassMethods
# Creates a new job instance from a hash created with +serialize+
def deserialize(job_data)
- job = job_data['job_class'].constantize.new
- job.job_id = job_data['job_id']
- job.queue_name = job_data['queue_name']
- job.serialized_arguments = job_data['arguments']
+ job = job_data['job_class'].constantize.new
+ job.deserialize(job_data)
job
end
@@ -69,6 +67,32 @@ module ActiveJob
}
end
+ # Attaches the stored job data to the current instance. Receives a hash
+ # returned from +serialize+
+ #
+ # ==== Examples
+ #
+ # class DeliverWebhookJob < ActiveJob::Base
+ # def serialize
+ # super.merge('attempt_number' => (@attempt_number || 0) + 1)
+ # end
+ #
+ # def deserialize(job_data)
+ # super
+ # @attempt_number = job_data['attempt_number']
+ # end
+ #
+ # rescue_from(TimeoutError) do |exception|
+ # raise exception if @attempt_number > 5
+ # retry_job(wait: 10)
+ # end
+ # end
+ def deserialize(job_data)
+ self.job_id = job_data['job_id']
+ self.queue_name = job_data['queue_name']
+ self.serialized_arguments = job_data['arguments']
+ end
+
private
def deserialize_arguments_if_needed
if defined?(@serialized_arguments) && @serialized_arguments.present?
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index ac364f77d1..27a5de93f4 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -5,10 +5,10 @@ module ActiveJob
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
index 21d2fda3ff..cd29e6908e 100644
--- a/activejob/lib/active_job/logging.rb
+++ b/activejob/lib/active_job/logging.rb
@@ -85,7 +85,12 @@ module ActiveJob
end
def args_info(job)
- job.arguments.any? ? " with arguments: #{job.arguments.map(&:inspect).join(", ")}" : ""
+ if job.arguments.any?
+ ' with arguments: ' +
+ job.arguments.map { |arg| arg.try(:to_global_id).try(:to_s) || arg.inspect }.join(', ')
+ else
+ ''
+ end
end
def scheduled_at(event)
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 85d7c44bb8..d610d30e01 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -2,7 +2,7 @@ require 'active_job/queue_adapters/inline_adapter'
require 'active_support/core_ext/string/inflections'
module ActiveJob
- # The <tt>ActionJob::QueueAdapter</tt> module is used to load the
+ # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
# correct adapter. The default queue adapter is the :inline queue.
module QueueAdapter #:nodoc:
extend ActiveSupport::Concern
@@ -21,8 +21,8 @@ module ActiveJob
ActiveJob::QueueAdapters::TestAdapter.new
when Symbol, String
load_adapter(name_or_adapter)
- when Class
- name_or_adapter
+ else
+ name_or_adapter if name_or_adapter.respond_to?(:enqueue)
end
end
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
index 08e26b7418..e25d88e723 100644
--- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -15,7 +15,7 @@ module ActiveJob
end
def enqueue_at(*) #:nodoc:
- raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/v4.2.0/active_job_basics.html")
+ raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html")
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 e4fdf60008..ea9df9a063 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -14,6 +14,11 @@ module ActiveJob
attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs)
attr_writer(:enqueued_jobs, :performed_jobs)
+ def initialize
+ self.perform_enqueued_jobs = false
+ self.perform_enqueued_at_jobs = false
+ end
+
# Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
def enqueued_jobs
@enqueued_jobs ||= []
@@ -26,19 +31,19 @@ module ActiveJob
def enqueue(job) #:nodoc:
if perform_enqueued_jobs
- performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name}
- job.perform_now
+ 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.arguments, queue: job.queue_name}
+ enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name}
end
end
def enqueue_at(job, timestamp) #:nodoc:
if perform_enqueued_at_jobs
- performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp}
- job.perform_now
+ 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.arguments, queue: job.queue_name, at: timestamp}
+ enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp}
end
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index bb20d93947..2efcea7f2e 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/keys'
+
module ActiveJob
# Provides helper methods for testing Active Job
module TestHelper
@@ -77,13 +79,21 @@ module ActiveJob
end
# Asserts that the number of performed jobs matches the given number.
+ # If no block is passed, <tt>perform_enqueued_jobs</tt>
+ # must be called around the job call.
#
# def test_jobs
# assert_performed_jobs 0
- # HelloJob.perform_later('xavier')
+ #
+ # perform_enqueued_jobs do
+ # HelloJob.perform_later('xavier')
+ # end
# assert_performed_jobs 1
- # HelloJob.perform_later('yves')
- # assert_performed_jobs 2
+ #
+ # perform_enqueued_jobs do
+ # HelloJob.perform_later('yves')
+ # assert_performed_jobs 2
+ # end
# end
#
# If a block is passed, that block should cause the specified number of
@@ -102,7 +112,7 @@ module ActiveJob
def assert_performed_jobs(number)
if block_given?
original_count = performed_jobs.size
- yield
+ perform_enqueued_jobs { yield }
new_count = performed_jobs.size
assert_equal original_count + number, new_count,
"#{number} jobs expected, but #{new_count - original_count} were performed"
@@ -116,8 +126,11 @@ module ActiveJob
#
# def test_jobs
# assert_no_performed_jobs
- # HelloJob.perform_later('matthew')
- # assert_performed_jobs 1
+ #
+ # perform_enqueued_jobs do
+ # HelloJob.perform_later('matthew')
+ # assert_performed_jobs 1
+ # end
# end
#
# If a block is passed, that block should not cause any job to be performed.
@@ -166,7 +179,7 @@ module ActiveJob
original_performed_jobs = performed_jobs.dup
clear_performed_jobs
args.assert_valid_keys(:job, :args, :at, :queue)
- yield
+ perform_enqueued_jobs { yield }
matching_job = performed_jobs.any? do |job|
args.all? { |key, value| value == job[key] }
end
@@ -175,6 +188,17 @@ module ActiveJob
queue_adapter.performed_jobs = original_performed_jobs + performed_jobs
end
+ def perform_enqueued_jobs
+ @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
+ yield
+ ensure
+ queue_adapter.perform_enqueued_jobs = @old_perform_enqueued_jobs
+ queue_adapter.perform_enqueued_at_jobs = @old_perform_enqueued_at_jobs
+ end
+
def queue_adapter
ActiveJob::Base.queue_adapter
end
diff --git a/activejob/test/adapters/test.rb b/activejob/test/adapters/test.rb
new file mode 100644
index 0000000000..7180b38a57
--- /dev/null
+++ b/activejob/test/adapters/test.rb
@@ -0,0 +1,3 @@
+ActiveJob::Base.queue_adapter = :test
+ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
+ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
diff --git a/activejob/test/cases/adapter_test.rb b/activejob/test/cases/adapter_test.rb
index 4fc235ae40..6570c55a83 100644
--- a/activejob/test/cases/adapter_test.rb
+++ b/activejob/test/cases/adapter_test.rb
@@ -2,7 +2,6 @@ require 'helper'
class AdapterTest < ActiveSupport::TestCase
test "should load #{ENV['AJADAPTER']} adapter" do
- ActiveJob::Base.queue_adapter = ENV['AJADAPTER'].to_sym
- assert_equal ActiveJob::Base.queue_adapter, "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify.constantize
+ assert_equal "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify, ActiveJob::Base.queue_adapter.name
end
end
diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb
index 3d4e561117..64aae00441 100644
--- a/activejob/test/cases/logging_test.rb
+++ b/activejob/test/cases/logging_test.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/numeric/time'
require 'jobs/hello_job'
require 'jobs/logging_job'
require 'jobs/nested_job'
+require 'models/person'
class AdapterTest < ActiveSupport::TestCase
include ActiveSupport::LogSubscriber::TestHelper
@@ -65,6 +66,14 @@ class AdapterTest < ActiveSupport::TestCase
LoggingJob.queue_name = original_queue_name
end
+ def test_globalid_parameter_logging
+ person = Person.new(123)
+ LoggingJob.perform_later person
+ assert_match(%r{Enqueued.*gid://aj/Person/123}, @logger.messages)
+ assert_match(%r{Dummy, here is it: #<Person:.*>}, @logger.messages)
+ assert_match(%r{Performing.*gid://aj/Person/123}, @logger.messages)
+ end
+
def test_enqueue_job_logging
HelloJob.perform_later "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*?\) to .*?:.*Cristian/, @logger.messages)
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 71c505a65f..784ede3674 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -6,8 +6,6 @@ require 'jobs/logging_job'
require 'jobs/nested_job'
class EnqueuedJobsTest < ActiveJob::TestCase
- setup { queue_adapter.perform_enqueued_at_jobs = true }
-
def test_assert_enqueued_jobs
assert_nothing_raised do
assert_enqueued_jobs 1 do
@@ -44,11 +42,16 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_no_enqueued_jobs_with_no_block
+ assert_nothing_raised do
+ assert_no_enqueued_jobs
+ end
+ end
+
def test_assert_no_enqueued_jobs
assert_nothing_raised do
assert_no_enqueued_jobs do
- # Scheduled jobs are being performed in this context
- HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey')
+ HelloJob.perform_now
end
end
end
@@ -86,7 +89,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_job
assert_enqueued_with(job: LoggingJob, queue: 'default') do
- NestedJob.set(wait_until: Date.tomorrow.noon).perform_later
+ LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later
end
end
@@ -116,8 +119,6 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
class PerformedJobsTest < ActiveJob::TestCase
- setup { queue_adapter.perform_enqueued_jobs = true }
-
def test_assert_performed_jobs
assert_nothing_raised do
assert_performed_jobs 1 do
@@ -143,22 +144,31 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_assert_performed_jobs_with_no_block
assert_nothing_raised do
- HelloJob.perform_later('rafael')
+ perform_enqueued_jobs do
+ HelloJob.perform_later('rafael')
+ end
assert_performed_jobs 1
end
assert_nothing_raised do
- HelloJob.perform_later('aaron')
- HelloJob.perform_later('matthew')
- assert_performed_jobs 3
+ perform_enqueued_jobs do
+ HelloJob.perform_later('aaron')
+ HelloJob.perform_later('matthew')
+ assert_performed_jobs 3
+ end
+ end
+ end
+
+ def test_assert_no_performed_jobs_with_no_block
+ assert_nothing_raised do
+ assert_no_performed_jobs
end
end
def test_assert_no_performed_jobs
assert_nothing_raised do
assert_no_performed_jobs do
- # Scheduled jobs are being enqueued in this context
- HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey')
+ # empty block won't perform jobs
end
end
end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index ec0c8a8ede..db5265d7b2 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -7,17 +7,6 @@ GlobalID.app = 'aj'
@adapter = ENV['AJADAPTER'] || 'inline'
-def sidekiq?
- @adapter == 'sidekiq'
-end
-
-def ruby_193?
- RUBY_VERSION == '1.9.3' && RUBY_ENGINE != 'java'
-end
-
-# Sidekiq doesn't work with MRI 1.9.3
-exit if sidekiq? && ruby_193?
-
if ENV['AJ_INTEGRATION_TESTS']
require 'support/integration/helper'
else
diff --git a/activejob/test/support/delayed_job/delayed/backend/test.rb b/activejob/test/support/delayed_job/delayed/backend/test.rb
index b50ed36fc2..f80ec3a5a6 100644
--- a/activejob/test/support/delayed_job/delayed/backend/test.rb
+++ b/activejob/test/support/delayed_job/delayed/backend/test.rb
@@ -43,9 +43,7 @@ module Delayed
end
def self.create(attrs = {})
- new(attrs).tap do |o|
- o.save
- end
+ new(attrs).tap(&:save)
end
def self.create!(*args); create(*args); end
diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb
index 82acb17b2b..912f4bc387 100644
--- a/activejob/test/support/integration/adapters/resque.rb
+++ b/activejob/test/support/integration/adapters/resque.rb
@@ -1,7 +1,7 @@
module ResqueJobsManager
def setup
ActiveJob::Base.queue_adapter = :resque
- Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "tcp://127.0.0.1:6379/12", :thread_safe => true)
+ Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "redis://127.0.0.1:6379/12", :thread_safe => true)
Resque.logger = Rails.logger
unless can_run?
puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n"
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index 3cc9a34993..6ff18fb56a 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -48,7 +48,8 @@ module SidekiqJobsManager
def can_run?
begin
- Sidekiq.redis { |conn| conn.connect }
+ Sidekiq.redis(&:info)
+ Sidekiq.logger = nil
rescue
return false
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 5588699d9b..f86b4804c8 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,61 +1,17 @@
-* Passwords with spaces only allowed in `ActiveModel::SecurePassword`.
-
- Presence validation can be used to restore old behavior.
-
- *Yevhene Shemet*
-
-* Validate options passed to `ActiveModel::Validations.validate`.
-
- Preventing, in many cases, the simple mistake of using `validate` instead of `validates`.
-
- *Sonny Michaud*
-
-* Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
-
- These methods may cause confusion with the `reset_changes`, which has
- different behaviour.
-
- *Rafael Mendonça França*
-
-* Deprecate `ActiveModel::Dirty#reset_changes` in favor of `#clear_changes_information`.
-
- Method's name is causing confusion with the `reset_#{attribute}` methods.
- While `reset_name` sets the value of the name attribute to previous value
- `reset_changes` only discards the changes.
+* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and
+ `ActiveModel::Dirty#reset_changes`.
*Rafael Mendonça França*
-* Added `restore_attributes` method to `ActiveModel::Dirty` API which restores
- the value of changed attributes to previous value.
-
- *Igor G.*
-
-* Allow proc and symbol as values for `only_integer` of `NumericalityValidator`
-
- *Robin Mehner*
-
-* `has_secure_password` now verifies that the given password is less than 72
- characters if validations are enabled.
-
- Fixes #14591.
-
- *Akshay Vishnoi*
-
-* Remove deprecated `Validator#setup` without replacement.
-
- See #10716.
-
- *Kuldeep Aggarwal*
-
-* Add plural and singular form for length validator's default messages.
-
- *Abd ar-Rahman Hamid*
-
-* Introduce `validate` as an alias for `valid?`.
+* Change the way in which callback chains can be halted.
- This is more intuitive when you want to run validations but don't care about
- the return value.
+ The preferred method to halt a callback chain from now on is to explicitly
+ `throw(:abort)`.
+ In the past, returning `false` in an ActiveModel or ActiveModel::Validations
+ `before_` callback had the side effect of halting the callback chain.
+ This is not recommended anymore and, depending on the value of the
+ `config.active_support.halt_callback_chains_on_return_false` option, will
+ either not work at all or display a deprecation warning.
- *Henrik Nyh*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 36e565f692..3c6eb56296 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'A toolkit for building modeling frameworks (part of Rails).'
s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index feb3d9371d..46d60db756 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index ea07c5c039..96be551264 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -353,14 +353,12 @@ module ActiveModel
@attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4)
end
- def attribute_method_matcher(method_name) #:nodoc:
+ def attribute_method_matchers_matching(method_name) #:nodoc:
attribute_method_matchers_cache.compute_if_absent(method_name) do
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
# will match every time.
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
- match = nil
- matchers.detect { |method| match = method.match(method_name) }
- match
+ matchers.map { |method| method.match(method_name) }.compact
end
end
@@ -469,8 +467,8 @@ module ActiveModel
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
def match_attribute_method?(method_name)
- match = self.class.send(:attribute_method_matcher, method_name)
- match if match && attribute_method?(match.attr_name)
+ matches = self.class.send(:attribute_method_matchers_matching, method_name)
+ matches.detect { |match| attribute_method?(match.attr_name) }
end
def missing_attribute(attr_name, stack)
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index b3d70dc515..6214802074 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -6,7 +6,7 @@ module ActiveModel
# Provides an interface for any class to have Active Record like callbacks.
#
# Like the Active Record methods, the callback chain is aborted as soon as
- # one of the methods in the chain returns +false+.
+ # one of the methods throws +:abort+.
#
# First, extend ActiveModel::Callbacks from the class you are creating:
#
@@ -103,7 +103,6 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
- terminator: ->(_,result) { result == false },
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 9c9b6f4a77..9de6ea65be 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -22,7 +22,7 @@ module ActiveModel
module Conversion
extend ActiveSupport::Concern
- # If your object is already designed to implement all of the Active Model
+ # If your object is already designed to implement all of the \Active \Model
# you can use the default <tt>:to_model</tt> implementation, which simply
# returns +self+.
#
@@ -33,9 +33,9 @@ module ActiveModel
# person = Person.new
# person.to_model == person # => true
#
- # If your model does not act like an Active Model object, then you should
+ # If your model does not act like an \Active \Model object, then you should
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
- # your object with Active Model compliant methods.
+ # your object with \Active \Model compliant methods.
def to_model
self
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 4e389c8692..afba9bab0d 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,6 +1,5 @@
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
-require 'active_support/core_ext/string/filters'
module ActiveModel
# == Active \Model \Dirty
@@ -13,7 +12,7 @@ module ActiveModel
# * <tt>include ActiveModel::Dirty</tt> in your object.
# * Call <tt>define_attribute_methods</tt> passing each method you want to
# track.
- # * Call <tt>attr_name_will_change!</tt> before each change to the tracked
+ # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
# attribute.
# * Call <tt>changes_applied</tt> after the changes are persisted.
# * Call <tt>clear_changes_information</tt> when you want to reset the changes
@@ -102,7 +101,7 @@ module ActiveModel
#
# If an attribute is modified in-place then make use of
# +[attribute_name]_will_change!+ to mark that the attribute is changing.
- # Otherwise Active Model can't track changes to in-place attributes. Note
+ # Otherwise \Active \Model can't track changes to in-place attributes. Note
# that Active Record can detect in-place modifications automatically. You do
# not need to call +[attribute_name]_will_change!+ on Active Record models.
#
@@ -116,7 +115,6 @@ module ActiveModel
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
- attribute_method_affix prefix: 'reset_', suffix: '!'
attribute_method_affix prefix: 'restore_', suffix: '!'
end
@@ -170,7 +168,7 @@ module ActiveModel
# Handle <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, options = {}) #:nodoc:
- result = changed_attributes.include?(attr)
+ result = changes_include?(attr)
result &&= options[:to] == __send__(attr) if options.key?(:to)
result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
result
@@ -188,6 +186,10 @@ module ActiveModel
private
+ def changes_include?(attr_name)
+ attributes_changed_by_setter.include?(attr_name)
+ end
+
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied # :doc:
@previously_changed = changes
@@ -200,15 +202,6 @@ module ActiveModel
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- def reset_changes
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#reset_changes` is deprecated and will be removed on Rails 5.
- Please use `#clear_changes_information` instead.
- MSG
-
- clear_changes_information
- end
-
# Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
@@ -227,16 +220,6 @@ module ActiveModel
set_attribute_was(attr, value)
end
- # Handle <tt>reset_*!</tt> for +method_missing+.
- def reset_attribute!(attr)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#reset_#{attr}!` is deprecated and will be removed on Rails 5.
- Please use `#restore_#{attr}!` instead.
- MSG
-
- restore_attribute!(attr)
- end
-
# Handle <tt>restore_*!</tt> for +method_missing+.
def restore_attribute!(attr)
if attribute_changed?(attr)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 9105ef5dd6..55687cb3c7 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -283,9 +283,9 @@ module ActiveModel
# ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
- # person.errors.add(:name, nil, strict: true)
+ # person.errors.add(:name, :invalid, strict: true)
# # => ActiveModel::StrictValidationFailed: name is invalid
- # person.errors.add(:name, nil, strict: NameIsInvalid)
+ # person.errors.add(:name, :invalid, strict: NameIsInvalid)
# # => NameIsInvalid: name is invalid
#
# person.errors.messages # => {}
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index 932fe3e5a9..762f4fe939 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -1,14 +1,14 @@
module ActiveModel
- # Returns the version of the currently loaded Active Model as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
def self.gem_version
Gem::Version.new VERSION::STRING
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 4e6b02c246..1819d2403f 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -211,7 +211,7 @@ module ActiveModel
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
- # is required to pass the Active Model Lint test. So either extending the
+ # is required to pass the \Active \Model Lint test. So either extending the
# provided method below, or rolling your own is required.
module Naming
def self.extended(base) #:nodoc:
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 8f2a069ba3..96e88f1b6c 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -99,7 +99,7 @@ module ActiveModel
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password)
- BCrypt::Password.new(password_digest) == unencrypted_password && self
+ BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
end
attr_reader :password
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 77f2a64b11..b66dbf1afe 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -130,10 +130,10 @@ module ActiveModel
#
# json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
# person = Person.new
- # person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
- # person.name # => "bob"
- # person.age # => 22
- # person.awesome # => true
+ # person.from_json(json, true) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
+ # person.name # => "bob"
+ # person.age # => 22
+ # person.awesome # => true
def from_json(json, include_root=include_root_in_json)
hash = ActiveSupport::JSON.decode(json)
hash = hash.values.first if include_root
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 3ad3bf30ad..e33c766627 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -6,7 +6,7 @@ require 'active_support/core_ext/time/acts_like'
module ActiveModel
module Serializers
- # == Active Model XML Serializer
+ # == \Active \Model XML Serializer
module Xml
extend ActiveSupport::Concern
include ActiveModel::Serialization
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index c1e344b215..6a2668b8f7 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -87,7 +87,7 @@ module ActiveModel
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
- VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless].freeze
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb
index 9b5416fb1d..75bf655578 100644
--- a/activemodel/lib/active_model/validations/absence.rb
+++ b/activemodel/lib/active_model/validations/absence.rb
@@ -1,6 +1,6 @@
module ActiveModel
module Validations
- # == Active Model Absence Validator
+ # == \Active \Model Absence Validator
class AbsenceValidator < EachValidator #:nodoc:
def validate_each(record, attr_name, value)
record.errors.add(attr_name, :present, options) if value.present?
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 25ccabd66b..4b58ef66e3 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -15,15 +15,14 @@ module ActiveModel
# after_validation :do_stuff_after_validation
# end
#
- # Like other <tt>before_*</tt> callbacks if +before_validation+ returns
- # +false+ then <tt>valid?</tt> will not be called.
+ # Like other <tt>before_*</tt> callbacks if +before_validation+ throws
+ # +:abort+ then <tt>valid?</tt> will not be called.
module Callbacks
extend ActiveSupport::Concern
included do
include ActiveSupport::Callbacks
define_callbacks :validation,
- terminator: ->(_,result) { result == false },
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name]
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 13d6a966c0..4ba4e3e8f7 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -23,6 +23,10 @@ module ActiveModel
raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
raw_value ||= value
+ if record_attribute_changed_in_place?(record, attr_name)
+ raw_value = value
+ end
+
return if options[:allow_nil] && raw_value.nil?
unless value = parse_raw_value_as_a_number(raw_value)
@@ -86,6 +90,13 @@ module ActiveModel
options[:only_integer]
end
end
+
+ private
+
+ def record_attribute_changed_in_place?(record, attr_name)
+ record.respond_to?(:attribute_changed_in_place?) &&
+ record.attribute_changed_in_place?(attr_name.to_s)
+ end
end
module HelperMethods
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 0116de68ab..ac32750946 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -127,7 +127,7 @@ module ActiveModel
# in the options hash invoking the <tt>validate_each</tt> method passing in the
# record, attribute and value.
#
- # All Active Model validations are built on top of this validator.
+ # All \Active \Model validations are built on top of this validator.
class EachValidator < Validator #:nodoc:
attr_reader :attributes
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index b1f9082ea7..6da3b4117b 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,7 +1,7 @@
require_relative 'gem_version'
module ActiveModel
- # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
def self.version
gem_version
end
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index 5fede098d1..85455c112c 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -7,6 +7,7 @@ class CallbacksTest < ActiveModel::TestCase
model.callbacks << :before_around_create
yield
model.callbacks << :after_around_create
+ false
end
end
@@ -24,16 +25,22 @@ class CallbacksTest < ActiveModel::TestCase
after_create do |model|
model.callbacks << :after_create
+ false
end
after_create "@callbacks << :final_callback"
- def initialize(valid=true)
- @callbacks, @valid = [], valid
+ def initialize(options = {})
+ @callbacks = []
+ @valid = options[:valid]
+ @before_create_returns = options.fetch(:before_create_returns, true)
+ @before_create_throws = options[:before_create_throws]
end
def before_create
@callbacks << :before_create
+ throw(@before_create_throws) if @before_create_throws
+ @before_create_returns
end
def create
@@ -51,14 +58,28 @@ class CallbacksTest < ActiveModel::TestCase
:after_around_create, :after_create, :final_callback]
end
- test "after callbacks are always appended" do
+ test "the callback chain is not halted when around or after callbacks return false" do
model = ModelCallbacks.new
model.create
assert_equal model.callbacks.last, :final_callback
end
+ test "the callback chain is halted when a before callback returns false (deprecated)" do
+ model = ModelCallbacks.new(before_create_returns: false)
+ assert_deprecated do
+ model.create
+ assert_equal model.callbacks.last, :before_create
+ end
+ end
+
+ test "the callback chain is halted when a callback throws :abort" do
+ model = ModelCallbacks.new(before_create_throws: :abort)
+ model.create
+ assert_equal model.callbacks, [:before_create]
+ end
+
test "after callbacks are not executed if the block returns false" do
- model = ModelCallbacks.new(false)
+ model = ModelCallbacks.new(valid: false)
model.create
assert_equal model.callbacks, [ :before_create, :before_around_create,
:create, :after_around_create]
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index db2cd885e2..8ffd62fd86 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -181,23 +181,6 @@ class DirtyTest < ActiveModel::TestCase
assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
end
- test "reset_changes is deprecated" do
- @model.name = 'Dmitry'
- @model.name_changed?
- @model.save
- @model.name = 'Bob'
-
- assert_equal [nil, 'Dmitry'], @model.previous_changes['name']
- assert_equal 'Dmitry', @model.changed_attributes['name']
-
- assert_deprecated do
- @model.deprecated_reload
- end
-
- assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes
- assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
- end
-
test "restore_attributes should restore all previous data" do
@model.name = 'Dmitry'
@model.color = 'Red'
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index 6cd0f4ed4d..4b0dd58efb 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -30,11 +30,21 @@ class DogWithTwoValidators < Dog
before_validation { self.history << 'before_validation_marker2' }
end
-class DogValidatorReturningFalse < Dog
+class DogDeprecatedBeforeValidatorReturningFalse < Dog
before_validation { false }
before_validation { self.history << 'before_validation_marker2' }
end
+class DogBeforeValidatorThrowingAbort < Dog
+ before_validation { throw :abort }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogAfterValidatorReturningFalse < Dog
+ after_validation { false }
+ after_validation { self.history << 'after_validation_marker' }
+end
+
class DogWithMissingName < Dog
before_validation { self.history << 'before_validation_marker' }
validates_presence_of :name
@@ -81,13 +91,28 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history
end
- def test_further_callbacks_should_not_be_called_if_before_validation_returns_false
- d = DogValidatorReturningFalse.new
+ def test_further_callbacks_should_not_be_called_if_before_validation_throws_abort
+ d = DogBeforeValidatorThrowingAbort.new
output = d.valid?
assert_equal [], d.history
assert_equal false, output
end
+ def test_deprecated_further_callbacks_should_not_be_called_if_before_validation_returns_false
+ d = DogDeprecatedBeforeValidatorReturningFalse.new
+ assert_deprecated do
+ output = d.valid?
+ assert_equal [], d.history
+ assert_equal false, output
+ end
+ end
+
+ def test_further_callbacks_should_be_called_if_after_validation_returns_false
+ d = DogAfterValidatorReturningFalse.new
+ d.valid?
+ assert_equal ['after_validation_marker'], d.history
+ end
+
def test_validation_test_should_be_done
d = DogWithMissingName.new
output = d.valid?
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 3834d327ea..12a22f9c40 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -59,7 +59,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_of_with_integer_only_and_proc_as_value
Topic.send(:define_method, :allow_only_integers?, lambda { false })
- Topic.validates_numericality_of :approved, only_integer: Proc.new {|topic| topic.allow_only_integers? }
+ Topic.validates_numericality_of :approved, only_integer: Proc.new(&:allow_only_integers?)
invalid!(NIL + BLANK + JUNK)
valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
@@ -130,7 +130,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_with_proc
Topic.send(:define_method, :min_approved, lambda { 5 })
- Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new {|topic| topic.min_approved }
+ Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new(&:min_approved)
invalid!([3, 4])
valid!([5, 6])
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index de71bb6f42..2b932683ea 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -171,10 +171,45 @@ class ValidationsTest < ActiveModel::TestCase
# A common mistake -- we meant to call 'validates'
Topic.validate :title, presence: true
end
- message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless. Perhaps you meant to call `validates` instead of `validate`?'
+ message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless, :prepend. Perhaps you meant to call `validates` instead of `validate`?'
assert_equal message, error.message
end
+ def test_callback_options_to_validate
+ klass = Class.new(Topic) do
+ attr_reader :call_sequence
+
+ def initialize(*)
+ super
+ @call_sequence = []
+ end
+
+ private
+ def validator_a
+ @call_sequence << :a
+ end
+
+ def validator_b
+ @call_sequence << :b
+ end
+
+ def validator_c
+ @call_sequence << :c
+ end
+ end
+
+ assert_nothing_raised do
+ klass.validate :validator_a, if: ->{ true }
+ klass.validate :validator_b, prepend: true
+ klass.validate :validator_c, unless: ->{ true }
+ end
+
+ t = klass.new
+
+ assert_predicate t, :valid?
+ assert_equal [:b, :a], t.call_sequence
+ end
+
def test_errors_conversions
Topic.validates_presence_of %w(title content)
t = Topic.new
@@ -282,7 +317,7 @@ class ValidationsTest < ActiveModel::TestCase
ActiveModel::Validations::FormatValidator,
ActiveModel::Validations::LengthValidator,
ActiveModel::Validations::PresenceValidator
- ], validators.map { |v| v.class }.sort_by { |c| c.to_s }
+ ], validators.map(&:class).sort_by(&:to_s)
end
def test_list_of_validators_will_be_empty_when_empty
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index e4c59673cd..97e65a5bb9 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1297 +1,297 @@
-* Add `Table#name` to match `TableDefinition#name`.
-
- *Cody Cutrer*
-
-* Cache `CollectionAssociation#reader` proxies separately before and after
- the owner has been saved so that the proxy is not cached without the
- owner's id.
-
- *Ben Woosley*
-
-* `ActiveRecord::ReadOnlyRecord` now has a descriptive message.
-
- *Franky W.*
-
-* Fix preloading of associations which unscope a default scope.
-
- Fixes #11036.
-
- *Byron Bischoff*
-
-* Added SchemaDumper support for tables with jsonb columns.
-
- *Ted O'Meara*
-
-* Deprecate `sanitize_sql_hash_for_conditions` without replacement. Using a
- `Relation` for performing queries and updates is the prefered API.
-
- *Sean Griffin*
-
-* Queries now properly type cast values that are part of a join statement,
- even when using type decorators such as `serialize`.
-
- *Melanie Gilman & Sean Griffin*
-
-* MySQL enum type lookups, with values matching another type, no longer result
- in an endless loop.
-
- Fixes #17402.
+* Remove deprecated support to preload instance-dependent associations.
*Yves Senn*
-* Raise `ArgumentError` when the body of a scope is not callable.
-
- *Mauro George*
-
-* Use type column first in multi-column indexes created with `add-reference`.
-
- *Derek Prior*
-
-* Fix `Relation.rewhere` to work with Range values.
-
- *Dan Olson*
-
-* `AR::UnknownAttributeError` now includes the class name of a record.
-
- User.new(name: "Yuki Nishijima", project_attributes: {name: "kaminari"})
- # => ActiveRecord::UnknownAttributeError: unknown attribute 'name' for User.
-
- *Yuki Nishijima*
-
-* Fix regression causing `after_create` callbacks to run before associated
- records are autosaved.
-
- Fixes #17209.
-
- *Agis Anastasopoulos*
-
-* Honor overridden `rack.test` in Rack environment for the connection
- management middleware.
-
- *Simon Eskildsen*
-
-* Add a truncate method to the connection.
-
- *Aaron Patterson*
-
-* Don't autosave unchanged has_one through records.
-
- *Alan Kennedy*, *Steve Parrington*
-
-* Do not dump foreign keys for ignored tables.
+* Remove deprecated support for PostgreSQL ranges with exclusive lower bounds.
*Yves Senn*
-* PostgreSQL adapter correctly dumps foreign keys targeting tables
- outside the schema search path.
-
- Fixes #16907.
-
- *Matthew Draper*, *Yves Senn*
-
-* When a thread is killed, rollback the active transaction, instead of
- committing it during the stack unwind. Previously, we could commit half-
- completed work. This fix only works for Ruby 2.0+; on 1.9, we can't
- distinguish a thread kill from an ordinary non-local (block) return, so must
- default to committing.
-
- *Chris Hanks*
-
-* A `NullRelation` should represent nothing. This fixes a bug where
- `Comment.where(post_id: Post.none)` returned a non-empty result.
-
- Fixes #15176.
-
- *Matthew Draper*, *Yves Senn*
-
-* Include default column limits in schema.rb. Allows defaults to be changed
- in the future without affecting old migrations that assumed old defaults.
-
- *Jeremy Kemper*
-
-* MySQL: schema.rb now includes TEXT and BLOB column limits.
-
- *Jeremy Kemper*
-
-* MySQL: correct LONGTEXT and LONGBLOB limits from 2GB to their true 4GB.
-
- *Jeremy Kemper*
-
-* SQLite3Adapter now checks for views in `table_exists?`. Fixes #14041.
-
- *Girish Sonawane*
-
-* Introduce `connection.supports_views?` to check whether the current adapter
- has support for SQL views. Connection adapters should define this method.
-
- *Yves Senn*
-
-* Allow included modules to override association methods.
-
- Fixes #16684.
-
- *Yves Senn*
-
-* Schema loading rake tasks (like `db:schema:load` and `db:setup`) maintain
- the database connection to the current environment.
-
- Fixes #16757.
-
- *Joshua Cody*, *Yves Senn*
-
-* MySQL: set the connection collation along with the charset.
-
- Sets the connection collation to the database collation configured in
- database.yml. Otherwise, `SET NAMES utf8mb4` will use the default
- collation for that charset (utf8mb4_general_ci) when you may have chosen
- a different collation, like utf8mb4_unicode_ci.
-
- This only applies to literal string comparisons, not column values, so it
- is unlikely to affect you.
-
- *Jeremy Kemper*
-
-* `default_sequence_name` from the PostgreSQL adapter returns a `String`.
+* Remove deprecation when modifying a relation with cached arel.
+ This raises an `ImmutableRelation` error instead.
*Yves Senn*
-* Fixed a regression where whitespaces were stripped from DISTINCT queries in
- PostgreSQL.
-
- *Agis Anastasopoulos*
-
- Fixes #16623.
-
-* Fix has_many :through relation merging failing when dynamic conditions are
- passed as a lambda with an arity of one.
-
- Fixes #16128.
-
- *Agis Anastasopoulos*
-
-* Fixed `Relation#exists?` to work with polymorphic associations.
-
- Fixes #15821.
-
- *Kassio Borges*
-
-* Currently, Active Record rescues any errors raised within
- `after_rollback`/`after_create` callbacks and prints them to the logs.
- Future versions of Rails will not rescue these errors anymore and
- just bubble them up like the other callbacks.
-
- This commit adds an opt-in flag to enable not rescuing the errors.
-
- Example:
-
- # Do not swallow errors in after_commit/after_rollback callbacks.
- config.active_record.raise_in_transactional_callbacks = true
-
- Fixes #13460.
-
- *arthurnn*
-
-* Fixed an issue where custom accessor methods (such as those generated by
- `enum`) with the same name as a global method are incorrectly overridden
- when subclassing.
-
- Fixes #16288.
-
- *Godfrey Chan*
+* Added `ActiveRecord::SecureToken` in order to encapsulate generation of
+ unique tokens for attributes in a model using `SecureRandom`.
-* `*_was` and `changes` now work correctly for in-place attribute changes as
- well.
+ *Roberto Miranda*
- *Sean Griffin*
-
-* Fix regression on `after_commit` that did not fire with nested transactions.
-
- Fixes #16425.
-
- *arthurnn*
-
-* Do not try to write timestamps when a table has no timestamps columns.
+* Change the behavior of boolean columns to be closer to Ruby's semantics.
- Fixes #8813.
+ Before this change we had a small set of "truthy", and all others are "falsy".
- *Sergey Potapov*
-
-* `index_exists?` with `:name` option does verify specified columns.
-
- Example:
-
- add_index :articles, :title, name: "idx_title"
-
- # Before:
- index_exists? :articles, :title, name: "idx_title" # => `true`
- index_exists? :articles, :body, name: "idx_title" # => `true`
-
- # After:
- index_exists? :articles, :title, name: "idx_title" # => `true`
- index_exists? :articles, :body, name: "idx_title" # => `false`
-
- *Yves Senn*, *Matthew Draper*
-
-* When calling `update_columns` on a record that is not persisted, the error
- message now reflects whether that object is a new record or has been
- destroyed.
-
- *Lachlan Sylvester*
-
-* Define `id_was` to get the previous value of the primary key.
-
- Currently when we call `id_was` and we have a custom primary key name,
- Active Record will return the current value of the primary key. This
- makes it impossible to correctly do an update operation if you change the
- id.
-
- Fixes #16413.
+ Now, we have a small set of "falsy" values and all others are "truthy" matching
+ Ruby's semantics.
*Rafael Mendonça França*
-* Deprecate `DatabaseTasks.load_schema` to act on the current connection.
- Use `.load_schema_current` instead. In the future `load_schema` will
- require the `configuration` to act on as an argument.
-
- *Yves Senn*
-
-* Fixed automatic maintaining test schema to properly handle sql structure
- schema format.
-
- Fixes #15394.
-
- *Wojciech Wnętrzak*
-
-* Fix type casting to Decimal from Float with large precision.
-
- *Tomohiro Hashidate*
-
-* Deprecate `Reflection#source_macro`
-
- `Reflection#source_macro` is no longer needed in Active Record
- source so it has been deprecated. Code that used `source_macro`
- was removed in #16353.
-
- *Eileen M. Uchtitelle*, *Aaron Patterson*
-
-* No verbose backtrace by `db:drop` when database does not exist.
-
- Fixes #16295.
-
- *Kenn Ejima*
-
-* Add support for PostgreSQL JSONB.
-
- Example:
-
- create_table :posts do |t|
- t.jsonb :meta_data
- end
-
- *Philippe Creux*, *Chris Teague*
-
-* `db:purge` with MySQL respects `Rails.env`.
-
- *Yves Senn*
-
-* `change_column_default :table, :column, nil` with PostgreSQL will issue a
- `DROP DEFAULT` instead of a `DEFAULT NULL` query.
-
- Fixes #16261.
-
- *Matthew Draper*, *Yves Senn*
-
-* Allow to specify a type for the foreign key column in `references`
- and `add_reference`.
-
- Example:
-
- change_table :vehicle do |t|
- t.references :station, type: :uuid
- end
-
- *Andrey Novikov*, *Łukasz Sarnacki*
-
-* `create_join_table` removes a common prefix when generating the join table.
- This matches the existing behavior of HABTM associations.
-
- Fixes #13683.
-
- *Stefan Kanev*
-
-* Do not swallow errors on `compute_type` when having a bad `alias_method` on
- a class.
-
- *arthurnn*
-
-* PostgreSQL invalid `uuid` are convert to nil.
-
- *Abdelkader Boudih*
-
-* Restore 4.0 behavior for using serialize attributes with `JSON` as coder.
-
- With 4.1.x, `serialize` started returning a string when `JSON` was passed as
- the second attribute. It will now return a hash as per previous versions.
-
- Example:
-
- class Post < ActiveRecord::Base
- serialize :comment, JSON
- end
-
- class Comment
- include ActiveModel::Model
- attr_accessor :category, :text
- end
-
- post = Post.create!
- post.comment = Comment.new(category: "Animals", text: "This is a comment about squirrels.")
- post.save!
+* Deprecate `ActiveRecord::Base.errors_in_transactional_callbacks=`.
- # 4.0
- post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."}
-
- # 4.1 before
- post.comment # => "#<Comment:0x007f80ab48ff98>"
-
- # 4.1 after
- post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."}
-
- When using `JSON` as the coder in `serialize`, Active Record will use the
- new `ActiveRecord::Coders::JSON` coder which delegates its `dump/load` to
- `ActiveSupport::JSON.encode/decode`. This ensures special objects are dumped
- correctly using the `#as_json` hook.
+ *Rafael Mendonça França*
- To keep the previous behaviour, supply a custom coder instead
- ([example](https://gist.github.com/jenncoop/8c4142bbe59da77daa63)).
+* Change transaction callbacks to not swallowing errors.
- Fixes #15594.
+ Before this change any error raised inside a transaction callback are
+ rescued and printed in the logs.
- *Jenn Cooper*
+ Now these errors are not rescue anymore and just bubble up, as the other callbacks.
-* Do not use `RENAME INDEX` syntax for MariaDB 10.0.
+ *Rafael Mendonça França*
- Fixes #15931.
+* Remove deprecated `sanitize_sql_hash_for_conditions`.
- *Jeff Browning*
+ *Rafael Mendonça França*
-* Calling `#empty?` on a `has_many` association would use the value from the
- counter cache if one exists.
+* Remove deprecated `Reflection#source_macro`.
- *David Verhasselt*
+ *Rafael Mendonça França*
-* Fix the schema dump generated for tables without constraints and with
- primary key with default value of custom PostgreSQL function result.
+* Remove deprecated `symbolized_base_class` and `symbolized_sti_name`.
- Fixes #16111.
+ *Rafael Mendonça França*
- *Andrey Novikov*
+* Remove deprecated `ActiveRecord::Base.disable_implicit_join_references=`.
-* Fix the SQL generated when a `delete_all` is run on an association to not
- produce an `IN` statements.
+ *Rafael Mendonça França*
- Before:
+* Remove deprecated access to connection specification using a string acessor.
- UPDATE "categorizations" SET "category_id" = NULL WHERE
- "categorizations"."category_id" = 1 AND "categorizations"."id" IN (1, 2)
+ Now all strings will be handled as a URL.
- After:
+ *Rafael Mendonça França*
- UPDATE "categorizations" SET "category_id" = NULL WHERE
- "categorizations"."category_id" = 1
+* Change the default `null` value for `timestamps` to `false`.
- *Eileen M. Uchitelle, Aaron Patterson*
+ *Rafael Mendonça França*
-* Avoid type casting boolean and `ActiveSupport::Duration` values to numeric
- values for string columns. Otherwise, in some database, the string column
- values will be coerced to a numeric allowing false or 0.seconds match any
- string starting with a non-digit.
+* Return an array of pools from `connection_pools`.
- Example:
+ *Rafael Mendonça França*
- App.where(apikey: false) # => SELECT * FROM users WHERE apikey = '0'
+* Return a null column from `column_for_attribute` when no column exists.
- *Dylan Thacker-Smith*
+ *Rafael Mendonça França*
-* Add a `:required` option to singular associations, providing a nicer
- API for presence validations on associations.
+* Remove deprecated `serialized_attributes`.
- *Sean Griffin*
+ *Rafael Mendonça França*
-* Fixed error in `reset_counters` when associations have `select` scope.
- (Call to `count` generates invalid SQL.)
+* Remove deprecated automatic counter caches on `has_many :through`.
- *Cade Truitt*
+ *Rafael Mendonça França*
-* After a successful `reload`, `new_record?` is always false.
+* Change the way in which callback chains can be halted.
- Fixes #12101.
+ The preferred method to halt a callback chain from now on is to explicitly
+ `throw(:abort)`.
+ In the past, returning `false` in an ActiveRecord `before_` callback had the
+ side effect of halting the callback chain.
+ This is not recommended anymore and, depending on the value of the
+ `config.active_support.halt_callback_chains_on_return_false` option, will
+ either not work at all or display a deprecation warning.
- *Matthew Draper*
+ *claudiob*
-* PostgreSQL renaming table doesn't attempt to rename non existent sequences.
+* Clear query cache on rollback.
- *Abdelkader Boudih*
+ *Florian Weingarten*
-* Move 'dependent: :destroy' handling for `belongs_to`
- from `before_destroy` to `after_destroy` callback chain
+* Fixed setting of foreign_key for through associations while building of new record.
- Fixes #12380.
+ Fixes #12698.
*Ivan Antropov*
-* Detect in-place modifications on String attributes.
-
- Before this change, an attribute modified in-place had to be marked as
- changed in order for it to be persisted in the database. Now it is no longer
- required.
-
- Before:
-
- user = User.first
- user.name << ' Griffin'
- user.name_will_change!
- user.save
- user.reload.name # => "Sean Griffin"
-
- After:
-
- user = User.first
- user.name << ' Griffin'
- user.save
- user.reload.name # => "Sean Griffin"
-
- *Sean Griffin*
-
-* Add `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the record
- is invalid.
-
- *Bogdan Gusiev*, *Marc Schütz*
-
-* Support for adding and removing foreign keys. Foreign keys are now
- a part of `schema.rb`. This is supported by Mysql2Adapter, MysqlAdapter
- and PostgreSQLAdapter.
-
- Many thanks to *Matthew Higgins* for laying the foundation with his work on
- [foreigner](https://github.com/matthuhiggins/foreigner).
-
- Example:
-
- # within your migrations:
- add_foreign_key :articles, :authors
- remove_foreign_key :articles, :authors
-
- *Yves Senn*
-
-* Fix subtle bugs regarding attribute assignment on models with no primary
- key. `'id'` will no longer be part of the attributes hash.
-
- *Sean Griffin*
-
-* Deprecate automatic counter caches on `has_many :through`. The behavior was
- broken and inconsistent.
-
- *Sean Griffin*
-
-* `preload` preserves readonly flag for associations.
-
- See #15853.
-
- *Yves Senn*
-
-* Assume numeric types have changed if they were assigned to a value that
- would fail numericality validation, regardless of the old value. Previously
- this would only occur if the old value was 0.
-
- Example:
-
- model = Model.create!(number: 5)
- model.number = '5wibble'
- model.number_changed? # => true
-
- Fixes #14731.
-
- *Sean Griffin*
-
-* `reload` no longer merges with the existing attributes.
- The attribute hash is fully replaced. The record is put into the same state
- as it would be with `Model.find(model.id)`.
+* Improve a dump of the primary key support. If it is not a default primary key,
+ correctly dump the type and options.
- *Sean Griffin*
-
-* The object returned from `select_all` must respond to `column_types`.
- If this is not the case a `NoMethodError` is raised.
-
- *Sean Griffin*
+ Fixes #14169, #16599.
-* Detect in-place modifications of PG array types
-
- *Sean Griffin*
-
-* Add `bin/rake db:purge` task to empty the current database.
+ *Ryuta Kamizono*
- *Yves Senn*
+* Format the datetime string according to the precision of the datetime field.
-* Deprecate `serialized_attributes` without replacement.
+ Incompatible to rounding behavior between MySQL 5.6 and earlier.
- *Sean Griffin*
+ In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part
+ is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`:
-* Correctly extract IPv6 addresses from `DATABASE_URI`: the square brackets
- are part of the URI structure, not the actual host.
+ http://bugs.mysql.com/bug.php?id=68760
- Fixes #15705.
+ *Ryuta Kamizono*
- *Andy Bakun*, *Aaron Stone*
+* Allow precision option for MySQL datetimes.
-* Ensure both parent IDs are set on join records when both sides of a
- through association are new.
+ *Ryuta Kamizono*
- *Sean Griffin*
+* Fixed automatic inverse_of for models nested in module.
-* `ActiveRecord::Dirty` now detects in-place changes to mutable values.
- Serialized attributes on ActiveRecord models will no longer save when
- unchanged.
+ *Andrew McCloud*
- Fixes #8328.
+* Change `ActiveRecord::Relation#update` behavior so that it can
+ be called without passing ids of the records to be updated.
- *Sean Griffin*
+ This change allows to update multiple records returned by
+ `ActiveRecord::Relation` with callbacks and validations.
-* `Pluck` now works when selecting columns from different tables with the same
- name.
+ # Before
+ # ArgumentError: wrong number of arguments (1 for 2)
+ Comment.where(group: 'expert').update(body: "Group of Rails Experts")
- Fixes #15649.
+ # After
+ # Comments with group expert updated with body "Group of Rails Experts"
+ Comment.where(group: 'expert').update(body: "Group of Rails Experts")
- *Sean Griffin*
+ *Prathamesh Sonpatki*
-* Remove `cache_attributes` and friends. All attributes are cached.
+* Fix `reaping_frequency` option when the value is a string.
- *Sean Griffin*
+ This usually happens when it is configured using `DATABASE_URL`.
-* Remove deprecated method `ActiveRecord::Base.quoted_locking_column`.
+ *korbin*
- *Akshay Vishnoi*
+* Fix error message when trying to create an associated record and the foreign
+ key is missing.
-* `ActiveRecord::FinderMethods.find` with block can handle proc parameter as
- `Enumerable#find` does.
+ Before this fix the following exception was being raised:
- Fixes #15382.
+ NoMethodError: undefined method `val' for #<Arel::Nodes::BindParam:0x007fc64d19c218>
- *James Yang*
+ Now the message is:
-* Make timezone aware attributes work with PostgreSQL array columns.
+ ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model.
- Fixes #13402.
-
- *Kuldeep Aggarwal*, *Sean Griffin*
+ *Rafael Mendonça França*
-* `ActiveRecord::SchemaMigration` has no primary key regardless of the
- `primary_key_prefix_type` configuration.
+* When a table has a composite primary key, the `primary_key` method for
+ SQLite3 and PostgreSQL adapters was only returning the first field of the key.
+ Ensures that it will return nil instead, as Active Record doesn't support
+ composite primary keys.
- Fixes #15051.
+ Fixes #18070.
- *JoseLuis Torres*, *Yves Senn*
+ *arthurnn*
-* `rake db:migrate:status` works with legacy migration numbers like `00018_xyz.rb`.
+* `validates_size_of` / `validates_length_of` do not count records,
+ which are `marked_for_destruction?`.
- Fixes #15538.
+ Fixes #7247.
*Yves Senn*
-* Baseclass becomes! subclass.
-
- Before this change, a record which changed its STI type, could not be
- updated.
-
- Fixes #14785.
-
- *Matthew Draper*, *Earl St Sauver*, *Edo Balvers*
-
-* Remove deprecated `ActiveRecord::Migrator.proper_table_name`. Use the
- `proper_table_name` instance method on `ActiveRecord::Migration` instead.
-
- *Akshay Vishnoi*
+* Ensure `first!` and friends work on loaded associations.
-* Fix regression on eager loading association based on SQL query rather than
- existing column.
-
- Fixes #15480.
-
- *Lauro Caetano*, *Carlos Antonio da Silva*
-
-* Deprecate returning `nil` from `column_for_attribute` when no column exists.
- It will return a null object in Rails 5.0
+ Fixes #18237.
*Sean Griffin*
-* Implemented `ActiveRecord::Base#pretty_print` to work with PP.
-
- *Ethan*
+* `eager_load` preserves readonly flag for associations.
-* Preserve type when dumping PostgreSQL point, bit, bit varying and money
- columns.
+ Closes #15853.
- *Yves Senn*
-
-* New records remain new after YAML serialization.
-
- *Sean Griffin*
-
-* PostgreSQL support default values for enum types. Fixes #7814.
-
- *Yves Senn*
-
-* PostgreSQL `default_sequence_name` respects schema. Fixes #7516.
-
- *Yves Senn*
-
-* Fixed `columns_for_distinct` of postgresql adapter to work correctly
- with orders without sort direction modifiers.
-
- *Nikolay Kondratyev*
-
-* PostgreSQL `reset_pk_sequence!` respects schemas. Fixes #14719.
-
- *Yves Senn*
-
-* Keep PostgreSQL `hstore` and `json` attributes as `Hash` in `@attributes`.
- Fixes duplication in combination with `store_accessor`.
-
- Fixes #15369.
-
- *Yves Senn*
+ *Takashi Kokubun*
-* `rake railties:install:migrations` respects the order of railties.
+* Provide `:touch` option to `save()` to accommodate saving without updating
+ timestamps.
- *Arun Agrawal*
+ Fixes #18202.
-* Fix redefine a `has_and_belongs_to_many` inside inherited class
- Fixing regression case, where redefining the same `has_and_belongs_to_many`
- definition into a subclass would raise.
-
- Fixes #14983.
-
- *arthurnn*
-
-* Fix `has_and_belongs_to_many` public reflection.
- When defining a `has_and_belongs_to_many`, internally we convert that to two has_many.
- But as `reflections` is a public API, people expect to see the right macro.
-
- Fixes #14682.
-
- *arthurnn*
-
-* Fixed serialization for records with an attribute named `format`.
-
- Fixes #15188.
-
- *Godfrey Chan*
-
-* When a `group` is set, `sum`, `size`, `average`, `minimum` and `maximum`
- on a NullRelation should return a Hash.
-
- *Kuldeep Aggarwal*
-
-* Fixed serialized fields returning serialized data after being updated with
- `update_column`.
-
- *Simon Hørup Eskildsen*
-
-* Fixed polymorphic eager loading when using a String as foreign key.
-
- Fixes #14734.
-
- *Lauro Caetano*
-
-* Change belongs_to touch to be consistent with timestamp updates
-
- If a model is set up with a belongs_to: touch relationship the parent
- record will only be touched if the record was modified. This makes it
- consistent with timestamp updating on the record itself.
-
- *Brock Trappitt*
-
-* Fixed the inferred table name of a `has_and_belongs_to_many` auxiliar
- table inside a schema.
-
- Fixes #14824.
+ *Dan Olson*
- *Eric Chahin*
+* Provide a more helpful error message when an unsupported class is passed to
+ `serialize`.
-* Remove unused `:timestamp` type. Transparently alias it to `:datetime`
- in all cases. Fixes inconsistencies when column types are sent outside of
- `ActiveRecord`, such as for XML Serialization.
+ Fixes #18224.
*Sean Griffin*
-* Fix bug that added `table_name_prefix` and `table_name_suffix` to
- extension names in PostgreSQL when migrating.
-
- *Joao Carlos*
-
-* The `:index` option in migrations, which previously was only available for
- `references`, now works with any column types.
-
- *Marc Schütz*
-
-* Add support for counter name to be passed as parameter on `CounterCache::ClassMethods#reset_counters`.
-
- *jnormore*
-
-* Restrict deletion of record when using `delete_all` with `uniq`, `group`, `having`
- or `offset`.
-
- In these cases the generated query ignored them and that caused unintended
- records to be deleted.
-
- Fixes #11985.
-
- *Leandro Facchinetti*
-
-* Floats with limit >= 25 that get turned into doubles in MySQL no longer have
- their limit dropped from the schema.
-
- Fixes #14135.
-
- *Aaron Nelson*
-
-* Fix how to calculate associated class name when using namespaced `has_and_belongs_to_many`
- association.
-
- Fixes #14709.
-
- *Kassio Borges*
-
-* `ActiveRecord::Relation::Merger#filter_binds` now compares equivalent symbols and
- strings in column names as equal.
-
- This fixes a rare case in which more bind values are passed than there are
- placeholders for them in the generated SQL statement, which can make PostgreSQL
- throw a `StatementInvalid` exception.
-
- *Nat Budin*
-
-* Fix `stored_attributes` to correctly merge the details of stored
- attributes defined in parent classes.
-
- Fixes #14672.
-
- *Brad Bennett*, *Jessica Yao*, *Lakshmi Parthasarathy*
-
-* `change_column_default` allows `[]` as argument to `change_column_default`.
-
- Fixes #11586.
-
- *Yves Senn*
-
-* Handle `name` and `"char"` column types in the PostgreSQL adapter.
-
- `name` and `"char"` are special character types used internally by
- PostgreSQL and are used by internal system catalogs. These field types
- can sometimes show up in structure-sniffing queries that feature internal system
- structures or with certain PostgreSQL extensions.
-
- *J Smith*, *Yves Senn*
-
-* Fix `PostgreSQLAdapter::OID::Float#type_cast` to convert Infinity and
- NaN PostgreSQL values into a native Ruby `Float::INFINITY` and `Float::NAN`
-
- Before:
-
- Point.create(value: 1.0/0)
- Point.last.value # => 0.0
-
- After:
-
- Point.create(value: 1.0/0)
- Point.last.value # => Infinity
-
- *Innokenty Mikhailov*
-
-* Allow the PostgreSQL adapter to handle bigserial primary key types again.
-
- Fixes #10410.
-
- *Patrick Robertson*
-
-* Deprecate joining, eager loading and preloading of instance dependent
- associations without replacement. These operations happen before instances
- are created. The current behavior is unexpected and can result in broken
- behavior.
-
- Fixes #15024.
-
- *Yves Senn*
-
-* Fixed has_and_belongs_to_many's CollectionAssociation size calculation.
-
- `has_and_belongs_to_many` should fall back to using the normal CollectionAssociation's
- size calculation if the collection is not cached or loaded.
-
- Fixes #14913, #14914.
-
- *Fred Wu*
-
-* Return a non zero status when running `rake db:migrate:status` and migration table does
- not exist.
-
- *Paul B.*
-
-* Add support for module-level `table_name_suffix` in models.
-
- This makes `table_name_suffix` work the same way as `table_name_prefix` when
- using namespaced models.
-
- *Jenner LaFave*
-
-* Revert the behaviour of `ActiveRecord::Relation#join` changed through 4.0 => 4.1 to 4.0.
-
- In 4.1.0 `Relation#join` is delegated to `Arel#SelectManager`.
- In 4.0 series it is delegated to `Array#join`.
-
- *Bogdan Gusiev*
-
-* Log nil binary column values correctly.
-
- When an object with a binary column is updated with a nil value
- in that column, the SQL logger would throw an exception when trying
- to log that nil value. This only occurs when updating a record
- that already has a non-nil value in that column since an initial nil
- value isn't included in the SQL anyway (at least, when dirty checking
- is enabled.) The column's new value will now be logged as `<NULL binary data>`
- to parallel the existing `<N bytes of binary data>` for non-nil values.
-
- *James Coleman*
-
-* Rails will now pass a custom validation context through to autosave associations
- in order to validate child associations with the same context.
-
- Fixes #13854.
-
- *Eric Chahin*, *Aaron Nelson*, *Kevin Casey*
-
-* Stringify all variables keys of MySQL connection configuration.
-
- When `sql_mode` variable for MySQL adapters set in configuration as `String`
- was ignored and overwritten by strict mode option.
-
- Fixes #14895.
-
- *Paul Nikitochkin*
-
-* Ensure SQLite3 statements are closed on errors.
-
- Fixes #13631.
-
- *Timur Alperovich*
-
-* Give `ActiveRecord::PredicateBuilder` private methods the privacy they deserve.
-
- *Hector Satre*
-
-* When using a custom `join_table` name on a `habtm`, rails was not saving it
- on Reflections. This causes a problem when rails loads fixtures, because it
- uses the reflections to set database with fixtures.
-
- Fixes #14845.
-
- *Kassio Borges*
-
-* Reset the cache when modifying a Relation with cached Arel.
- Additionally display a warning message to make the user aware.
-
- *Yves Senn*
-
-* PostgreSQL should internally use `:datetime` consistently for TimeStamp. Assures
- different spellings of timestamps are treated the same.
+* Add bigint primary key support for MySQL.
Example:
- mytimestamp.simplified_type('timestamp without time zone')
- # => :datetime
- mytimestamp.simplified_type('timestamp(6) without time zone')
- # => also :datetime (previously would be :timestamp)
-
- See #14513.
-
- *Jefferson Lai*
-
-* `ActiveRecord::Base.no_touching` no longer triggers callbacks or start empty transactions.
-
- Fixes #14841.
-
- *Lucas Mazza*
-
-* Fix name collision with `Array#select!` with `Relation#select!`.
-
- Fixes #14752.
-
- *Earl St Sauver*
-
-* Fixed unexpected behavior for `has_many :through` associations going through a scoped `has_many`.
-
- If a `has_many` association is adjusted using a scope, and another `has_many :through`
- uses this association, then the scope adjustment is unexpectedly neglected.
-
- Fixes #14537.
-
- *Jan Habermann*
-
-* `@destroyed` should always be set to `false` when an object is duped.
-
- *Kuldeep Aggarwal*
-
-* Fixed `has_many` association to make it support irregular inflections.
-
- Fixes #8928.
-
- *arthurnn*, *Javier Goizueta*
-
-* Fixed a problem where count used with a grouping was not returning a Hash.
-
- Fixes #14721.
-
- *Eric Chahin*
-
-* `sanitize_sql_like` helper method to escape a string for safe use in an SQL
- LIKE statement.
-
- Example:
-
- class Article
- def self.search(term)
- where("title LIKE ?", sanitize_sql_like(term))
- end
+ create_table :foos, id: :bigint do |t|
end
- Article.search("20% _reduction_")
- # => Query looks like "... title LIKE '20\% \_reduction\_' ..."
-
- *Rob Gilson*, *Yves Senn*
-
-* Do not quote uuid default value on `change_column`.
-
- Fixes #14604.
-
- *Eric Chahin*
-
-* The comparison between `Relation` and `CollectionProxy` should be consistent.
-
- Example:
-
- author.posts == Post.where(author_id: author.id)
- # => true
- Post.where(author_id: author.id) == author.posts
- # => true
-
- Fixes #13506.
-
- *Lauro Caetano*
-
-* Calling `delete_all` on an unloaded `CollectionProxy` no longer
- generates an SQL statement containing each id of the collection:
-
- Before:
-
- DELETE FROM `model` WHERE `model`.`parent_id` = 1
- AND `model`.`id` IN (1, 2, 3...)
-
- After:
-
- DELETE FROM `model` WHERE `model`.`parent_id` = 1
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select`
- which created invalid SQL.
-
- Fixes #13648.
-
- *Simon Woker*
-
-* PostgreSQL adapter only warns once for every missing OID per connection.
-
- Fixes #14275.
-
- *Matthew Draper*, *Yves Senn*
-
-* PostgreSQL adapter automatically reloads it's type map when encountering
- unknown OIDs.
-
- Fixes #14678.
-
- *Matthew Draper*, *Yves Senn*
-
-* Fix insertion of records via `has_many :through` association with scope.
-
- Fixes #3548.
-
- *Ivan Antropov*
-
-* Auto-generate stable fixture UUIDs on PostgreSQL.
-
- Fixes #11524.
-
- *Roderick van Domburg*
-
-* Fixed a problem where an enum would overwrite values of another enum
- with the same name in an unrelated class.
-
- Fixes #14607.
-
- *Evan Whalen*
-
-* PostgreSQL and SQLite string columns no longer have a default limit of 255.
-
- Fixes #13435, #9153.
-
- *Vladimir Sazhin*, *Toms Mikoss*, *Yves Senn*
-
-* Make possible to have an association called `records`.
-
- Fixes #11645.
-
- *prathamesh-sonpatki*
-
-* `to_sql` on an association now matches the query that is actually executed, where it
- could previously have incorrectly accrued additional conditions (e.g. as a result of
- a previous query). `CollectionProxy` now always defers to the association scope's
- `arel` method so the (incorrect) inherited one should be entirely concealed.
-
- Fixes #14003.
-
- *Jefferson Lai*
-
-* Block a few default Class methods as scope name.
-
- For instance, this will raise:
-
- scope :public, -> { where(status: 1) }
-
- *arthurnn*
-
-* Fixed error when using `with_options` with lambda.
-
- Fixes #9805.
-
- *Lauro Caetano*
-
-* Switch `sqlite3:///` URLs (which were temporarily
- deprecated in 4.1) from relative to absolute.
-
- If you still want the previous interpretation, you should replace
- `sqlite3:///my/path` with `sqlite3:my/path`.
-
- *Matthew Draper*
+ *Ryuta Kamizono*
-* Treat blank UUID values as `nil`.
+* Support for any type primary key.
- Example:
+ Fixes #14194.
- Sample.new(uuid_field: '') #=> <Sample id: nil, uuid_field: nil>
+ *Ryuta Kamizono*
- *Dmitry Lavrov*
+* Dump the default `nil` for PostgreSQL UUID primary key.
-* Enable support for materialized views on PostgreSQL >= 9.3.
+ *Ryuta Kamizono*
- *Dave Lee*
+* Add a `:foreign_key` option to `references` and associated migration
+ methods. The model and migration generators now use this option, rather than
+ the `add_foreign_key` form.
-* The PostgreSQL adapter supports custom domains. Fixes #14305.
+ *Sean Griffin*
- *Yves Senn*
+* Don't raise when writing an attribute with an out-of-range datetime passed
+ by the user.
-* PostgreSQL `Column#type` is now determined through the corresponding OID.
- The column types stay the same except for enum columns. They no longer have
- `nil` as type but `enum`.
+ *Grey Baker*
- See #7814.
+* Replace deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema` with
+ `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`.
*Yves Senn*
-* Fixed error when specifying a non-empty default value on a PostgreSQL array column.
-
- Fixes #10613.
-
- *Luke Steensen*
-
-* Fixed error where .persisted? throws SystemStackError for an unsaved model with a
- custom primary key that didn't save due to validation error.
-
- Fixes #14393.
+* Fixes bug with 'ActiveRecord::Type::Numeric' that causes negative values to
+ be marked as having changed when set to the same negative value.
- *Chris Finne*
+ Closes #18161.
-* Introduce `validate` as an alias for `valid?`.
+ *Daniel Fox*
- This is more intuitive when you want to run validations but don't care about the return value.
+* Introduce `force: :cascade` option for `create_table`. Using this option
+ will recreate tables even if they have dependent objects (like foreign keys).
+ `db/schema.rb` now uses `force: :cascade`. This makes it possible to
+ reload the schema when foreign keys are in place.
- *Henrik Nyh*
-
-* Create indexes inline in CREATE TABLE for MySQL.
-
- This is important, because adding an index on a temporary table after it has been created
- would commit the transaction.
-
- It also allows creating and dropping indexed tables with fewer queries and fewer permissions
- required.
-
- Example:
-
- create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t|
- t.index :zip
- end
- # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query
-
- *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca*
-
-* Use singular table name in generated migrations when
- `ActiveRecord::Base.pluralize_table_names` is `false`.
-
- Fixes #13426.
-
- *Kuldeep Aggarwal*
-
-* `touch` accepts many attributes to be touched at once.
-
- Example:
-
- # touches :signed_at, :sealed_at, and :updated_at/on attributes.
- Photo.last.touch(:signed_at, :sealed_at)
-
- *James Pinto*
-
-* `rake db:structure:dump` only dumps schema information if the schema
- migration table exists.
-
- Fixes #14217.
-
- *Yves Senn*
-
-* Reap connections that were checked out by now-dead threads, instead
- of waiting until they disconnect by themselves. Before this change,
- a suitably constructed series of short-lived threads could starve
- the connection pool, without ever having more than a couple alive at
- the same time.
-
- *Matthew Draper*
-
-* `pk_and_sequence_for` now ensures that only the pg_depend entries
- pointing to pg_class, and thus only sequence objects, are considered.
-
- *Josh Williams*
+ *Matthew Draper*, *Yves Senn*
-* `where.not` adds `references` for `includes` like normal `where` calls do.
+* `db:schema:load` and `db:structure:load` no longer purge the database
+ before loading the schema. This is left for the user to do.
+ `db:test:prepare` will still purge the database.
- Fixes #14406.
+ Closes #17945.
*Yves Senn*
-* Extend fixture `$LABEL` replacement to allow string interpolation.
-
- Example:
-
- martin:
- email: $LABEL@email.com
-
- users(:martin).email # => martin@email.com
-
- *Eric Steele*
-
-* Add support for `Relation` be passed as parameter on `QueryCache#select_all`.
-
- Fixes #14361.
-
- *arthurnn*
-
-* Passing an Active Record object to `find` or `exists?` is now deprecated.
- Call `.id` on the object first.
-
- *Aaron Patterson*
-
-* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation.
+* Fix undesirable RangeError by `Type::Integer`. Add `Type::UnsignedInteger`.
*Ryuta Kamizono*
-* Support for MySQL 5.6 fractional seconds.
-
- *arthurnn*, *Tatsuhiko Miyagawa*
-
-* Support for Postgres `citext` data type enabling case-insensitive where
- values without needing to wrap in UPPER/LOWER sql functions.
-
- *Troy Kruthoff*, *Lachlan Sylvester*
-
-* Only save has_one associations if record has changes.
- Previously after save related callbacks, such as `#after_commit`, were triggered when the has_one
- object did not get saved to the db.
-
- *Alan Kennedy*
-
-* Allow strings to specify the `#order` value.
-
- Example:
-
- Model.order(id: 'asc').to_sql == Model.order(id: :asc).to_sql
-
- *Marcelo Casiraghi*, *Robin Dupret*
+* Add `foreign_type` option to `has_one` and `has_many` association macros.
-* Dynamically register PostgreSQL enum OIDs. This prevents "unknown OID"
- warnings on enum columns.
+ This option enables to define the column name of associated object's type for polymorphic associations.
- *Dieter Komendera*
+ *Ulisses Almeida, Kassio Borges*
-* `includes` is able to detect the right preloading strategy when string
- joins are involved.
+* Remove deprecated behavior allowing nested arrays to be passed as query
+ values.
- Fixes #14109.
+ *Melanie Gilman*
- *Aaron Patterson*, *Yves Senn*
+* Deprecate passing a class as a value in a query. Users should pass strings
+ instead.
-* Fixed error with validation with enum fields for records where the
- value for any enum attribute is always evaluated as 0 during
- uniqueness validation.
+ *Melanie Gilman*
- Fixes #14172.
+* `add_timestamps` and `remove_timestamps` now properly reversible with
+ options.
- *Vilius Luneckas* *Ahmed AbouElhamayed*
-
-* `before_add` callbacks are fired before the record is saved on
- `has_and_belongs_to_many` associations *and* on `has_many :through`
- associations. Before this change, `before_add` callbacks would be fired
- before the record was saved on `has_and_belongs_to_many` associations, but
- *not* on `has_many :through` associations.
-
- Fixes #14144.
-
-* Fixed STI classes not defining an attribute method if there is a
- conflicting private method defined on its ancestors.
-
- Fixes #11569.
-
- *Godfrey Chan*
-
-* Coerce strings when reading attributes. Fixes #10485.
-
- Example:
-
- book = Book.new(title: 12345)
- book.save!
- book.title # => "12345"
-
- *Yves Senn*
-
-* Deprecate half-baked support for PostgreSQL range values with excluding beginnings.
- We currently map PostgreSQL ranges to Ruby ranges. This conversion is not fully
- possible because the Ruby range does not support excluded beginnings.
-
- The current solution of incrementing the beginning is not correct and is now
- deprecated. For subtypes where we don't know how to increment (e.g. `#succ`
- is not defined) it will raise an `ArgumentException` for ranges with excluding
- beginnings.
-
- *Yves Senn*
+ *Noam Gagliardi-Rabinovich*
-* Support for user created range types in PostgreSQL.
+* `ActiveRecord::ConnectionAdapters::ColumnDumper#column_spec` and
+ `ActiveRecord::ConnectionAdapters::ColumnDumper#prepare_column_options` no
+ longer have a `types` argument. They should access
+ `connection#native_database_types` directly.
*Yves Senn*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 2950f05b11..7c2197229d 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index 569685bd45..7e3460365b 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -20,11 +20,11 @@ example:
Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
- $ bundle exec rake test_mysql
- $ bundle exec rake test_mysql2
- $ bundle exec rake test_postgresql
- $ bundle exec rake test_sqlite3
- $ bundle exec rake test_sqlite3_mem
+ $ bundle exec rake test:mysql
+ $ bundle exec rake test:mysql2
+ $ bundle exec rake test:postgresql
+ $ bundle exec rake test:sqlite3
+ $ bundle exec rake test:sqlite3_mem
There should be tests available for each database backend listed in the {Config
File}[rdoc-label:label-Config+File]. (the exact set of available tests is
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index bc10e96244..c5cd0c89f7 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Object-relational mapper framework (part of Rails).'
s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '>= 6.0.0.beta2', '< 6.1'
+ s.add_dependency 'arel', '7.0.0.alpha'
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index d3546ce948..a5a1f284a0 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -39,8 +39,8 @@ class Exhibit < ActiveRecord::Base
where("notes IS NOT NULL")
end
- def self.look(exhibits) exhibits.each { |e| e.look } end
- def self.feel(exhibits) exhibits.each { |e| e.feel } end
+ def self.look(exhibits) exhibits.each(&:look) end
+ def self.feel(exhibits) exhibits.each(&:feel) end
end
def progress_bar(int); print "." if (int%100).zero? ; end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 9028970a3d..d9d47c3d99 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -62,10 +62,12 @@ module ActiveRecord
autoload :Serialization
autoload :StatementCache
autoload :Store
+ autoload :TableMetadata
autoload :Timestamp
autoload :Transactions
autoload :Translation
autoload :Validations
+ autoload :SecureToken
eager_autoload do
autoload :ActiveRecordError, 'active_record/errors'
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index e576ec4d40..1040e6e3bb 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -230,8 +230,8 @@ module ActiveRecord
private
def reader_method(name, class_name, mapping, allow_nil, constructor)
define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? })
- attrs = mapping.collect {|key, _| read_attribute(key)}
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
+ attrs = mapping.collect {|key, _| _read_attribute(key)}
object = constructor.respond_to?(:call) ?
constructor.call(*attrs) :
class_name.constantize.send(constructor, *attrs)
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 5a84792f45..f2b44913db 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -1,7 +1,7 @@
module ActiveRecord
class AssociationRelation < Relation
- def initialize(klass, table, association)
- super(klass, table)
+ def initialize(klass, table, predicate_builder, association)
+ super(klass, table, predicate_builder)
@association = association
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d2bcdc55bf..14af55f327 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -57,7 +57,7 @@ module ActiveRecord
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
source_associations = reflection.through_reflection.klass._reflections.keys
- super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
end
end
@@ -116,6 +116,7 @@ module ActiveRecord
autoload :Association, 'active_record/associations/association'
autoload :SingularAssociation, 'active_record/associations/singular_association'
autoload :CollectionAssociation, 'active_record/associations/collection_association'
+ autoload :ForeignAssociation, 'active_record/associations/foreign_association'
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
@@ -1176,6 +1177,12 @@ module ActiveRecord
# 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_many+
# association will use "person_id" as the default <tt>:foreign_key</tt>.
+ # [:foreign_type]
+ # Specify the column used to store the associated object's type, if this is a polymorphic
+ # association. By default this is guessed to be the name of the polymorphic association
+ # specified on "as" option with a "_type" suffix. So a class that defines a
+ # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the
+ # default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the name of the column to use as the primary key for the association. By default this is +id+.
# [:dependent]
@@ -1323,6 +1330,12 @@ module ActiveRecord
# 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
# will use "person_id" as the default <tt>:foreign_key</tt>.
+ # [:foreign_type]
+ # Specify the column used to store the associated object's type, if this is a polymorphic
+ # association. By default this is guessed to be the name of the polymorphic association
+ # specified on "as" option with a "_type" suffix. So a class that defines a
+ # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the
+ # default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:as]
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index a6a1947148..2b7e4f28c5 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -5,20 +5,23 @@ module ActiveRecord
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
# ActiveRecord::Associations::ThroughAssociationScope
class AliasTracker # :nodoc:
- attr_reader :aliases, :connection
+ attr_reader :aliases
- def self.empty(connection)
- new connection, Hash.new(0)
+ def self.create(connection, initial_table, type_caster)
+ aliases = Hash.new(0)
+ aliases[initial_table] = 1
+ new connection, aliases, type_caster
end
- def self.create(connection, table_joins)
- if table_joins.empty?
- empty connection
+ def self.create_with_joins(connection, initial_table, joins, type_caster)
+ if joins.empty?
+ create(connection, initial_table, type_caster)
else
- aliases = Hash.new { |h,k|
- h[k] = initial_count_for(connection, k, table_joins)
+ aliases = Hash.new { |h, k|
+ h[k] = initial_count_for(connection, k, joins)
}
- new connection, aliases
+ aliases[initial_table] = 1
+ new connection, aliases, type_caster
end
end
@@ -51,45 +54,37 @@ module ActiveRecord
end
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection, aliases)
+ def initialize(connection, aliases, type_caster)
@aliases = aliases
@connection = connection
+ @type_caster = type_caster
end
def aliased_table_for(table_name, aliased_name)
- table_alias = aliased_name_for(table_name, aliased_name)
-
- if table_alias == table_name
- Arel::Table.new(table_name)
- else
- Arel::Table.new(table_name).alias(table_alias)
- end
- end
-
- def aliased_name_for(table_name, aliased_name)
if aliases[table_name].zero?
# If it's zero, we can have our table_name
aliases[table_name] = 1
- table_name
+ Arel::Table.new(table_name, type_caster: @type_caster)
else
# Otherwise, we need to use an alias
- aliased_name = connection.table_alias_for(aliased_name)
+ aliased_name = @connection.table_alias_for(aliased_name)
# Update the count
aliases[aliased_name] += 1
- if aliases[aliased_name] > 1
+ table_alias = if aliases[aliased_name] > 1
"#{truncate(aliased_name)}_#{aliases[aliased_name]}"
else
aliased_name
end
+ Arel::Table.new(table_name, type_caster: @type_caster).alias(table_alias)
end
end
private
def truncate(name)
- name.slice(0, connection.table_alias_length - 2)
+ name.slice(0, @connection.table_alias_length - 2)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index f1c36cd047..0d8e4ba870 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -121,7 +121,7 @@ module ActiveRecord
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
# through association's scope)
def target_scope
- AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
+ AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all)
end
# Loads the \target if needed and returns it.
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index dcbd57e61d..d06b7b3508 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -10,8 +10,8 @@ module ActiveRecord
@block = block
end
- def bind_value(scope, column, value, alias_tracker)
- substitute = alias_tracker.connection.substitute_at(column)
+ def bind_value(scope, column, value, connection)
+ substitute = connection.substitute_at(column)
scope.bind_values += [[column, @block.call(value)]]
substitute
end
@@ -33,10 +33,11 @@ module ActiveRecord
reflection = association.reflection
scope = klass.unscoped
owner = association.owner
- alias_tracker = AliasTracker.empty connection
+ 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, alias_tracker)
+ add_constraints(scope, owner, klass, reflection, connection, chain_head, chain_tail)
end
def join_type
@@ -61,59 +62,43 @@ module ActiveRecord
end
private
-
- def construct_tables(chain, klass, refl, alias_tracker)
- chain.map do |reflection|
- alias_tracker.aliased_table_for(
- table_name_for(reflection, klass, refl),
- table_alias_for(reflection, refl, reflection != refl)
- )
- end
- end
-
- def table_alias_for(reflection, refl, join = false)
- name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
- name << "_join" if join
- name
- end
-
def join(table, constraint)
table.create_join(table, table.create_on(constraint), join_type)
end
- def column_for(table_name, column_name, alias_tracker)
- columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
+ 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, alias_tracker)
- @bind_substitution.bind_value scope, column, value, alias_tracker
+ def bind_value(scope, column, value, connection)
+ @bind_substitution.bind_value scope, column, value, connection
end
- def bind(scope, table_name, column_name, value, tracker)
- column = column_for table_name, column_name, tracker
- bind_value scope, column, value, tracker
+ 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, tracker, assoc_klass)
- join_keys = reflection.join_keys(assoc_klass)
+ def last_chain_scope(scope, table, reflection, owner, connection, 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], tracker
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], connection
scope = scope.where(table[key].eq(bind_val))
if reflection.type
value = owner.class.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
+ bind_val = bind scope, table.table_name, reflection.type, value, connection
scope = scope.where(table[reflection.type].eq(bind_val))
else
scope
end
end
- def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
- join_keys = reflection.join_keys(assoc_klass)
+ def next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection)
+ join_keys = reflection.join_keys(association_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
@@ -121,44 +106,64 @@ module ActiveRecord
if reflection.type
value = next_reflection.klass.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
+ bind_val = bind scope, table.table_name, reflection.type, value, connection
scope = scope.where(table[reflection.type].eq(bind_val))
end
scope = scope.joins(join(foreign_table, constraint))
end
- def add_constraints(scope, owner, assoc_klass, refl, tracker)
- chain = refl.chain
- scope_chain = refl.scope_chain
+ class ReflectionProxy < SimpleDelegator # :nodoc:
+ attr_accessor :next
+ attr_reader :alias_name
+
+ def initialize(reflection, alias_name)
+ super(reflection)
+ @alias_name = alias_name
+ end
+
+ def all_includes; nil; end
+ end
- tables = construct_tables(chain, assoc_klass, refl, tracker)
+ def get_chain(reflection, association, tracker)
+ name = reflection.name
+ runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
+ previous_reflection = runtime_reflection
+ reflection.chain.drop(1).each do |refl|
+ alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name))
+ proxy = ReflectionProxy.new(refl, alias_name)
+ previous_reflection.next = proxy
+ previous_reflection = proxy
+ end
+ [runtime_reflection, previous_reflection]
+ end
- owner_reflection = chain.last
- table = tables.last
- scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
+ def add_constraints(scope, owner, association_klass, refl, connection, 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)
- chain.each_with_index do |reflection, i|
- table, foreign_table = tables.shift, tables.first
+ reflection = chain_head
+ loop do
+ break unless reflection
+ table = reflection.alias_name
- unless reflection == chain.last
- next_reflection = chain[i + 1]
- scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
+ 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)
end
- is_first_chain = i == 0
- klass = is_first_chain ? assoc_klass : reflection.klass
-
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
- scope_chain[i].each do |scope_chain_item|
- item = eval_scope(klass, scope_chain_item, owner)
+ reflection.constraints.each do |scope_chain_item|
+ item = eval_scope(reflection.klass, scope_chain_item, owner)
if scope_chain_item == refl.scope
scope.merge! item.except(:where, :includes, :bind)
end
- if is_first_chain
+ reflection.all_includes do
scope.includes! item.includes_values
end
@@ -166,26 +171,13 @@ module ActiveRecord
scope.bind_values += item.bind_values
scope.order_values |= item.order_values
end
+
+ reflection = reflection.next
end
scope
end
- def alias_suffix(refl)
- refl.name
- end
-
- def table_name_for(reflection, klass, refl)
- if reflection == refl
- # If this is a polymorphic belongs_to, we want to get the klass from the
- # association because it depends on the polymorphic_type attribute of
- # the owner
- klass.table_name
- else
- reflection.table_name
- end
- end
-
def eval_scope(klass, scope, owner)
klass.unscoped.instance_exec(owner, &scope)
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 81fdd681de..c63b42e2a0 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -73,11 +73,11 @@ module ActiveRecord
# Checks whether record is different to the current target, without loading it
def different_target?(record)
- record.id != owner[reflection.foreign_key]
+ record.id != owner._read_attribute(reflection.foreign_key)
end
def replace_keys(record)
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
end
def remove_keys
@@ -85,7 +85,7 @@ module ActiveRecord
end
def foreign_key_present?
- owner[reflection.foreign_key]
+ owner._read_attribute(reflection.foreign_key)
end
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
@@ -99,12 +99,13 @@ module ActiveRecord
if options[:primary_key]
owner.send(reflection.name).try(:id)
else
- owner[reflection.foreign_key]
+ owner._read_attribute(reflection.foreign_key)
end
end
def stale_state
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
+ result = owner._read_attribute(reflection.foreign_key)
+ result && result.to_s
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 947d61ee7b..88406740d8 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/attribute_accessors'
-
# This is the parent Association class which defines the variables
# used by all associations.
#
@@ -15,15 +13,10 @@ module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
attr_accessor :extensions
- # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
- # We can move it to a constant in 5.0.
- attr_accessor :valid_options
end
self.extensions = []
- self.valid_options = [:class_name, :class, :foreign_key, :validate]
-
- attr_reader :name, :scope, :options
+ VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] # :nodoc:
def self.build(model, name, scope, options, &block)
if model.dangerous_attribute_method?(name)
@@ -32,57 +25,60 @@ module ActiveRecord::Associations::Builder
"Please choose a different association name."
end
- builder = create_builder model, name, scope, options, &block
- reflection = builder.build(model)
+ extension = define_extensions model, name, &block
+ reflection = create_reflection model, name, scope, options, extension
define_accessors model, reflection
define_callbacks model, reflection
define_validations model, reflection
- builder.define_extensions model
reflection
end
- def self.create_builder(model, name, scope, options, &block)
+ def self.create_reflection(model, name, scope, options, extension = nil)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
- new(model, name, scope, options, &block)
- end
-
- def initialize(model, name, scope, options)
- # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
if scope.is_a?(Hash)
options = scope
scope = nil
end
- # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
- @name = name
- @scope = scope
- @options = options
+ validate_options(options)
- validate_options
+ scope = build_scope(scope, extension)
+
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ end
+
+ def self.build_scope(scope, extension)
+ new_scope = scope
if scope && scope.arity == 0
- @scope = proc { instance_exec(&scope) }
+ new_scope = proc { instance_exec(&scope) }
+ end
+
+ if extension
+ new_scope = wrap_scope new_scope, extension
end
+
+ new_scope
end
- def build(model)
- ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ def self.wrap_scope(scope, extension)
+ scope
end
- def macro
+ def self.macro
raise NotImplementedError
end
- def valid_options
- Association.valid_options + Association.extensions.flat_map(&:valid_options)
+ def self.valid_options(options)
+ VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
end
- def validate_options
- options.assert_valid_keys(valid_options)
+ def self.validate_options(options)
+ options.assert_valid_keys(valid_options(options))
end
- def define_extensions(model)
+ def self.define_extensions(model, name)
end
def self.define_callbacks(model, reflection)
@@ -133,8 +129,6 @@ module ActiveRecord::Associations::Builder
raise NotImplementedError
end
- private
-
def self.check_dependent_options(dependent)
unless valid_dependent_options.include? dependent
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 954ea3878a..d0ad57f9c6 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,10 +1,10 @@
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:belongs_to
end
- def valid_options
+ def self.valid_options(options)
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
end
@@ -23,8 +23,6 @@ module ActiveRecord::Associations::Builder
add_counter_cache_methods mixin
end
- private
-
def self.add_counter_cache_methods(mixin)
return if mixin.method_defined? :belongs_to_counter_cache_after_update
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index bc15a49996..2ff67f904d 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -7,22 +7,11 @@ module ActiveRecord::Associations::Builder
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
- def valid_options
+ def self.valid_options(options)
super + [:table_name, :before_add,
:after_add, :before_remove, :after_remove, :extend]
end
- attr_reader :block_extension
-
- def initialize(model, name, scope, options)
- super
- @mod = nil
- if block_given?
- @mod = Module.new(&Proc.new)
- @scope = wrap_scope @scope, @mod
- end
- end
-
def self.define_callbacks(model, reflection)
super
name = reflection.name
@@ -32,10 +21,11 @@ module ActiveRecord::Associations::Builder
}
end
- def define_extensions(model)
- if @mod
+ def self.define_extensions(model, name)
+ if block_given?
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- model.parent.const_set(extension_module_name, @mod)
+ extension = Module.new(&Proc.new)
+ model.parent.const_set(extension_module_name, extension)
end
end
@@ -78,9 +68,7 @@ module ActiveRecord::Associations::Builder
CODE
end
- private
-
- def wrap_scope(scope, mod)
+ def self.wrap_scope(scope, mod)
if scope
proc { |owner| instance_exec(owner, &scope).extending(mod) }
else
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 357b28ac94..93dc4ae118 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -87,11 +87,11 @@ module ActiveRecord::Associations::Builder
middle_name = [lhs_model.name.downcase.pluralize,
association_name].join('_').gsub(/::/, '_').to_sym
middle_options = middle_options join_model
- hm_builder = HasMany.create_builder(lhs_model,
- middle_name,
- nil,
- middle_options)
- hm_builder.build lhs_model
+
+ HasMany.create_reflection(lhs_model,
+ middle_name,
+ nil,
+ middle_options)
end
private
@@ -110,7 +110,7 @@ module ActiveRecord::Associations::Builder
rhs_options = {}
if options.key? :class_name
- rhs_options[:foreign_key] = options[:class_name].foreign_key
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
rhs_options[:class_name] = options[:class_name]
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 4c8c826f76..1c1b47bd56 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,11 +1,11 @@
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
- def macro
+ def self.macro
:has_many
end
- def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
+ def self.valid_options(options)
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index c194c8ae9a..64e9e6b334 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,11 +1,11 @@
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:has_one
end
- def valid_options
- valid = super + [:as]
+ def self.valid_options(options)
+ valid = super + [:as, :foreign_type]
valid += [:through, :source, :source_type] if options[:through]
valid
end
@@ -14,8 +14,6 @@ module ActiveRecord::Associations::Builder
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
end
- private
-
def self.add_destroy_callbacks(model, reflection)
super unless reflection.options[:through]
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 6e6dd7204c..1369212837 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -2,7 +2,7 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
- def valid_options
+ def self.valid_options(options)
super + [:dependent, :primary_key, :inverse_of, :required]
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 93f611dd8d..f2c96e9a2a 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -62,9 +62,9 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
pk_type = reflection.primary_key_type
- ids = Array(ids).reject { |id| id.blank? }
+ ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.type_cast_from_user(i) }
- replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
+ replace(klass.find(ids).index_by(&:id).values_at(*ids))
end
def reset
@@ -175,8 +175,8 @@ module ActiveRecord
end
# Removes all records from the association without calling callbacks
- # on the associated records. It honors the `:dependent` option. However
- # if the `:dependent` value is `:destroy` then in that case the `:delete_all`
+ # on the associated records. It honors the +:dependent+ option. However
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
# deletion strategy for the association is applied.
#
# You can force a particular deletion strategy by passing a parameter.
@@ -218,11 +218,7 @@ module ActiveRecord
# Count all records using SQL. Construct options and pass them with
# scope to the target class's +count+.
- def count(column_name = nil, count_options = {})
- # TODO: Remove count_options argument as soon we remove support to
- # activerecord-deprecated_finders.
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
-
+ def count(column_name = nil)
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -289,7 +285,7 @@ module ActiveRecord
elsif !loaded? && !association_scope.group_values.empty?
load_target.size
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
- unsaved_records = target.select { |r| r.new_record? }
+ unsaved_records = target.select(&:new_record?)
unsaved_records.size + count_records
else
count_records
@@ -358,6 +354,7 @@ module ActiveRecord
if owner.new_record?
replace_records(other_array, original_target)
else
+ replace_common_records_in_memory(other_array, original_target)
if other_array != original_target
transaction { replace_records(other_array, original_target) }
end
@@ -385,11 +382,18 @@ module ActiveRecord
target
end
- def add_to_target(record, skip_callbacks = false)
+ def add_to_target(record, skip_callbacks = false, &block)
+ if association_scope.distinct_value
+ index = @target.index(record)
+ end
+ replace_on_target(record, index, skip_callbacks, &block)
+ end
+
+ def replace_on_target(record, index, skip_callbacks)
callback(:before_add, record) unless skip_callbacks
yield(record) if block_given?
- if association_scope.distinct_value && index = @target.index(record)
+ if index
@target[index] = record
else
@target << record
@@ -498,7 +502,7 @@ module ActiveRecord
def delete_or_destroy(records, method)
records = records.flatten
records.each { |record| raise_on_type_mismatch!(record) }
- existing_records = records.reject { |r| r.new_record? }
+ existing_records = records.reject(&:new_record?)
if existing_records.empty?
remove_records(existing_records, records, method)
@@ -534,6 +538,14 @@ module ActiveRecord
target
end
+ def replace_common_records_in_memory(new_target, original_target)
+ common_records = new_target & original_target
+ common_records.each do |record|
+ skip_callbacks = true
+ replace_on_target(record, @target.index(record), skip_callbacks)
+ end
+ end
+
def concat_records(records, should_raise = false)
result = true
@@ -581,8 +593,8 @@ module ActiveRecord
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
assoc = owner.association(reflection.through_reflection.name)
assoc.reader.any? { |source|
- target = source.send(reflection.source_reflection.name)
- target.respond_to?(:include?) ? target.include?(record) : target == record
+ target_reflection = source.send(reflection.source_reflection.name)
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
} || target.include?(record)
else
target.include?(record)
@@ -593,7 +605,7 @@ module ActiveRecord
# specified, then #find scans the entire collection.
def find_by_scan(*args)
expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
+ ids = args.flatten.compact.map(&:to_s).uniq
if ids.size == 1
id = ids.first
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 060b2278d9..c22dc6e11e 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -29,10 +29,11 @@ module ActiveRecord
# instantiation of the actual post records.
class CollectionProxy < Relation
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
+ delegate :find_nth, to: :scope
def initialize(klass, association) #:nodoc:
@association = association
- super klass, klass.arel_table
+ super klass, klass.arel_table, klass.predicate_builder
merge! association.scope(nullify: false)
end
@@ -687,10 +688,8 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- @association.count(column_name, options)
+ def count(column_name = nil)
+ @association.count(column_name)
end
# Returns the size of the collection. If the collection hasn't been loaded,
diff --git a/activerecord/lib/active_record/associations/foreign_association.rb b/activerecord/lib/active_record/associations/foreign_association.rb
new file mode 100644
index 0000000000..fe48ecec29
--- /dev/null
+++ b/activerecord/lib/active_record/associations/foreign_association.rb
@@ -0,0 +1,11 @@
+module ActiveRecord::Associations
+ module ForeignAssociation
+ def foreign_key_present?
+ if reflection.klass.primary_key
+ owner.attribute_present?(reflection.active_record_primary_key)
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 1413efaf7f..2a782c06d0 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -6,6 +6,7 @@ module ActiveRecord
# If the association has a <tt>:through</tt> option further specialization
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < CollectionAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
@@ -16,7 +17,7 @@ module ActiveRecord
unless empty?
record = klass.human_attribute_name(reflection.name).downcase
owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
- false
+ throw(:abort)
end
else
@@ -66,7 +67,7 @@ module ActiveRecord
# the loaded flag is set to true as well.
def count_records
count = if has_cached_counter?
- owner.read_attribute cached_counter_attribute_name
+ owner._read_attribute cached_counter_attribute_name
else
scope.count
end
@@ -153,14 +154,6 @@ module ActiveRecord
end
end
- def foreign_key_present?
- if reflection.klass.primary_key
- owner.attribute_present?(reflection.association_primary_key)
- else
- false
- end
- end
-
def concat_records(records, *)
update_counter_if_success(super, records.length)
end
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 bde23fc116..f1e784d771 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/filters'
-
module ActiveRecord
# = Active Record Has Many Through Association
module Associations
@@ -13,21 +11,6 @@ module ActiveRecord
@through_association = nil
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query
- # if the collection hasn't been loaded, and by calling collection.size if
- # it has. If the collection will likely have a size greater than zero,
- # and if fetching the collection will be needed afterwards, one less
- # SELECT query will be generated by using #length instead.
- def size
- if has_cached_counter?
- owner.read_attribute cached_counter_attribute_name(reflection)
- elsif loaded?
- target.size
- else
- super
- end
- end
-
def concat(*records)
unless owner.new_record?
records.flatten.each do |record|
@@ -64,16 +47,7 @@ module ActiveRecord
end
save_through_record(record)
- if has_cached_counter? && !through_reflection_updates_counter_cache?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Automatic updating of counter caches on through associations has been
- deprecated, and will be removed in Rails 5. Instead, please set the
- appropriate `counter_cache` options on the `has_many` and `belongs_to`
- for your associations to #{through_reflection.name}.
- MSG
- update_counter_in_database(1)
- end
record
end
@@ -161,13 +135,11 @@ module ActiveRecord
if scope.klass.primary_key
count = scope.destroy_all.length
else
- scope.each do |record|
- record._run_destroy_callbacks
- end
+ scope.each(&:_run_destroy_callbacks)
arel = scope.arel
- stmt = Arel::DeleteManager.new arel.engine
+ stmt = Arel::DeleteManager.new
stmt.from scope.klass.arel_table
stmt.wheres = arel.constraints
@@ -228,11 +200,6 @@ module ActiveRecord
def invertible_for?(record)
false
end
-
- def through_reflection_updates_counter_cache?
- counter_name = cached_counter_attribute_name
- inverse_updates_counter_named?(counter_name, through_reflection)
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index e6095d84dc..41a75b820e 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -2,6 +2,7 @@ module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < SingularAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
@@ -12,7 +13,7 @@ module ActiveRecord
if load_target
record = klass.human_attribute_name(reflection.name).downcase
owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
- false
+ throw(:abort)
end
else
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index c5c4edd090..4b75370171 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
def columns
- @tables.flat_map { |t| t.column_aliases }
+ @tables.flat_map(&:column_aliases)
end
# An array of [column_name, alias] pairs for the table
@@ -93,8 +93,7 @@ module ActiveRecord
# joins # => []
#
def initialize(base, associations, joins)
- @alias_tracker = AliasTracker.create(base.connection, joins)
- @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
+ @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -257,6 +256,7 @@ module ActiveRecord
construct(model, node, row, rs, seen, model_cache, aliases)
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
+ model.readonly!
seen[parent.base_klass][primary_id][node.base_klass][id] = model
construct(model, node, row, rs, seen, model_cache, aliases)
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 5dede5527d..c1ef86a95b 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -43,16 +43,23 @@ module ActiveRecord
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
+ predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
scope_chain_items = scope_chain[scope_chain_index].map do |item|
if item.is_a?(Relation)
item
else
- ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
+ ActiveRecord::Relation.create(klass, table, predicate_builder)
+ .instance_exec(node, &item)
end
end
scope_chain_index += 1
- scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
+ relation = ActiveRecord::Relation.create(
+ klass,
+ table,
+ predicate_builder,
+ )
+ scope_chain_items.concat [klass.send(:build_default_scope, relation)].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 46bccbf15a..4358f3b581 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -178,6 +178,7 @@ module ActiveRecord
class NullPreloader
def self.new(klass, owners, reflection, preload_scope); self; end
def self.run(preloader); end
+ def self.preloaded_records; []; end
end
def preloader_for(reflection, owners, rhs_klass)
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 496c426986..afcaa5d55a 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -33,7 +33,7 @@ module ActiveRecord
end
def query_scope(ids)
- scope.where(association_key.in(ids))
+ scope.where(association_key_name => ids)
end
def table
@@ -104,11 +104,11 @@ module ActiveRecord
end
def association_key_type
- @klass.type_for_attribute(association_key_name.to_s).type
+ @klass.column_for_attribute(association_key_name).type
end
def owner_key_type
- @model.type_for_attribute(owner_key_name.to_s).type
+ @model.column_for_attribute(owner_key_name).type
end
def load_slices(slices)
@@ -142,14 +142,8 @@ module ActiveRecord
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
-
- if preload_values.key? :order
- scope.order! preload_values[:order]
- else
- if values.key? :order
- scope.order! values[:order]
- end
- end
+ scope.joins! preload_values[:joins] || values[:joins]
+ scope.order! preload_values[:order] || values[:order]
if preload_values[:readonly] || values[:readonly]
scope.readonly!
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index 7b37b5942d..2029871f39 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -8,7 +8,7 @@ module ActiveRecord
records_by_owner = super
if reflection_scope.distinct_value
- records_by_owner.each_value { |records| records.uniq! }
+ records_by_owner.each_value(&:uniq!)
end
records_by_owner
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index e47e81aa0f..09828dbd9b 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -91,6 +91,17 @@ module ActiveRecord
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
end
end
+
+ def build_record(attributes)
+ inverse = source_reflection.inverse_of
+ target = through_association.target
+
+ if inverse && target && !target.is_a?(Array)
+ attributes[inverse.foreign_key] = target.id
+ end
+
+ super(attributes)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 8cc1904575..88536eaac0 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -9,6 +9,10 @@ module ActiveRecord
FromUser.new(name, value, type)
end
+ def with_cast_value(name, value, type)
+ WithCastValue.new(name, value, type)
+ end
+
def null(name)
Null.new(name)
end
@@ -58,6 +62,10 @@ module ActiveRecord
self.class.from_database(name, value, type)
end
+ def with_cast_value(value)
+ self.class.with_cast_value(name, value, type)
+ end
+
def type_cast(*)
raise NotImplementedError
end
@@ -93,6 +101,16 @@ module ActiveRecord
end
end
+ class WithCastValue < Attribute # :nodoc:
+ def type_cast(value)
+ value
+ end
+
+ def changed_in_place_from?(old_value)
+ false
+ end
+ end
+
class Null < Attribute # :nodoc:
def initialize(name)
super(name, nil, Type::Value.new)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 2887db3bf7..bf64830417 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -87,7 +87,7 @@ module ActiveRecord
end
end
unless errors.empty?
- error_descriptions = errors.map { |ex| ex.message }.join(",")
+ error_descriptions = errors.map(&:message).join(",")
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 34ec397aee..8f165fb1dc 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -150,7 +150,7 @@ module ActiveRecord
BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
end
- def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
if klass.respond_to?(name, true)
if superklass.respond_to?(name, true)
klass.method(name).owner != superklass.method(name).owner
@@ -192,7 +192,8 @@ module ActiveRecord
end
# Returns the column object for the named attribute.
- # Returns nil if the named attribute does not exist.
+ # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
+ # named attribute does not exist.
#
# class Person < ActiveRecord::Base
# end
@@ -202,23 +203,18 @@ module ActiveRecord
# # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
- # # => nil
+ # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
def column_for_attribute(name)
- column = columns_hash[name.to_s]
- if column.nil?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#column_for_attribute` will return a null object for non-existent
- columns in Rails 5. Use `#has_attribute?` if you need to check for
- an attribute's existence.
- MSG
+ name = name.to_s
+ columns_hash.fetch(name) do
+ ConnectionAdapters::NullColumn.new(name)
end
- column
end
end
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
- # which will all return +true+. It also define the attribute methods if they have
+ # which will all return +true+. It also defines the attribute methods if they have
# not been generated.
#
# class Person < ActiveRecord::Base
@@ -332,7 +328,7 @@ module ActiveRecord
# task.attribute_present?(:title) # => true
# task.attribute_present?(:is_done) # => true
def attribute_present?(attribute)
- value = read_attribute(attribute)
+ value = _read_attribute(attribute)
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
end
@@ -433,7 +429,7 @@ module ActiveRecord
end
def typecasted_attribute_value(name)
- read_attribute(name)
+ _read_attribute(name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 2f02738f6d..d5702accaf 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -69,8 +69,17 @@ module ActiveRecord
end
end
+ def attribute_changed_in_place?(attr_name)
+ old_value = original_raw_attribute(attr_name)
+ @attributes[attr_name].changed_in_place_from?(old_value)
+ end
+
private
+ def changes_include?(attr_name)
+ super || attribute_changed_in_place?(attr_name)
+ end
+
def calculate_changes_from_defaults
@changed_attributes = nil
self.class.column_defaults.each do |attr, orig_value|
@@ -110,7 +119,7 @@ module ActiveRecord
if attribute_changed?(attr)
changed_attributes[attr]
else
- clone_attribute_value(:read_attribute, attr)
+ clone_attribute_value(:_read_attribute, attr)
end
end
@@ -141,15 +150,10 @@ module ActiveRecord
def changed_in_place
self.class.attribute_names.select do |attr_name|
- changed_in_place?(attr_name)
+ attribute_changed_in_place?(attr_name)
end
end
- def changed_in_place?(attr_name)
- old_value = original_raw_attribute(attr_name)
- @attributes[attr_name].changed_in_place_from?(old_value)
- end
-
def original_raw_attribute(attr_name)
original_raw_attributes.fetch(attr_name) do
read_attribute_before_type_cast(attr_name)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 104d84a1f8..c28374e4ab 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -17,7 +17,7 @@ module ActiveRecord
def id
if pk = self.class.primary_key
sync_with_transaction_state
- read_attribute(pk)
+ _read_attribute(pk)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 55beb309ca..4b72fe7d7e 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/method_transplanting'
-
module ActiveRecord
module AttributeMethods
module Read
@@ -27,7 +25,7 @@ module ActiveRecord
<<-EOMETHOD
def #{method_name}
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
- read_attribute(name) { |n| missing_attribute(n, caller) }
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
end
EOMETHOD
end
@@ -36,42 +34,24 @@ module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
- [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
- define_method method_name do |*|
- cached_attributes_deprecation_warning(method_name)
- true
- end
- end
-
protected
- def cached_attributes_deprecation_warning(method_name)
- ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
- end
+ def define_method_attribute(name)
+ safe_name = name.unpack('h*').first
+ temp_method = "__temp__#{safe_name}"
- if Module.methods_transplantable?
- def define_method_attribute(name)
- method = ReaderMethodCache[name]
- generated_attribute_methods.module_eval { define_method name, method }
- end
- else
- def define_method_attribute(name)
- safe_name = name.unpack('h*').first
- temp_method = "__temp__#{safe_name}"
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
-
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def #{temp_method}
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- read_attribute(name) { |n| missing_attribute(n, caller) }
- end
- STR
-
- generated_attribute_methods.module_eval do
- alias_method name, temp_method
- undef_method temp_method
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def #{temp_method}
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
end
+ STR
+
+ generated_attribute_methods.module_eval do
+ alias_method name, temp_method
+ undef_method temp_method
end
end
end
@@ -84,13 +64,19 @@ module ActiveRecord
def read_attribute(attr_name, &block)
name = attr_name.to_s
name = self.class.primary_key if name == ID
- @attributes.fetch_value(name, &block)
+ _read_attribute(name, &block)
+ end
+
+ # 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? }
end
private
def attribute(attribute_name)
- read_attribute(attribute_name)
+ _read_attribute(attribute_name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index e5ec5ddca5..d0d8a968c5 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/filters'
-
module ActiveRecord
module AttributeMethods
module Serialization
@@ -51,19 +49,6 @@ module ActiveRecord
Type::Serialized.new(type, coder)
end
end
-
- def serialized_attributes
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `serialized_attributes` is deprecated without replacement, and will
- be removed in Rails 5.0.
- MSG
-
- @serialized_attributes ||= Hash[
- columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
- [c.name, c.cast_type.coder]
- }
- ]
- end
end
end
end
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 33d9d2002c..777f7ab4d7 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -12,7 +12,11 @@ module ActiveRecord
if value.is_a?(Array)
value.map { |v| type_cast_from_user(v) }
elsif value.respond_to?(:in_time_zone)
- value.in_time_zone
+ begin
+ value.in_time_zone || super
+ rescue ArgumentError
+ nil
+ end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index b3c8209a74..ab017c7b54 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/method_transplanting'
-
module ActiveRecord
module AttributeMethods
module Write
@@ -25,27 +23,18 @@ module ActiveRecord
module ClassMethods
protected
- if Module.methods_transplantable?
- def define_method_attribute=(name)
- method = WriterMethodCache[name]
- generated_attribute_methods.module_eval {
- define_method "#{name}=", method
- }
- end
- else
- def define_method_attribute=(name)
- safe_name = name.unpack('h*').first
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+ def define_method_attribute=(name)
+ safe_name = name.unpack('h*').first
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}=(value)
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- write_attribute(name, value)
- end
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
- undef_method :__temp__#{safe_name}=
- STR
- end
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__#{safe_name}=(value)
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ write_attribute(name, value)
+ end
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
+ undef_method :__temp__#{safe_name}=
+ STR
end
end
@@ -73,7 +62,7 @@ module ActiveRecord
if should_type_cast
@attributes.write_from_user(attr_name, value)
else
- @attributes.write_from_database(attr_name, value)
+ @attributes.write_cast_value(attr_name, value)
end
value
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 21c58cbf1d..66fcaf6945 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -27,8 +27,8 @@ module ActiveRecord
attributes.initialized_keys
end
- def fetch_value(name, &block)
- self[name].value(&block)
+ def fetch_value(name)
+ self[name].value { |n| yield n if block_given? }
end
def write_from_database(name, value)
@@ -39,6 +39,10 @@ module ActiveRecord
attributes[name] = self[name].with_value_from_user(value)
end
+ def write_cast_value(name, value)
+ attributes[name] = self[name].with_cast_value(value)
+ end
+
def freeze
@attributes.freeze
super
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 05138ae36d..3a76f5262d 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -76,7 +76,9 @@ module ActiveRecord
unless @materialized
values.each_key { |key| self[key] }
types.each_key { |key| self[key] }
- @materialized = true
+ unless frozen?
+ @materialized = true
+ end
end
delegate_hash
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 3288108a6a..aafb990bc1 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -53,9 +53,9 @@ module ActiveRecord
# 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
+ # 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
+ # +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.
#
# class MoneyType < ActiveRecord::Type::Integer
@@ -122,6 +122,7 @@ module ActiveRecord
end
def clear_caches_calculated_from_columns
+ @arel_table = nil
@attributes_builder = nil
@column_names = nil
@column_types = nil
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index a0d70435fa..fa6c5e9e8c 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -200,13 +200,19 @@ module ActiveRecord
after_create save_method
after_update save_method
else
- define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
+ define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
before_save save_method
end
if reflection.validate? && !method_defined?(validation_method)
method = (collection ? :validate_collection_association : :validate_single_association)
- define_non_cyclic_method(validation_method) { send(method, reflection) }
+ define_non_cyclic_method(validation_method) do
+ send(method, reflection)
+ # TODO: remove the following line as soon as the return value of
+ # callbacks is ignored, that is, returning `false` does not
+ # display a deprecation warning or halts the callback chain.
+ true
+ end
validate validation_method
end
end
@@ -263,9 +269,9 @@ module ActiveRecord
if new_record
association && association.target
elsif autosave
- association.target.find_all { |record| record.changed_for_autosave? }
+ association.target.find_all(&:changed_for_autosave?)
else
- association.target.find_all { |record| record.new_record? }
+ association.target.find_all(&:new_record?)
end
end
@@ -275,7 +281,7 @@ module ActiveRecord
self.class._reflections.values.any? do |reflection|
if reflection.options[:autosave]
association = association_instance_get(reflection.name)
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ association && Array.wrap(association.target).any?(&:changed_for_autosave?)
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index f978fbd0a4..100d3780f6 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -22,6 +22,7 @@ require 'active_record/log_subscriber'
require 'active_record/explain_subscriber'
require 'active_record/relation/delegation'
require 'active_record/attributes'
+require 'active_record/type_caster'
module ActiveRecord #:nodoc:
# = Active Record
@@ -141,7 +142,7 @@ module ActiveRecord #:nodoc:
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
# Query methods allow you to test whether an attribute value is present.
- # For numeric values, present is defined as non-zero.
+ # Additionally, when dealing with numeric values, a query method will return false if the value is zero.
#
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
# to determine whether the user has a name:
@@ -311,6 +312,7 @@ module ActiveRecord #:nodoc:
include Reflection
include Serialization
include Store
+ include SecureToken
end
ActiveSupport.run_load_hooks(:active_record, Base)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 523d492a48..f44e5af5de 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -192,14 +192,14 @@ module ActiveRecord
#
# == <tt>before_validation*</tt> returning statements
#
- # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
+ # If the +before_validation+ callback throws +:abort+, the process will be
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
# ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
#
# == Canceling callbacks
#
- # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
- # cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
+ # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
+ # the associated action are cancelled.
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
# methods on the model, which are called last.
#
@@ -298,7 +298,7 @@ module ActiveRecord
private
- def create_or_update #:nodoc:
+ def create_or_update(*) #:nodoc:
_run_save_callbacks { super }
end
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index d3d7396c91..9ea22ed798 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -8,6 +8,7 @@ module ActiveRecord
def initialize(object_class = Object)
@object_class = object_class
+ check_arity_of_constructor
end
def dump(obj)
@@ -33,6 +34,16 @@ module ActiveRecord
obj
end
+
+ private
+
+ def check_arity_of_constructor
+ begin
+ load(nil)
+ rescue ArgumentError
+ raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 46812b75bb..1371317e3c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -2,7 +2,6 @@ require 'thread'
require 'thread_safe'
require 'monitor'
require 'set'
-require 'active_support/core_ext/string/filters'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -122,13 +121,13 @@ module ActiveRecord
# greater than the number of threads currently waiting (that
# is, don't jump ahead in line). Otherwise, return nil.
#
- # If +timeout+ is given, block if it there is no element
+ # If +timeout+ is given, block if there is no element
# available, waiting up to +timeout+ seconds for an element to
# become available.
#
# Raises:
# - ConnectionTimeoutError if +timeout+ is given and no element
- # becomes available after +timeout+ seconds,
+ # becomes available within +timeout+ seconds,
def poll(timeout = nil)
synchronize do
if timeout
@@ -151,7 +150,7 @@ module ActiveRecord
end
# A thread can remove an element from the queue without
- # waiting if an only if the number of currently available
+ # waiting if and only if the number of currently available
# connections is strictly greater than the number of waiting
# threads.
def can_remove_no_wait?
@@ -236,7 +235,7 @@ module ActiveRecord
@spec = spec
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
- @reaper = Reaper.new self, spec.config[:reaping_frequency]
+ @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
@reaper.run
# default max pool size to 5
@@ -320,9 +319,7 @@ module ActiveRecord
checkin conn
conn.disconnect! if conn.requires_reloading?
end
- @connections.delete_if do |conn|
- conn.requires_reloading?
- end
+ @connections.delete_if(&:requires_reloading?)
@available.clear
@connections.each do |conn|
@available.add conn
@@ -365,7 +362,7 @@ module ActiveRecord
conn.expire
end
- release owner
+ release conn, owner
@available.add conn
end
@@ -378,7 +375,7 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- release conn.owner
+ release conn, conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
@@ -426,10 +423,12 @@ module ActiveRecord
end
end
- def release(owner)
+ def release(conn, owner)
thread_id = owner.object_id
- @reserved_connections.delete thread_id
+ if @reserved_connections[thread_id] == conn
+ @reserved_connections.delete thread_id
+ end
end
def new_connection
@@ -517,15 +516,7 @@ module ActiveRecord
def connection_pool_list
owner_to_pool.values.compact
end
-
- def connection_pools
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- In the next release, this will return the same as `#connection_pool_list`.
- (An array of pools, rather than a hash mapping specs to pools.)
- MSG
-
- Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
- end
+ alias :connection_pools :connection_pool_list
def establish_connection(owner, spec)
@class_to_pool.clear
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 12b16b2473..59cdd8e98c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -258,7 +258,18 @@ module ActiveRecord
# Rolls back the transaction (and turns on auto-committing). Must be
# done if the transaction block raises an exception or returns false.
- def rollback_db_transaction() end
+ def rollback_db_transaction
+ exec_rollback_db_transaction
+ end
+
+ def exec_rollback_db_transaction() end #:nodoc:
+
+ def rollback_to_savepoint(name = nil)
+ exec_rollback_to_savepoint(name)
+ end
+
+ def exec_rollback_to_savepoint(name = nil) #:nodoc:
+ end
def default_sequence_name(table, column)
nil
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 4a4506c7f5..5e27cfe507 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module QueryCache
class << self
def included(base) #:nodoc:
- dirties_query_cache base, :insert, :update, :delete
+ dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction
end
def dirties_query_cache(base, *method_names)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 679878d860..143d7d9574 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -19,7 +19,7 @@ module ActiveRecord
# Cast a +value+ to a type that the database understands. For example,
# SQLite does not understand dates, so this method will convert a Date
# to a String.
- def type_cast(value, column)
+ def type_cast(value, column = nil)
if value.respond_to?(:quoted_id) && value.respond_to?(:id)
return value.id
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
index 25c17ce971..c0662f8473 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -9,7 +9,7 @@ module ActiveRecord
execute("SAVEPOINT #{name}")
end
- def rollback_to_savepoint(name = current_savepoint_name)
+ def exec_rollback_to_savepoint(name = current_savepoint_name)
execute("ROLLBACK TO SAVEPOINT #{name}")
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 6bab260f5a..db20b60d60 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -15,9 +15,7 @@ module ActiveRecord
end
def visit_AddColumn(o)
- sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
- sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(sql, column_options(o))
+ "ADD #{accept(o)}"
end
private
@@ -30,9 +28,9 @@ module ActiveRecord
end
def visit_ColumnDefinition(o)
- sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
- column_sql = "#{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(column_sql, column_options(o)) unless o.primary_key?
+ 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
end
@@ -67,6 +65,8 @@ module ActiveRecord
column_options[:column] = o
column_options[:first] = o.first
column_options[:after] = o.after
+ column_options[:auto_increment] = o.auto_increment
+ column_options[:primary_key] = o.primary_key
column_options
end
@@ -83,7 +83,7 @@ module ActiveRecord
end
def add_column_options!(sql, options)
- sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(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
if options[:null] == false
sql << " NOT NULL"
@@ -91,14 +91,15 @@ module ActiveRecord
if options[:auto_increment] == true
sql << " AUTO_INCREMENT"
end
+ if options[:primary_key] == true
+ sql << " PRIMARY KEY"
+ end
sql
end
- def quote_value(value, column)
- column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
- column.cast_type ||= type_for_column(column)
-
- @conn.quote(value, column)
+ 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)
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 5c4c214385..7eaa89c9a7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,14 +15,14 @@ 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, :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, :cast_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
end
end
- class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc:
+ class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
end
class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
@@ -56,15 +56,81 @@ module ActiveRecord
end
end
- module TimestampDefaultDeprecation # :nodoc:
- def emit_warning_if_null_unspecified(options)
- return if options.key?(:null)
+ class ReferenceDefinition # :nodoc:
+ def initialize(
+ name,
+ polymorphic: false,
+ index: false,
+ foreign_key: false,
+ type: :integer,
+ **options
+ )
+ @name = name
+ @polymorphic = polymorphic
+ @index = index
+ @foreign_key = foreign_key
+ @type = type
+ @options = options
+
+ if polymorphic && foreign_key
+ raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
+ end
+ end
+
+ def add_to(table)
+ columns.each do |column_options|
+ table.column(*column_options)
+ end
+
+ if index
+ table.index(column_names, index_options)
+ end
+
+ if foreign_key
+ table.foreign_key(foreign_table_name, foreign_key_options)
+ end
+ end
+
+ protected
+
+ attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options
+
+ private
+
+ def as_options(value, default = {})
+ if value.is_a?(Hash)
+ value
+ else
+ default
+ end
+ end
+
+ def polymorphic_options
+ as_options(polymorphic, options)
+ end
+
+ def index_options
+ as_options(index)
+ end
+
+ def foreign_key_options
+ as_options(foreign_key)
+ end
+
+ def columns
+ result = [["#{name}_id", type, options]]
+ if polymorphic
+ result.unshift(["#{name}_type", :string, polymorphic_options])
+ end
+ result
+ end
+
+ def column_names
+ columns.map(&:first)
+ end
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#timestamp` was called without specifying an option for `null`. In Rails 5,
- this behavior will change to `null: false`. You should manually specify
- `null: true` to prevent the behavior of your existing migrations from changing.
- MSG
+ def foreign_table_name
+ name.to_s.pluralize
end
end
@@ -89,16 +155,15 @@ module ActiveRecord
# The table definitions
# The Columns are stored as a ColumnDefinition in the +columns+ attribute.
class TableDefinition
- include TimestampDefaultDeprecation
-
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as
+ attr_reader :name, :temporary, :options, :as, :foreign_keys
def initialize(types, name, temporary, options, as = nil)
@columns_hash = {}
@indexes = {}
+ @foreign_keys = {}
@native = types
@temporary = temporary
@options = options
@@ -226,7 +291,7 @@ module ActiveRecord
# t.integer :shop_id, :creator_id
# t.string :item_number, index: true
# t.string :name, :value, default: "Untitled"
- # t.timestamps
+ # t.timestamps null: false
# end
#
# There's a short-hand method for each of the type values declared at the top. And then there's
@@ -286,33 +351,37 @@ module ActiveRecord
indexes[column_name] = options
end
+ def foreign_key(table_name, options = {}) # :nodoc:
+ foreign_keys[table_name] = options
+ end
+
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
- # <tt>:updated_at</tt> to the table.
+ # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps
+ #
+ # t.timestamps null: false
def timestamps(*args)
options = args.extract_options!
- emit_warning_if_null_unspecified(options)
+
+ options[:null] = false if options[:null].nil?
+
column(:created_at, :datetime, options)
column(:updated_at, :datetime, options)
end
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
- # by default, the <tt>:type</tt> option can be used to specify a different type.
+ # Adds a reference. Optionally adds a +type+ column, if the
+ # +:polymorphic+ option is provided. +references+ and +belongs_to+
+ # are acceptable. The reference column will be an +integer+ by default,
+ # the +:type+ option can be used to specify a different type. A foreign
+ # key will be created if the +:foreign_key+ option is passed.
#
# t.references(:user)
# t.references(:user, type: "string")
# t.belongs_to(:supplier, polymorphic: true)
#
# See SchemaStatements#add_reference
- def references(*args)
- options = args.extract_options!
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- type = options.delete(:type) || :integer
+ def references(*args, **options)
args.each do |col|
- column("#{col}_id", type, options)
- column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
+ ReferenceDefinition.new(col, **options).add_to(self)
end
end
alias :belongs_to :references
@@ -331,6 +400,7 @@ module ActiveRecord
column.null = options[:null]
column.first = options[:first]
column.after = options[:after]
+ column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column
end
@@ -412,8 +482,6 @@ module ActiveRecord
# end
#
class Table
- include TimestampDefaultDeprecation
-
attr_reader :name
def initialize(table_name, base)
@@ -422,33 +490,36 @@ module ActiveRecord
end
# Adds a new column to the named table.
- # See TableDefinition#column for details of the options you can use.
#
- # ====== Creating a simple column
# t.column(:name, :string)
+ #
+ # See TableDefinition#column for details of the options you can use.
def column(column_name, type, options = {})
@base.add_column(name, column_name, type, options)
end
- # Checks to see if a column exists. See SchemaStatements#column_exists?
+ # Checks to see if a column exists.
+ #
+ # See SchemaStatements#column_exists?
def column_exists?(column_name, type = nil, options = {})
@base.column_exists?(name, column_name, type, options)
end
# Adds a new index to the table. +column_name+ can be a single Symbol, or
- # an Array of Symbols. See SchemaStatements#add_index
+ # an Array of Symbols.
#
- # ====== Creating a simple index
# t.index(:name)
- # ====== Creating a unique index
# t.index([:branch_id, :party_id], unique: true)
- # ====== Creating a named index
# t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
+ #
+ # See SchemaStatements#add_index for details of the options you can use.
def index(column_name, options = {})
@base.add_index(name, column_name, options)
end
- # Checks to see if an index exists. See SchemaStatements#index_exists?
+ # Checks to see if an index exists.
+ #
+ # See SchemaStatements#index_exists?
def index_exists?(column_name, options = {})
@base.index_exists?(name, column_name, options)
end
@@ -456,31 +527,37 @@ module ActiveRecord
# Renames the given index on the table.
#
# t.rename_index(:user_id, :account_id)
+ #
+ # See SchemaStatements#rename_index
def rename_index(index_name, new_index_name)
@base.rename_index(name, index_name, new_index_name)
end
- # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
+ # Adds timestamps (+created_at+ and +updated_at+) columns to the table.
#
- # t.timestamps
+ # t.timestamps(null: false)
+ #
+ # See SchemaStatements#add_timestamps
def timestamps(options = {})
- emit_warning_if_null_unspecified(options)
@base.add_timestamps(name, options)
end
# Changes the column's definition according to the new options.
- # See TableDefinition#column for details of the options you can use.
#
# t.change(:name, :string, limit: 80)
# t.change(:description, :text)
+ #
+ # See TableDefinition#column for details of the options you can use.
def change(column_name, type, options = {})
@base.change_column(name, column_name, type, options)
end
- # Sets a new default value for a column. See SchemaStatements#change_column_default
+ # Sets a new default value for a column.
#
# t.change_default(:qualification, 'new')
# t.change_default(:authorized, 1)
+ #
+ # See SchemaStatements#change_column_default
def change_default(column_name, default)
@base.change_column_default(name, column_name, default)
end
@@ -489,20 +566,19 @@ module ActiveRecord
#
# t.remove(:qualification)
# t.remove(:qualification, :experience)
+ #
+ # See SchemaStatements#remove_columns
def remove(*column_names)
@base.remove_columns(name, *column_names)
end
# Removes the given index from the table.
#
- # ====== Remove the index_table_name_on_column in the table_name table
- # t.remove_index :column
- # ====== Remove the index named index_table_name_on_branch_id in the table_name table
- # t.remove_index column: :branch_id
- # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
- # t.remove_index column: [:branch_id, :party_id]
- # ====== Remove the index named by_branch_party in the table_name table
- # t.remove_index name: :by_branch_party
+ # t.remove_index(:branch_id)
+ # t.remove_index(column: [:branch_id, :party_id])
+ # t.remove_index(name: :by_branch_party)
+ #
+ # See SchemaStatements#remove_index
def remove_index(options = {})
@base.remove_index(name, options)
end
@@ -510,24 +586,28 @@ module ActiveRecord
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
#
# t.remove_timestamps
- def remove_timestamps
- @base.remove_timestamps(name)
+ #
+ # See SchemaStatements#remove_timestamps
+ def remove_timestamps(options = {})
+ @base.remove_timestamps(name, options)
end
# Renames a column.
#
# t.rename(:description, :name)
+ #
+ # See SchemaStatements#rename_column
def rename(column_name, new_column_name)
@base.rename_column(name, column_name, new_column_name)
end
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
- # by default, the <tt>:type</tt> option can be used to specify a different type.
+ # Adds a reference. Optionally adds a +type+ column, if
+ # <tt>:polymorphic</tt> option is provided.
#
# t.references(:user)
# t.references(:user, type: "string")
# t.belongs_to(:supplier, polymorphic: true)
+ # t.belongs_to(:supplier, foreign_key: true)
#
# See SchemaStatements#add_reference
def references(*args)
@@ -539,7 +619,6 @@ module ActiveRecord
alias :belongs_to :references
# Removes a reference. Optionally removes a +type+ column.
- # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
#
# t.remove_references(:user)
# t.remove_belongs_to(:supplier, polymorphic: true)
@@ -553,10 +632,12 @@ module ActiveRecord
end
alias :remove_belongs_to :remove_references
- # Adds a column or columns of a specified type
+ # Adds a column or columns of a specified type.
#
# t.string(:goat)
# t.string(:goat, :sheep)
+ #
+ # See SchemaStatements#add_column
[:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
define_method column_type do |*args|
options = args.extract_options!
@@ -566,6 +647,10 @@ module ActiveRecord
end
end
+ def foreign_key(*args) # :nodoc:
+ @base.add_foreign_key(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 6eab11b88b..42ea599a74 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -6,22 +6,28 @@ module ActiveRecord
# We can then redefine how certain data types may be handled in the schema dumper on the
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
- def column_spec(column, types)
- spec = prepare_column_options(column, types)
+ def column_spec(column)
+ spec = prepare_column_options(column)
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")}
spec
end
+ def column_spec_for_primary_key(column)
+ return if column.type == :integer
+ spec = { id: column.type.inspect }
+ spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) })
+ end
+
# This can be overridden on a Adapter level basis to support other
# extended datatypes (Example: Adding an array option in the
# PostgreSQLAdapter)
- def prepare_column_options(column, types)
+ def prepare_column_options(column)
spec = {}
spec[:name] = column.name.inspect
spec[:type] = column.type.to_s
spec[:null] = 'false' unless column.null
- limit = column.limit || types[column.type][:limit]
+ limit = column.limit || native_database_types[column.type][:limit]
spec[:limit] = limit.inspect if limit
spec[:precision] = column.precision.inspect if column.precision
spec[:scale] = column.scale.inspect if column.scale
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 cbf87df356..0f44c332ae 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -132,6 +132,7 @@ module ActiveRecord
# Make a temporary table.
# [<tt>:force</tt>]
# Set to true to drop the table before creating it.
+ # Set to +:cascade+ to drop dependent objects as well.
# Defaults to false.
# [<tt>:as</tt>]
# SQL to use to generate the table. When this option is used, the block is
@@ -203,7 +204,17 @@ module ActiveRecord
end
result = execute schema_creation.accept td
- td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
+
+ unless supports_indexes_in_create?
+ td.indexes.each_pair do |column_name, index_options|
+ add_index(table_name, column_name, index_options)
+ end
+ end
+
+ td.foreign_keys.each_pair do |other_table_name, foreign_key_options|
+ add_foreign_key(table_name, other_table_name, foreign_key_options)
+ end
+
result
end
@@ -361,8 +372,12 @@ module ActiveRecord
# Drops a table from the database.
#
- # Although this command ignores +options+ and the block if one is given, it can be helpful
- # to provide these in a migration's +change+ method so it can be reverted.
+ # [<tt>:force</tt>]
+ # Set to +:cascade+ to drop dependent objects as well.
+ # Defaults to false.
+ #
+ # Although this command ignores most +options+ and the block if one is given,
+ # 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)}"
@@ -571,6 +586,8 @@ module ActiveRecord
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
#
def rename_index(table_name, old_name, new_name)
+ validate_index_length!(table_name, new_name)
+
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
return unless old_index_def
@@ -619,17 +636,16 @@ module ActiveRecord
#
# add_belongs_to(:products, :supplier, polymorphic: true)
#
- # ====== Create a supplier_id, supplier_type columns and appropriate index
+ # ====== Create supplier_id, supplier_type columns and appropriate index
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
- def add_reference(table_name, ref_name, options = {})
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- type = options.delete(:type) || :integer
- add_column(table_name, "#{ref_name}_id", type, options)
- add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
+ # ====== Create a supplier_id column and appropriate foreign key
+ #
+ # add_reference(:products, :supplier, foreign_key: true)
+ #
+ def add_reference(table_name, *args)
+ ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
end
alias :add_belongs_to :add_reference
@@ -740,7 +756,7 @@ module ActiveRecord
end
fk_name_to_delete = options.fetch(:name) do
- fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column] }
+ fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
if fk_to_delete
fk_to_delete.name
@@ -778,7 +794,7 @@ module ActiveRecord
version = version.to_i
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
- migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
+ migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
versions = Dir[*paths].map do |filename|
filename.split('/').last.split('_').first.to_i
@@ -835,11 +851,14 @@ module ActiveRecord
columns
end
- # Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
+ # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
+ # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
#
- # add_timestamps(:suppliers)
+ # add_timestamps(:suppliers, null: false)
#
def add_timestamps(table_name, options = {})
+ options[:null] = false if options[:null].nil?
+
add_column table_name, :created_at, :datetime, options
add_column table_name, :updated_at, :datetime, options
end
@@ -848,7 +867,7 @@ module ActiveRecord
#
# remove_timestamps(:suppliers)
#
- def remove_timestamps(table_name)
+ def remove_timestamps(table_name, options = {})
remove_column table_name, :updated_at
remove_column table_name, :created_at
end
@@ -962,12 +981,12 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary, options, as = nil)
+ def create_table_definition(name, temporary = false, options = nil, as = nil)
TableDefinition.new native_database_types, name, temporary, options, as
end
def create_alter_table(name)
- AlterTable.new create_table_definition(name, false, {})
+ AlterTable.new create_table_definition(name)
end
def foreign_key_name(table_name, options) # :nodoc:
@@ -975,6 +994,12 @@ module ActiveRecord
"fk_rails_#{SecureRandom.hex(5)}"
end
end
+
+ def validate_index_length!(table_name, new_name)
+ if new_name.length > allowed_index_name_length
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index fd666c8c39..f6ef3b0675 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -69,12 +69,7 @@ module ActiveRecord
def rollback_records
ite = records.uniq
while record = ite.shift
- begin
- record.rolledback! full_rollback?
- rescue => e
- raise if ActiveRecord::Base.raise_in_transactional_callbacks
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
- end
+ record.rolledback! full_rollback?
end
ensure
ite.each do |i|
@@ -89,12 +84,7 @@ module ActiveRecord
def commit_records
ite = records.uniq
while record = ite.shift
- begin
- record.committed!
- rescue => e
- raise if ActiveRecord::Base.raise_in_transactional_callbacks
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
- end
+ record.committed!
end
ensure
ite.each do |i|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 57aa2f9c58..c3a8bf5c74 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -21,6 +21,7 @@ module ActiveRecord
autoload :IndexDefinition
autoload :ColumnDefinition
autoload :ChangeColumnDefinition
+ autoload :ForeignKeyDefinition
autoload :TableDefinition
autoload :Table
autoload :AlterTable
@@ -262,9 +263,19 @@ module ActiveRecord
# 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)
+ def substitute_at(column, _unused = 0)
Arel::Nodes::BindParam.new
end
@@ -340,9 +351,6 @@ module ActiveRecord
def create_savepoint(name = nil)
end
- def rollback_to_savepoint(name = nil)
- end
-
def release_savepoint(name = nil)
end
@@ -448,7 +456,12 @@ module ActiveRecord
end
def translate_exception_class(e, sql)
- message = "#{e.class.name}: #{e.message}: #{sql}"
+ begin
+ message = "#{e.class.name}: #{e.message}: #{sql}"
+ rescue Encoding::CompatibilityError
+ message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}"
+ end
+
@logger.error message if @logger
exception = translate_exception(e, message)
exception.set_backtrace e.backtrace
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 167453657d..e9a3c26c32 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,6 +6,13 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include Savepoints
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
+ def primary_key(name, type = :primary_key, options = {})
+ options[:auto_increment] ||= type == :bigint
+ super
+ end
+ end
+
class SchemaCreation < AbstractAdapter::SchemaCreation
def visit_AddColumn(o)
add_column_position!(super, column_options(o))
@@ -31,12 +38,8 @@ module ActiveRecord
end
def visit_ChangeColumnDefinition(o)
- column = o.column
- options = o.options
- sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
- change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
- add_column_options!(change_column_sql, options.merge(column: column))
- add_column_position!(change_column_sql, options)
+ change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
+ add_column_position!(change_column_sql, column_options(o.column))
end
def add_column_position!(sql, options)
@@ -58,6 +61,18 @@ module ActiveRecord
SchemaCreation.new self
end
+ def column_spec_for_primary_key(column)
+ spec = {}
+ if column.extra == 'auto_increment'
+ return unless column.limit == 8
+ spec[:id] = ':bigint'
+ else
+ spec[:id] = column.type.inspect
+ spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ end
+ spec
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
attr_reader :collation, :strict, :extra
@@ -285,7 +300,9 @@ module ActiveRecord
end
end
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def clear_cache!
super
@@ -322,7 +339,7 @@ module ActiveRecord
execute "COMMIT"
end
- def rollback_db_transaction #:nodoc:
+ def exec_rollback_db_transaction #:nodoc:
execute "ROLLBACK"
end
@@ -396,7 +413,7 @@ module ActiveRecord
sql << "LIKE #{quote(like)}" if like
execute_and_free(sql, 'SCHEMA') do |result|
- result.collect { |field| field.first }
+ result.collect(&:first)
end
end
@@ -485,11 +502,13 @@ module ActiveRecord
end
def drop_table(table_name, options = {})
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
def rename_index(table_name, old_name, new_name)
if supports_rename_index?
+ validate_index_length!(table_name, new_name)
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
else
super
@@ -580,6 +599,13 @@ 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
@@ -654,19 +680,25 @@ module ActiveRecord
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
- m.register_type %r(^bigint)i, Type::Integer.new(limit: 8)
- m.register_type %r(^int)i, Type::Integer.new(limit: 4)
- m.register_type %r(^mediumint)i, Type::Integer.new(limit: 3)
- m.register_type %r(^smallint)i, Type::Integer.new(limit: 2)
- m.register_type %r(^tinyint)i, Type::Integer.new(limit: 1)
m.register_type %r(^float)i, Type::Float.new(limit: 24)
m.register_type %r(^double)i, Type::Float.new(limit: 53)
+ register_integer_type m, %r(^bigint)i, limit: 8
+ register_integer_type m, %r(^int)i, limit: 4
+ register_integer_type m, %r(^mediumint)i, limit: 3
+ register_integer_type m, %r(^smallint)i, limit: 2
+ register_integer_type m, %r(^tinyint)i, limit: 1
+
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
m.alias_type %r(set)i, 'varchar'
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
+ 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
@@ -674,6 +706,16 @@ module ActiveRecord
end
end
+ def register_integer_type(mapping, key, options) # :nodoc:
+ mapping.register_type(key) do |sql_type|
+ if /unsigned/i =~ sql_type
+ Type::UnsignedInteger.new(options)
+ else
+ Type::Integer.new(options)
+ end
+ end
+ 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)
@@ -722,7 +764,7 @@ module ActiveRecord
end
def add_column_sql(table_name, column_name, type, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options]
+ td = create_table_definition(table_name)
cd = td.new_column_definition(column_name, type, options)
schema_creation.visit_AddColumn cd
end
@@ -738,21 +780,23 @@ module ActiveRecord
options[:null] = column.null
end
- options[:name] = column.name
- schema_creation.accept ChangeColumnDefinition.new column, type, options
+ td = create_table_definition(table_name)
+ cd = td.new_column_definition(column.name, type, options)
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
def rename_column_sql(table_name, column_name, new_column_name)
column = column_for(table_name, column_name)
options = {
- name: new_column_name,
default: column.default,
null: column.null,
auto_increment: column.extra == "auto_increment"
}
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
+ td = create_table_definition(table_name)
+ cd = td.new_column_definition(new_column_name, current_type, options)
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
def remove_column_sql(table_name, column_name, type = nil, options = {})
@@ -777,14 +821,14 @@ module ActiveRecord
[add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
end
- def remove_timestamps_sql(table_name)
+ def remove_timestamps_sql(table_name, options = {})
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
end
private
def version
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
end
def mariadb?
@@ -846,6 +890,26 @@ module ActiveRecord
end
end
+ def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
+ 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)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index dd303c73d5..e74de60a83 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,7 +5,6 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
module Format
@@ -16,7 +15,7 @@ module ActiveRecord
attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
delegate :type, :precision, :scale, :limit, :klass, :accessor,
- :number?, :binary?, :changed?,
+ :text?, :number?, :binary?, :changed?,
:type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
:type_cast_for_schema,
to: :cast_type
@@ -30,13 +29,13 @@ module ActiveRecord
# <tt>company_name varchar(60)</tt>.
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, cast_type, sql_type = nil, null = true)
+ 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
- @default_function = nil
+ @default_function = default_function
end
def has_default?
@@ -77,6 +76,12 @@ module ActiveRecord
[self.class, name, default, cast_type, sql_type, null, default_function]
end
end
+
+ class NullColumn < Column
+ def initialize(name)
+ super name, nil, Type::Value.new
+ end
+ end
end
# :startdoc:
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 609ec7dabd..08d46fca96 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -1,5 +1,4 @@
require 'uri'
-require 'active_support/core_ext/string/filters'
module ActiveRecord
module ConnectionAdapters
@@ -210,31 +209,13 @@ module ActiveRecord
when Symbol
resolve_symbol_connection spec
when String
- resolve_string_connection spec
+ resolve_url_connection spec
when Hash
resolve_hash_connection spec
end
end
- def resolve_string_connection(spec)
- # Rails has historically accepted a string to mean either
- # an environment key or a URL spec, so we have deprecated
- # this ambiguous behaviour and in the future this function
- # can be removed in favor of resolve_url_connection.
- if configurations.key?(spec) || spec !~ /:/
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a string to ActiveRecord::Base.establish_connection for a
- configuration lookup is deprecated, please pass a symbol
- (#{spec.to_sym.inspect}) instead.
- MSG
-
- resolve_symbol_connection(spec)
- else
- resolve_url_connection(spec)
- end
- end
-
- # Takes the environment such as `:production` or `:development`.
+ # Takes the environment such as +:production+ or +:development+.
# This requires that the @configurations was initialized with a key that
# matches.
#
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 5b83131f0e..75f244b3f3 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -66,21 +66,17 @@ module ActiveRecord
exception.error_number if exception.respond_to?(:error_number)
end
+ #--
# QUOTING ==================================================
+ #++
def quote_string(string)
@connection.escape(string)
end
- def quoted_date(value)
- if value.acts_like?(:time) && value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
- end
- end
-
+ #--
# CONNECTION MANAGEMENT ====================================
+ #++
def active?
return false unless @connection
@@ -104,7 +100,9 @@ module ActiveRecord
end
end
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 3357ed52e5..23d8389abb 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -58,7 +58,7 @@ module ActiveRecord
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
# * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html)
- # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
+ # * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -137,7 +137,9 @@ module ActiveRecord
@connection.quote(string)
end
+ #--
# CONNECTION MANAGEMENT ====================================
+ #++
def active?
if @connection.respond_to?(:stat)
@@ -178,7 +180,9 @@ module ActiveRecord
end
end
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def select_rows(sql, name = nil, binds = [])
@connection.query_with_result = true
@@ -419,9 +423,7 @@ module ActiveRecord
cols = nil
if metadata = stmt.result_metadata
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
- field.name
- }
+ cols = cache[:cols] ||= metadata.fetch_fields.map(&:name)
metadata.free
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index 37e5c3859c..acb1278499 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -2,18 +2,21 @@ module ActiveRecord
module ConnectionAdapters
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
- attr_accessor :array
+ attr_reader :array
+ alias :array? :array
def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
if sql_type =~ /\[\]$/
@array = true
- super(name, default, cast_type, sql_type[0..sql_type.length - 3], null)
+ sql_type = sql_type[0..sql_type.length - 3]
else
@array = false
- super(name, default, cast_type, sql_type, null)
end
+ super
+ end
- @default_function = default_function
+ def serial?
+ default_function && default_function =~ /\Anextval\(.*\)\z/
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index d09468329a..11d3f5301a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -223,7 +223,7 @@ module ActiveRecord
end
# Aborts a transaction.
- def rollback_db_transaction
+ def exec_rollback_db_transaction
execute "ROLLBACK"
end
end
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 cd5efe2bb8..c203e6c604 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -34,6 +34,9 @@ module ActiveRecord
end
def type_cast_from_user(value)
+ if value.is_a?(::String)
+ value = parse_pg_array(value)
+ end
type_cast_array(value, :type_cast_from_user)
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 997613d7be..6bd1b8ecae 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -5,6 +5,7 @@ module ActiveRecord
class Bytea < Type::Binary # :nodoc:
def type_cast_from_database(value)
return if value.nil?
+ return value.to_s if value.is_a?(Type::Binary::Data)
PGconn.unescape_bytea(super)
end
end
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 961e6224c4..3adfb8b9d8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -25,16 +25,7 @@ module ActiveRecord
to = type_cast_single extracted[:to]
if !infinity?(from) && extracted[:exclude_start]
- if from.respond_to?(:succ)
- from = from.succ
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Excluding the beginning of a Range is only partialy supported
- through `#succ`. This is not reliable and will be removed in
- the future.
- MSG
- else
- raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
- end
+ raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
end
::Range.new(from, to, extracted[:exclude_end])
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 2d2fede4e8..b2a42e9ebb 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,6 +8,10 @@ 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/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
index 35e699eeda..9b3de41fab 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
@@ -39,14 +39,14 @@ module ActiveRecord
end
def register_array_type(row)
- if subtype = @store.lookup(row['typelem'].to_i)
- register row['oid'], OID::Array.new(subtype, row['typdelim'])
+ register_with_subtype(row['oid'], row['typelem'].to_i) do |subtype|
+ OID::Array.new(subtype, row['typdelim'])
end
end
def register_range_type(row)
- if subtype = @store.lookup(row['rngsubtype'].to_i)
- register row['oid'], OID::Range.new(subtype, row['typname'].to_sym)
+ register_with_subtype(row['oid'], row['rngsubtype'].to_i) do |subtype|
+ OID::Range.new(subtype, row['typname'].to_sym)
end
end
@@ -64,9 +64,13 @@ module ActiveRecord
end
end
- def register(oid, oid_type)
- oid = assert_valid_registration(oid, oid_type)
- @store.register_type(oid, oid_type)
+ def register(oid, oid_type = nil, &block)
+ oid = assert_valid_registration(oid, oid_type || block)
+ if block_given?
+ @store.register_type(oid, &block)
+ else
+ @store.register_type(oid, oid_type)
+ end
end
def alias_type(oid, target)
@@ -74,6 +78,14 @@ module ActiveRecord
@store.alias_type(oid, target)
end
+ def register_with_subtype(oid, target_oid)
+ if @store.key?(target_oid)
+ register(oid) do |_, *args|
+ yield @store.lookup(target_oid, *args)
+ end
+ end
+ end
+
def assert_valid_registration(oid, oid_type)
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
oid.to_i
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 033e0324bb..97b4fd3d08 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -3,14 +3,7 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Uuid < Type::Value # :nodoc:
- RFC_4122 = %r{\A\{?[a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [1-5][a-fA-F0-9]{3}-?
- [8-Bab][a-fA-F0-9]{3}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?\}?\z}x
+ ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
alias_method :type_cast_for_database, :type_cast_from_database
@@ -19,7 +12,7 @@ module ActiveRecord
end
def type_cast(value)
- value.to_s[RFC_4122, 0]
+ value.to_s[ACCEPTABLE_UUID, 0]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index f95f45c689..9de9e2c7dc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -14,22 +14,6 @@ module ActiveRecord
@connection.unescape_bytea(value) if value
end
- # Quotes PostgreSQL-specific data types for SQL input.
- def quote(value, column = nil) #:nodoc:
- return super unless column
-
- case value
- when Float
- if value.infinite? || value.nan?
- "'#{value}'"
- else
- super
- end
- else
- super
- end
- end
-
# Quotes strings for use in SQL input.
def quote_string(s) #:nodoc:
@connection.escape(s)
@@ -59,16 +43,12 @@ module ActiveRecord
# 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:
- result = super
- if value.acts_like?(:time) && value.respond_to?(:usec)
- result = "#{result}.#{sprintf("%06d", value.usec)}"
- end
-
if value.year <= 0
bce_year = format("%04d", -value.year + 1)
- result = result.sub(/^-?\d+/, bce_year) + " BC"
+ super.sub(/^-?\d+/, bce_year) + " BC"
+ else
+ super
end
- result
end
# Does not quote function default values for UUID columns
@@ -76,7 +56,8 @@ module ActiveRecord
if column.type == :uuid && value =~ /\(\)/
value
else
- quote(value, column)
+ value = column.cast_type.type_cast_for_database(value)
+ quote(value)
end
end
@@ -94,6 +75,12 @@ module ActiveRecord
elsif value.hex?
"X'#{value}'"
end
+ when Float
+ if value.infinite? || value.nan?
+ "'#{value}'"
+ else
+ super
+ end
else
super
end
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 b37630a04c..a9522e152f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -125,10 +125,8 @@ module ActiveRecord
# a record (as primary keys cannot be +nil+). This might be done via the
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, options = {})
- return super unless type == :uuid
- options[:default] = options.fetch(:default, 'uuid_generate_v4()')
- options[:primary_key] = true
- column name, type, options
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid
+ super
end
def new_column_definition(name, type, options) # :nodoc:
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 51853c6812..a90adcf4aa 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -4,29 +4,22 @@ module ActiveRecord
class SchemaCreation < AbstractAdapter::SchemaCreation
private
- def visit_AddColumn(o)
- sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
- sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(sql, column_options(o))
- end
-
- def visit_ColumnDefinition(o)
- sql = super
- if o.primary_key? && o.type != :primary_key
- sql << " PRIMARY KEY "
- add_column_options!(sql, column_options(o))
- end
- sql
+ def column_options(o)
+ column_options = super
+ column_options[:array] = o.array
+ column_options
end
def add_column_options!(sql, options)
- if options[:array] || options[:column].try(:array)
+ if options[:array]
sql << '[]'
end
+ super
+ end
- column = options.fetch(:column) { return super }
- if column.type == :uuid && options[:default] =~ /\(\)/
- sql << " DEFAULT #{options[:default]}"
+ def quote_default_expression(value, column)
+ if column.type == :uuid && value =~ /\(\)/
+ value
else
super
end
@@ -118,6 +111,10 @@ module ActiveRecord
SQL
end
+ def drop_table(table_name, options = {})
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ end
+
# Returns true if schema exists.
def schema_exists?(name)
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
@@ -388,15 +385,15 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
+ pks = exec_query(<<-end_sql, 'SCHEMA').rows
SELECT attr.attname
FROM pg_attribute attr
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
WHERE cons.contype = 'p'
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
-
- row && row.first
+ return nil unless pks.count == 1
+ pks[0][0]
end
# Renames a table.
@@ -411,7 +408,10 @@ module ActiveRecord
pk, seq = pk_and_sequence_for(new_name)
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
new_seq = "#{new_name}_#{pk}_seq"
+ idx = "#{table_name}_pkey"
+ new_idx = "#{new_name}_pkey"
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
end
rename_table_indexes(table_name, new_name)
@@ -430,7 +430,12 @@ module ActiveRecord
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]
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
+ 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])})"
+ end
+ execute sql
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -478,6 +483,8 @@ module ActiveRecord
end
def rename_index(table_name, old_name, new_name)
+ validate_index_length!(table_name, new_name)
+
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index d3e5b0a4ad..5b070cae4f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -125,16 +125,31 @@ module ActiveRecord
PostgreSQL::SchemaCreation.new self
end
- # Adds `:array` option to the default set provided by the
+ def column_spec_for_primary_key(column)
+ spec = {}
+ if column.serial?
+ return unless column.sql_type == 'bigint'
+ spec[:id] = ':bigserial'
+ elsif column.type == :uuid
+ spec[:id] = ':uuid'
+ spec[:default] = column.default_function.inspect
+ else
+ spec[:id] = column.type.inspect
+ spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ end
+ spec
+ end
+
+ # Adds +:array+ option to the default set provided by the
# AbstractAdapter
- def prepare_column_options(column, types) # :nodoc:
+ def prepare_column_options(column) # :nodoc:
spec = super
- spec[:array] = 'true' if column.respond_to?(:array) && column.array
+ spec[:array] = 'true' if column.array?
spec[:default] = "\"#{column.default_function}\"" if column.default_function
spec
end
- # Adds `:array` as a valid migration key
+ # Adds +:array+ as a valid migration key
def migration_keys
super + [:array]
end
@@ -431,28 +446,22 @@ module ActiveRecord
private
def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
-
- result = type_map.fetch(oid, fmod, sql_type) {
- nil
- }
-
- unless result
+ if !type_map.key?(oid)
load_additional_types(type_map, [oid])
- result = type_map.fetch(oid, fmod, sql_type) {
- warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
- Type::Value.new.tap do |cast_type|
- type_map.register_type(oid, cast_type)
- end
- }
end
- result
+ type_map.fetch(oid, fmod, sql_type) {
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
+ Type::Value.new.tap do |cast_type|
+ type_map.register_type(oid, cast_type)
+ end
+ }
end
def initialize_type_map(m) # :nodoc:
register_class_with_limit m, 'int2', OID::Integer
- m.alias_type 'int4', 'int2'
- m.alias_type 'int8', 'int2'
+ register_class_with_limit m, 'int4', OID::Integer
+ register_class_with_limit m, 'int8', OID::Integer
m.alias_type 'oid', 'int2'
m.register_type 'float4', OID::Float.new
m.alias_type 'float8', 'float4'
@@ -522,9 +531,12 @@ module ActiveRecord
def extract_limit(sql_type) # :nodoc:
case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- else super
+ when /^bigint/i, /^int8/i
+ 8
+ when /^smallint/i
+ 2
+ else
+ super
end
end
@@ -538,7 +550,7 @@ module ActiveRecord
when 'true', 'false'
default
# Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
+ when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
$1
# Object identifier types
when /\A-?\d+\z/
@@ -749,7 +761,7 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(name, temporary, options, as = nil) # :nodoc:
+ def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index a10ce330c7..37ff4e4613 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -61,9 +61,7 @@ module ActiveRecord
end
def size
- [@columns, @columns_hash, @primary_keys, @tables].map { |x|
- x.size
- }.inject :+
+ [@columns, @columns_hash, @primary_keys, @tables].map(&:size).inject :+
end
# Clear out internal caches for table with +table_name+.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index b18cb353f1..03dfd29a0a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -88,11 +88,11 @@ module ActiveRecord
include Comparable
def initialize(version_string)
- @version = version_string.split('.').map { |v| v.to_i }
+ @version = version_string.split('.').map(&:to_i)
end
def <=>(version_string)
- @version <=> version_string.split('.').map { |v| v.to_i }
+ @version <=> version_string.split('.').map(&:to_i)
end
end
@@ -256,17 +256,9 @@ module ActiveRecord
%Q("#{name.to_s.gsub('"', '""')}")
end
- # 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.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
- end
- end
-
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def explain(arel, binds = [])
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
@@ -359,7 +351,7 @@ module ActiveRecord
log('commit transaction',nil) { @connection.commit }
end
- def rollback_db_transaction #:nodoc:
+ def exec_rollback_db_transaction #:nodoc:
log('rollback transaction',nil) { @connection.rollback }
end
@@ -426,10 +418,9 @@ module ActiveRecord
end
def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find { |field|
- field['pk'] == 1
- }
- column && column['name']
+ pks = table_structure(table_name).select { |f| f['pk'] > 0 }
+ return nil unless pks.count == 1
+ pks[0]['name']
end
def remove_index!(table_name, index_name) #:nodoc:
@@ -554,7 +545,7 @@ module ActiveRecord
end
copy_table_indexes(from, to, options[:rename] || {})
copy_table_contents(from, to,
- @definition.columns.map {|column| column.name},
+ @definition.columns.map(&:name),
options[:rename] || {})
end
@@ -567,7 +558,7 @@ module ActiveRecord
name = name[1..-1]
end
- to_column_names = columns(to).map { |c| c.name }
+ to_column_names = columns(to).map(&:name)
columns = index.columns.map {|c| rename[c] || c }.select do |column|
to_column_names.include?(column)
end
@@ -584,25 +575,14 @@ module ActiveRecord
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
column_mappings = Hash[columns.map {|name| [name, name]}]
rename.each { |a| column_mappings[a.last] = a.first }
- from_columns = columns(from).collect {|col| col.name}
+ from_columns = columns(from).collect(&:name)
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ from_columns_to_copy = columns.map { |col| column_mappings[col] }
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
+ quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
- quoted_to = quote_table_name(to)
-
- raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
-
- exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
- sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
-
- column_values = columns.map do |col|
- quote(row[column_mappings[col]], raw_column_mappings[col])
- end
-
- sql << column_values * ', '
- sql << ')'
- exec_query sql
- end
+ exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
end
def sqlite_version
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 8f51590c99..984af79642 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module ConnectionHandling
- RAILS_ENV = -> { (Rails.env if defined?(Rails)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
+ RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
# Establishes the connection to the database. Accepts a hash as input where
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 89d8932e9e..5c7c0feeb7 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -85,16 +85,8 @@ module ActiveRecord
mattr_accessor :dump_schema_after_migration, instance_writer: false
self.dump_schema_after_migration = true
- # :nodoc:
mattr_accessor :maintain_test_schema, instance_accessor: false
- def self.disable_implicit_join_references=(value)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Implicit join references were removed with Rails 4.1.
- Make sure to remove this configuration because it does nothing.
- MSG
- end
-
class_attribute :default_connection_handler, instance_writer: false
class_attribute :find_by_statement_cache
@@ -132,6 +124,7 @@ module ActiveRecord
return super if block_given? ||
primary_key.nil? ||
default_scopes.any? ||
+ current_scope ||
columns_hash.include?(inheritance_column) ||
ids.first.kind_of?(Array)
@@ -235,7 +228,7 @@ module ActiveRecord
# scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
# end
def arel_table # :nodoc:
- @arel_table ||= Arel::Table.new(table_name, arel_engine)
+ @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
end
# Returns the Arel engine.
@@ -248,10 +241,18 @@ module ActiveRecord
end
end
+ def predicate_builder # :nodoc:
+ @predicate_builder ||= PredicateBuilder.new(table_metadata)
+ end
+
+ def type_caster # :nodoc:
+ TypeCaster::Map.new(self)
+ end
+
private
- def relation #:nodoc:
- relation = Relation.create(self, arel_table)
+ def relation # :nodoc:
+ relation = Relation.create(self, arel_table, predicate_builder)
if finder_needs_type_condition?
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
@@ -259,6 +260,10 @@ module ActiveRecord
relation
end
end
+
+ def table_metadata # :nodoc:
+ TableMetadata.new(self, arel_table)
+ end
end
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 73fd96f979..7d8e0a2063 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -37,10 +37,9 @@ module ActiveRecord
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
- stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
- arel_table[counter_name] => object.send(counter_association).count(:all)
- }, primary_key)
- connection.update stmt
+ unscoped.where(primary_key => object.id).update_all(
+ counter_name => object.send(counter_association).count(:all)
+ )
end
return true
end
@@ -123,16 +122,6 @@ module ActiveRecord
end
end
- protected
-
- def actually_destroyed?
- @_actually_destroyed
- end
-
- def clear_destroy_state
- @_actually_destroyed = nil
- end
-
private
def _create_record(*)
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e94b74063e..b6dd6814db 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,10 +1,5 @@
module ActiveRecord
module DynamicMatchers #:nodoc:
- # This code in this file seems to have a lot of indirection, but the indirection
- # is there to provide extension points for the activerecord-deprecated_finders
- # gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
- # then we can remove the indirection.
-
def respond_to?(name, include_private = false)
if self == Base
super
@@ -72,26 +67,14 @@ module ActiveRecord
CODE
end
- def body
- raise NotImplementedError
- end
- end
+ private
- module Finder
- # Extended in activerecord-deprecated_finders
def body
- result
- end
-
- # Extended in activerecord-deprecated_finders
- def result
"#{finder}(#{attributes_hash})"
end
# The parameters in the signature may have reserved Ruby words, in order
# to prevent errors, we start each param name with `_`.
- #
- # Extended in activerecord-deprecated_finders
def signature
attribute_names.map { |name| "_#{name}" }.join(', ')
end
@@ -109,7 +92,6 @@ module ActiveRecord
class FindBy < Method
Method.matchers << self
- include Finder
def self.prefix
"find_by"
@@ -122,7 +104,6 @@ module ActiveRecord
class FindByBang < Method
Method.matchers << self
- include Finder
def self.prefix
"find_by"
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 5958373e88..f053372cfb 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -142,7 +142,7 @@ module ActiveRecord
private
def save_changed_attribute(attr_name, old)
if (mapping = self.class.defined_enums[attr_name.to_s])
- value = read_attribute(attr_name)
+ value = _read_attribute(attr_name)
if attribute_changed?(attr_name)
if mapping[old] == value
clear_attribute_changes([attr_name])
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 5b3fdf16f5..fc28ab585f 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -52,10 +52,29 @@ module ActiveRecord
# Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
# saved because record is invalid.
class RecordNotSaved < ActiveRecordError
+ attr_reader :record
+
+ def initialize(message, record = nil)
+ @record = record
+ super(message)
+ end
end
# Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
+ #
+ # begin
+ # complex_operation_that_internally_calls_destroy!
+ # rescue ActiveRecord::RecordNotDestroyed => invalid
+ # puts invalid.record.errors
+ # end
+ #
class RecordNotDestroyed < ActiveRecordError
+ attr_reader :record
+
+ def initialize(record)
+ @record = record
+ super()
+ end
end
# Superclass for all database execution errors.
@@ -177,6 +196,7 @@ module ActiveRecord
# offending attribute.
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
+
def initialize(message, exception, attribute)
super(message)
@exception = exception
@@ -189,6 +209,7 @@ module ActiveRecord
# objects, each corresponding to the error while assigning to an attribute.
class MultiparameterAssignmentErrors < ActiveRecordError
attr_reader :errors
+
def initialize(errors)
@errors = errors
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index db6421dacb..5f6a75ebef 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -633,7 +633,7 @@ module ActiveRecord
# interpolate the fixture label
row.each do |key, value|
- row[key] = value.gsub("$LABEL", label) if value.is_a?(String)
+ row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
end
# generate a primary key if necessary
@@ -745,7 +745,7 @@ module ActiveRecord
end
def column_names
- @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
+ @column_names ||= @connection.columns(@table_name).collect(&:name)
end
def read_fixture_files(path, model_class)
@@ -768,12 +768,6 @@ module ActiveRecord
end
- #--
- # Deprecate 'Fixtures' in favor of 'FixtureSet'.
- #++
- # :nodoc:
- Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet')
-
class Fixture #:nodoc:
include Enumerable
@@ -806,7 +800,9 @@ module ActiveRecord
def find
if model_class
- model_class.find(fixture[model_class.primary_key])
+ model_class.unscoped do
+ model_class.find(fixture[model_class.primary_key])
+ end
else
raise FixtureClassNotFound, "No class attached to find."
end
@@ -866,7 +862,7 @@ module ActiveRecord
fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"]
fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
- fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
+ fixture_set_names = fixture_set_names.flatten.map(&:to_s)
end
self.fixture_table_names |= fixture_set_names
@@ -906,7 +902,7 @@ module ActiveRecord
def uses_transaction(*methods)
@uses_transaction = [] unless defined?(@uses_transaction)
- @uses_transaction.concat methods.map { |m| m.to_s }
+ @uses_transaction.concat methods.map(&:to_s)
end
def uses_transaction?(method)
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index e820835626..a388b529c9 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -5,10 +5,10 @@ module ActiveRecord
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index f58145ab05..fd1e22349b 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -79,16 +79,6 @@ module ActiveRecord
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
end
- def symbolized_base_class
- ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.')
- @symbolized_base_class ||= base_class.to_s.to_sym
- end
-
- def symbolized_sti_name
- ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.')
- @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
- end
-
# Returns the class descending directly from ActiveRecord::Base, or
# an abstract class, if any, in the inheritance hierarchy.
#
@@ -192,7 +182,7 @@ module ActiveRecord
def type_condition(table = arel_table)
sti_column = table[inheritance_column]
- sti_names = ([self] + descendants).map { |model| model.sti_name }
+ sti_names = ([self] + descendants).map(&:sti_name)
sti_column.in(sti_names)
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index ced694ba9a..9f053453bd 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -11,7 +11,7 @@ module ActiveRecord
#
# == Usage
#
- # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
+ # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
#
@@ -80,17 +80,15 @@ module ActiveRecord
begin
relation = self.class.unscoped
- stmt = relation.where(
- relation.table[self.class.primary_key].eq(id).and(
- relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
- )
- ).arel.compile_update(
- arel_attributes_with_values_for_update(attribute_names),
- self.class.primary_key
+ affected_rows = relation.where(
+ self.class.primary_key => id,
+ lock_col => previous_lock_value,
+ ).update_all(
+ attribute_names.map do |name|
+ [name, _read_attribute(name)]
+ end.to_h
)
- affected_rows = self.class.connection.update stmt
-
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 2024b225e4..46f4794010 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -39,7 +39,7 @@ module ActiveRecord
class PendingMigrationError < MigrationError#:nodoc:
def initialize
- if defined?(Rails)
+ if defined?(Rails.env)
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}")
else
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate")
@@ -184,8 +184,8 @@ module ActiveRecord
# you wish to downgrade. Alternatively, you can also use the STEP option if you
# wish to rollback last few migrations. <tt>rake db:migrate STEP=2</tt> will rollback
# the latest two migrations.
- #
- # If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
+ #
+ # If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
# that step will fail and you'll have some manual work to do.
#
# == Database support
@@ -395,7 +395,14 @@ module ActiveRecord
def load_schema_if_pending!
if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations?
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current_if_exists
+ # Roundrip to Rake to allow plugins to hook into database initialization.
+ FileUtils.cd Rails.root do
+ current_config = Base.connection_config
+ Base.clear_all_connections!
+ system("bin/rake db:test:prepare")
+ # Establish a new connection, the old database may be gone (db:test:prepare uses purge)
+ Base.establish_connection(current_config)
+ end
check_pending!
end
end
@@ -640,7 +647,7 @@ module ActiveRecord
end
def method_missing(method, *arguments, &block)
- arg_list = arguments.map{ |a| a.inspect } * ', '
+ arg_list = arguments.map(&:inspect) * ', '
say_with_time "#{method}(#{arg_list})" do
unless @connection.respond_to? :revert
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index adad7774b9..641512d323 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -147,7 +147,7 @@ module ActiveRecord
@quoted_table_name = nil
@arel_table = nil
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
- @relation = Relation.create(self, arel_table)
+ @predicate_builder = nil
end
# Returns a quoted version of the table name, used to construct SQL statements.
@@ -257,7 +257,7 @@ module ActiveRecord
# Returns an array of column names as strings.
def column_names
- @column_names ||= columns.map { |column| column.name }
+ @column_names ||= columns.map(&:name)
end
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
@@ -298,12 +298,12 @@ module ActiveRecord
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
@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
- @relation = nil
end
private
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 8a2a06f2ca..846e1162a9 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -81,6 +81,9 @@ module ActiveRecord
#
# Note that the model will _not_ be destroyed until the parent is saved.
#
+ # Also note that the model will not be destroyed unless you also specify
+ # its id in the updated hash.
+ #
# === One-to-many
#
# Consider a member that has a number of posts:
@@ -111,7 +114,7 @@ module ActiveRecord
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
#
- # You may also set a :reject_if proc to silently ignore any new record
+ # You may also set a +:reject_if+ proc to silently ignore any new record
# hashes if they fail to pass your criteria. For example, the previous
# example could be rewritten as:
#
@@ -133,7 +136,7 @@ module ActiveRecord
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
#
- # Alternatively, :reject_if also accepts a symbol for using methods:
+ # Alternatively, +:reject_if+ also accepts a symbol for using methods:
#
# class Member < ActiveRecord::Base
# has_many :posts
@@ -212,13 +215,13 @@ module ActiveRecord
# All changes to models, including the destruction of those marked for
# destruction, are saved and destroyed automatically and atomically when
# the parent model is saved. This happens inside the transaction initiated
- # by the parents save method. See ActiveRecord::AutosaveAssociation.
+ # by the parent's save method. See ActiveRecord::AutosaveAssociation.
#
# === Validating the presence of a parent model
#
# If you want to validate that a child record is associated with a parent
- # record, you can use <tt>validates_presence_of</tt> and
- # <tt>inverse_of</tt> as this example illustrates:
+ # record, you can use the +validates_presence_of+ method and the +:inverse_of+
+ # key as this example illustrates:
#
# class Member < ActiveRecord::Base
# has_many :posts, inverse_of: :member
@@ -230,7 +233,7 @@ module ActiveRecord
# validates_presence_of :member
# end
#
- # Note that if you do not specify the <tt>inverse_of</tt> option, then
+ # Note that if you do not specify the +:inverse_of+ option, then
# Active Record will try to automatically guess the inverse association
# based on heuristics.
#
@@ -264,29 +267,31 @@ module ActiveRecord
# Allows you to specify a Proc or a Symbol pointing to a method
# that checks whether a record should be built for a certain attribute
# hash. The hash is passed to the supplied Proc or the method
- # and it should return either +true+ or +false+. When no :reject_if
+ # and it should return either +true+ or +false+. When no +:reject_if+
# is specified, a record will be built for all attribute hashes that
# do not have a <tt>_destroy</tt> value that evaluates to true.
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
# that will reject a record where all the attributes are blank excluding
- # any value for _destroy.
+ # any value for +_destroy+.
# [:limit]
- # Allows you to specify the maximum number of the associated records that
- # can be processed with the nested attributes. Limit also can be specified as a
- # Proc or a Symbol pointing to a method that should return number. If the size of the
- # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
- # exception is raised. If omitted, any number associations can be processed.
- # Note that the :limit option is only applicable to one-to-many associations.
+ # Allows you to specify the maximum number of associated records that
+ # can be processed with the nested attributes. Limit also can be specified
+ # as a Proc or a Symbol pointing to a method that should return a number.
+ # If the size of the nested attributes array exceeds the specified limit,
+ # NestedAttributes::TooManyRecords exception is raised. If omitted, any
+ # number of associations can be processed.
+ # Note that the +:limit+ option is only applicable to one-to-many
+ # associations.
# [:update_only]
# For a one-to-one association, this option allows you to specify how
- # nested attributes are to be used when an associated record already
+ # nested attributes are going to be used when an associated record already
# exists. In general, an existing record may either be updated with the
# new set of attribute values or be replaced by a wholly new record
- # containing those values. By default the :update_only option is +false+
+ # containing those values. By default the +:update_only+ option is +false+
# and the nested attributes are used to update the existing record only
# if they include the record's <tt>:id</tt> value. Otherwise a new
# record will be instantiated and used to replace the existing one.
- # However if the :update_only option is +true+, the nested attributes
+ # However if the +:update_only+ option is +true+, the nested attributes
# are used to update the record's attributes always, regardless of
# whether the <tt>:id</tt> is present. The option is ignored for collection
# associations.
diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb
index dbf4564ae5..edb5066fa0 100644
--- a/activerecord/lib/active_record/no_touching.rb
+++ b/activerecord/lib/active_record/no_touching.rb
@@ -45,7 +45,7 @@ module ActiveRecord
NoTouching.applied_to?(self.class)
end
- def touch(*)
+ def touch(*) # :nodoc:
super unless no_touching?
end
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 807c301596..b406da14dc 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -62,9 +62,7 @@ module ActiveRecord
calculate :maximum, nil
end
- def calculate(operation, _column_name, _options = {})
- # TODO: Remove _options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def calculate(operation, _column_name)
if [:count, :sum, :size].include? operation
group_values.any? ? Hash.new : 0
elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index ee3b7b6163..cf6673db2e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -109,37 +109,45 @@ module ActiveRecord
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
- # There's a series of callbacks associated with +save+. If any of the
- # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
- # +save+ returns +false+. See ActiveRecord::Callbacks for further
+ # By default, #save also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
+ # There's a series of callbacks associated with #save. If any of the
+ # <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled and
+ # #save returns +false+. See ActiveRecord::Callbacks for further
# details.
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save(*)
- create_or_update
+ def save(*args)
+ create_or_update(*args)
rescue ActiveRecord::RecordInvalid
false
end
# Saves the model.
#
- # If the model is new a record gets created in the database, otherwise
+ # If the model is new, a record gets created in the database, otherwise
# the existing record gets updated.
#
# With <tt>save!</tt> validations always run. If any of them fail
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
# for more information.
#
- # There's a series of callbacks associated with <tt>save!</tt>. If any of
- # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
- # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
+ # By default, #save! also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
+ # There's a series of callbacks associated with #save!. If any of
+ # the <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled
+ # and #save! raises ActiveRecord::RecordNotSaved. See
# ActiveRecord::Callbacks for further details.
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save!(*)
- create_or_update || raise(RecordNotSaved)
+ def save!(*args)
+ create_or_update(*args) || raise(RecordNotSaved.new(nil, self))
end
# Deletes the record in the database and freezes this instance to
@@ -149,6 +157,8 @@ module ActiveRecord
# The row is simply removed with an SQL +DELETE+ statement on the
# record's primary key, and no callbacks are executed.
#
+ # Note that this will also delete records marked as <tt>readonly?</tt>.
+ #
# To enforce the object's +before_destroy+ and +after_destroy+
# callbacks or any <tt>:dependent</tt> association
# options, use <tt>#destroy</tt>.
@@ -161,10 +171,10 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
#
- # There's a series of callbacks associated with <tt>destroy</tt>. If
- # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
- # and <tt>destroy</tt> returns +false+. See
- # ActiveRecord::Callbacks for further details.
+ # There's a series of callbacks associated with #destroy. If the
+ # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
+ # and #destroy returns +false+.
+ # See ActiveRecord::Callbacks for further details.
def destroy
raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
destroy_associations
@@ -176,12 +186,12 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
#
- # There's a series of callbacks associated with <tt>destroy!</tt>. If
- # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
- # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
- # ActiveRecord::Callbacks for further details.
+ # There's a series of callbacks associated with #destroy!. If the
+ # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
+ # and #destroy! raises ActiveRecord::RecordNotDestroyed.
+ # See ActiveRecord::Callbacks for further details.
def destroy!
- destroy || raise(ActiveRecord::RecordNotDestroyed)
+ destroy || raise(ActiveRecord::RecordNotDestroyed, self)
end
# Returns an instance of the specified +klass+ with the attributes of the
@@ -496,9 +506,9 @@ module ActiveRecord
relation
end
- def create_or_update
+ def create_or_update(*args)
raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
- result = new_record? ? _create_record : _update_record
+ result = new_record? ? _create_record : _update_record(*args)
result != false
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index e8de4db3a7..91c9a0db99 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -55,11 +55,12 @@ module ActiveRecord
# The use of this method should be restricted to complicated SQL queries that can't be executed
# using the ActiveRecord::Calculations class methods. Look into those before using this.
#
- # ==== Parameters
+ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
+ # # => 12
#
- # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
+ # ==== Parameters
#
- # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
+ # * +sql+ - An SQL statement which should return a count query from the database, see the example above.
def count_by_sql(sql)
sql = sanitize_conditions(sql)
connection.select_value(sql, "#{name} Count").to_i
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a4ceacbf44..f1bdbc845c 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -36,8 +36,6 @@ module ActiveRecord
config.eager_load_namespaces << ActiveRecord
rake_tasks do
- require "active_record/base"
-
namespace :db do
task :load_config do
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 3ec25f9f17..04c2be045d 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -240,7 +240,7 @@ db_namespace = namespace :db do
end
desc 'Load a schema.rb file into the database'
- task :load => [:environment, :load_config] do
+ task :load => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
@@ -305,7 +305,7 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from the current schema"
- task :load => %w(db:test:deprecated db:test:purge) do
+ task :load => %w(db:test:purge) do
case ActiveRecord::Base.schema_format
when :ruby
db_namespace["test:load_schema"].invoke
@@ -315,11 +315,11 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from an existent schema.rb file"
- task :load_schema => %w(db:test:deprecated db:test:purge) do
+ task :load_schema => %w(db:test:purge) do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Schema.verbose = false
- ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
ensure
if should_reconnect
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
@@ -328,8 +328,8 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from an existent structure.sql file"
- task :load_structure => %w(db:test:deprecated db:test:purge) do
- ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
+ task :load_structure => %w(db:test:purge) do
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
end
# desc "Recreate the test database from a fresh schema"
@@ -349,12 +349,12 @@ db_namespace = namespace :db do
task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure)
# desc "Empty the test database"
- task :purge => %w(db:test:deprecated environment load_config) do
+ task :purge => %w(environment load_config) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare => %w(db:test:deprecated environment load_config) do
+ task :prepare => %w(environment load_config) do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
@@ -366,7 +366,7 @@ namespace :railties do
namespace :install do
# desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2"
task :migrations => :'db:load_config' do
- to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }
+ to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map(&:strip)
railties = {}
Rails.application.migration_railties.each do |railtie|
next unless to_load == :all || to_load.include?(railtie.railtie_name)
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 85bbac43e4..ce78f1756d 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# Attributes listed as readonly will be used to create a new record but update operations will
# ignore these fields.
def attr_readonly(*attributes)
- self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
+ self._attr_readonly = Set.new(attributes.map(&:to_s)) + (self._attr_readonly || [])
end
# Returns an array of all the attributes that have been specified as readonly.
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 4b58f2deb7..dab5a502a5 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -39,9 +39,9 @@ module ActiveRecord
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end
- # \Reflection enables interrogating of Active Record classes and objects
- # about their associations and aggregations. This information can,
- # for example, be used in a form builder that takes an Active Record object
+ # \Reflection enables the ability to examine the associations and aggregations of
+ # Active Record classes and objects. This information, for example,
+ # can be used in a form builder that takes an Active Record object
# and creates input fields for all of the attributes depending on their type
# and displays the associations to other objects.
#
@@ -63,7 +63,7 @@ module ActiveRecord
# Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
#
- # Account.reflections # => {balance: AggregateReflection}
+ # Account.reflections # => {"balance" => AggregateReflection}
#
# @api public
def reflections
@@ -149,19 +149,19 @@ module ActiveRecord
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
- def join_keys(assoc_klass)
+ def join_keys(association_klass)
JoinKeys.new(foreign_key, active_record_primary_key)
end
- def source_macro
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- ActiveRecord::Base.source_macro is deprecated and will be removed
- without replacement.
- MSG
+ def constraints
+ scope_chain.flatten
+ end
- macro
+ def alias_candidate(name)
+ "#{plural_name}_#{name}"
end
end
+
# Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
#
@@ -277,7 +277,7 @@ module ActiveRecord
def initialize(name, scope, options, active_record)
super
@automatic_inverse_of = nil
- @type = options[:as] && "#{options[:as]}_type"
+ @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
@foreign_type = options[:foreign_type] || "#{name}_type"
@constructable = calculate_constructable(macro, options)
@association_scope_cache = {}
@@ -287,7 +287,7 @@ module ActiveRecord
def association_scope_cache(conn, owner)
key = conn.prepared_statements
if polymorphic?
- key = [key, owner.read_attribute(@foreign_type)]
+ key = [key, owner._read_attribute(@foreign_type)]
end
@association_scope_cache[key] ||= @scope_lock.synchronize {
@association_scope_cache[key] ||= yield
@@ -343,13 +343,10 @@ module ActiveRecord
return unless scope
if scope.arity > 0
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ raise ArgumentError, <<-MSG.squish
The association scope '#{name}' is instance dependent (the scope
- block takes an argument). Preloading happens before the individual
- instances are created. This means that there is no instance being
- passed to the association scope. This will most likely result in
- broken or incorrect behavior. Joining, Preloading and eager loading
- of these associations is deprecated and will be removed in the future.
+ block takes an argument). Preloading instance dependent scopes is
+ not supported.
MSG
end
end
@@ -499,7 +496,7 @@ module ActiveRecord
# returns either nil or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
- inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
begin
reflection = klass._reflect_on_association(inverse_name)
@@ -601,8 +598,8 @@ module ActiveRecord
def belongs_to?; true; end
- def join_keys(assoc_klass)
- key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
+ def join_keys(association_klass)
+ key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
JoinKeys.new(key, foreign_key)
end
@@ -698,6 +695,11 @@ module ActiveRecord
@chain ||= begin
a = source_reflection.chain
b = through_reflection.chain
+
+ if options[:source_type]
+ b[0] = PolymorphicReflection.new(b[0], self)
+ end
+
chain = a + b
chain[0] = self # Use self so we don't lose the information from :source_type
chain
@@ -745,18 +747,8 @@ module ActiveRecord
end
end
- def join_keys(assoc_klass)
- source_reflection.join_keys(assoc_klass)
- end
-
- # The macro used by the source association
- def source_macro
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- ActiveRecord::Base.source_macro is deprecated and will be removed
- without replacement.
- MSG
-
- source_reflection.source_macro
+ def join_keys(association_klass)
+ source_reflection.join_keys(association_klass)
end
# A through association is nested if there would be more than one join table
@@ -791,7 +783,7 @@ module ActiveRecord
def source_reflection_name # :nodoc:
return @source_reflection_name if @source_reflection_name
- names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
names = names.find_all { |n|
through_reflection.klass._reflect_on_association(n)
}
@@ -855,6 +847,12 @@ module ActiveRecord
check_validity_of_inverse!
end
+ def constraints
+ scope_chain = source_reflection.constraints
+ scope_chain << scope if scope
+ scope_chain
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
@@ -877,5 +875,81 @@ module ActiveRecord
delegate(*delegate_methods, to: :delegate_reflection)
end
+
+ class PolymorphicReflection < ThroughReflection # :nodoc:
+ def initialize(reflection, previous_reflection)
+ @reflection = reflection
+ @previous_reflection = previous_reflection
+ end
+
+ def klass
+ @reflection.klass
+ end
+
+ def scope
+ @reflection.scope
+ end
+
+ def table_name
+ @reflection.table_name
+ end
+
+ def plural_name
+ @reflection.plural_name
+ end
+
+ def join_keys(association_klass)
+ @reflection.join_keys(association_klass)
+ end
+
+ def type
+ @reflection.type
+ end
+
+ def constraints
+ [source_type_info]
+ end
+
+ def source_type_info
+ type = @previous_reflection.foreign_type
+ source_type = @previous_reflection.options[:source_type]
+ lambda { |object| where(type => source_type) }
+ end
+ end
+
+ class RuntimeReflection < PolymorphicReflection # :nodoc:
+ attr_accessor :next
+
+ def initialize(reflection, association)
+ @reflection = reflection
+ @association = association
+ end
+
+ def klass
+ @association.klass
+ end
+
+ def table_name
+ klass.table_name
+ end
+
+ def constraints
+ @reflection.constraints
+ end
+
+ def source_type_info
+ @reflection.source_type_info
+ end
+
+ def alias_candidate(name)
+ "#{plural_name}_#{name}_join"
+ end
+
+ def alias_name
+ Arel::Table.new(table_name)
+ end
+
+ def all_includes; yield; end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 8575e5a7be..ab3debc03b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -16,16 +16,17 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
- attr_reader :table, :klass, :loaded
+ attr_reader :table, :klass, :loaded, :predicate_builder
alias :model :klass
alias :loaded? :loaded
- def initialize(klass, table, values = {})
+ def initialize(klass, table, predicate_builder, values = {})
@klass = klass
@table = table
@values = values
@offsets = {}
@loaded = false
+ @predicate_builder = predicate_builder
end
def initialize_copy(other)
@@ -304,11 +305,11 @@ module ActiveRecord
klass.current_scope = previous
end
- # Updates all records with details given if they match a set of conditions supplied, limits and order can
- # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
- # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
- # or validations. Values passed to `update_all` will not go through ActiveRecord's type-casting behavior.
- # It should receive only values that can be passed as-is to the SQL database.
+ # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
+ # statement and sends it straight to the database. It does not instantiate the involved models and it does not
+ # trigger Active Record callbacks or validations. Values passed to `update_all` will not go through
+ # ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL
+ # database.
#
# ==== Parameters
#
@@ -327,7 +328,7 @@ module ActiveRecord
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
- stmt = Arel::UpdateManager.new(arel.engine)
+ stmt = Arel::UpdateManager.new
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
stmt.table(table)
@@ -361,9 +362,21 @@ module ActiveRecord
# # Updates multiple records
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
# Person.update(people.keys, people.values)
- def update(id, attributes)
+ #
+ # # Updates multiple records from the result of a relation
+ # people = Person.where(group: 'expert')
+ # people.update(group: 'masters')
+ #
+ # Note: Updating a large number of records will run a
+ # UPDATE query for each record, which may cause a performance
+ # issue. So if it is not needed to run callbacks for each update, it is
+ # preferred to use <tt>update_all</tt> for updating all records using
+ # a single query.
+ def update(id = :all, attributes)
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
+ elsif id == :all
+ to_a.each { |record| record.update(attributes) }
else
object = find(id)
object.update(attributes)
@@ -400,7 +413,7 @@ module ActiveRecord
if conditions
where(conditions).destroy_all
else
- to_a.each {|object| object.destroy }.tap { reset }
+ to_a.each(&:destroy).tap { reset }
end
end
@@ -465,7 +478,7 @@ module ActiveRecord
if conditions
where(conditions).delete_all
else
- stmt = Arel::DeleteManager.new(arel.engine)
+ stmt = Arel::DeleteManager.new
stmt.from(table)
if joins_values.any?
@@ -568,7 +581,7 @@ module ActiveRecord
[name, binds.fetch(name.to_s) {
case where.right
when Array then where.right.map(&:val)
- else
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
where.right.val
end
}]
@@ -639,17 +652,21 @@ module ActiveRecord
preload = preload_values
preload += includes_values unless eager_loading?
- preloader = ActiveRecord::Associations::Preloader.new
+ preloader = build_preloader
preload.each do |associations|
preloader.preload @records, associations
end
- @records.each { |record| record.readonly! } if readonly_value
+ @records.each(&:readonly!) if readonly_value
@loaded = true
@records
end
+ def build_preloader
+ ActiveRecord::Associations::Preloader.new
+ end
+
def references_eager_loaded_tables?
joined_tables = arel.join_sources.map do |join|
if join.is_a?(Arel::Nodes::StringJoin)
@@ -662,7 +679,7 @@ module ActiveRecord
joined_tables += [table.name, table.table_alias]
# always convert table names to downcase as in Oracle quoted table names are in uppercase
- joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
+ joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
(references_values - joined_tables).any?
end
@@ -671,7 +688,7 @@ module ActiveRecord
return [] if string.blank?
# always convert table names to downcase as in Oracle quoted table names are in uppercase
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
- string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
+ string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_']
end
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index b069cdce7c..4f0502ae75 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -27,7 +27,7 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # * <tt>:start</tt> - Specifies the primary key value to start from.
# 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
@@ -40,8 +40,8 @@ module ActiveRecord
#
# NOTE: It's not possible to set the order. That is automatically set to
# ascending on the primary key ("id ASC") to make the batch ordering
- # work. This also means that this method only works with integer-based
- # primary keys.
+ # work. This also means that this method only works when the primary key is
+ # orderable (e.g. an integer or string).
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
@@ -77,7 +77,7 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # * <tt>:start</tt> - Specifies the primary key value to start from.
# 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
@@ -90,8 +90,8 @@ module ActiveRecord
#
# NOTE: It's not possible to set the order. That is automatically set to
# ascending on the primary key ("id ASC") to make the batch ordering
- # work. This also means that this method only works with integer-based
- # primary keys.
+ # work. This also means that this method only works when the primary key is
+ # orderable (e.g. an integer or string).
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index c8ebb41131..1d4cb1a83b 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -35,21 +35,16 @@ module ActiveRecord
#
# Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
- def count(column_name = nil, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ def count(column_name = nil)
+ calculate(:count, column_name)
end
# Calculates the average value on a given column. Returns +nil+ if there's
# no row. See +calculate+ for examples with options.
#
# Person.average(:age) # => 35.8
- def average(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:average, column_name, options)
+ def average(column_name)
+ calculate(:average, column_name)
end
# Calculates the minimum value on a given column. The value is returned
@@ -57,10 +52,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.minimum(:age) # => 7
- def minimum(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:minimum, column_name, options)
+ def minimum(column_name)
+ calculate(:minimum, column_name)
end
# Calculates the maximum value on a given column. The value is returned
@@ -68,10 +61,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.maximum(:age) # => 93
- def maximum(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:maximum, column_name, options)
+ def maximum(column_name)
+ calculate(:maximum, column_name)
end
# Calculates the sum of values on a given column. The value is returned
@@ -114,17 +105,15 @@ module ActiveRecord
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
- def calculate(operation, column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def calculate(operation, column_name)
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
column_name = attribute_alias(column_name)
end
if has_include?(column_name)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
+ construct_relation_for_association_calculations.calculate(operation, column_name)
else
- perform_calculation(operation, column_name, options)
+ perform_calculation(operation, column_name)
end
end
@@ -177,7 +166,7 @@ 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, bind_values)
+ result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
result.cast_values(klass.column_types)
end
end
@@ -196,9 +185,7 @@ module ActiveRecord
eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
end
- def perform_calculation(operation, column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def perform_calculation(operation, column_name)
operation = operation.to_s.downcase
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
@@ -317,7 +304,7 @@ module ActiveRecord
relation.group_values = group
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation, nil, bind_values)
+ calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 50f4d5c7ab..d4a8823cfe 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,6 +1,5 @@
require 'set'
require 'active_support/concern'
-require 'active_support/deprecation'
module ActiveRecord
module Delegation # :nodoc:
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index eacae73ebb..088bc203b7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -108,7 +108,7 @@ module ActiveRecord
# Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>take!</tt> accepts no arguments.
def take!
- take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Find the first record (or first N records if a parameter is supplied).
@@ -176,7 +176,7 @@ module ActiveRecord
# Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>last!</tt> accepts no arguments.
def last!
- last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Find the second record.
@@ -323,7 +323,7 @@ module ActiveRecord
# the expected number of results should be provided in the +expected_size+
# argument.
def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
- conditions = arel.where_sql
+ conditions = arel.where_sql(@klass.arel_engine)
conditions = " [#{conditions}]" if conditions
if Array(ids).size == 1
@@ -397,7 +397,7 @@ module ActiveRecord
else
if relation.limit_value
limited_ids = limited_ids_for(relation)
- limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
end
relation.except(:limit, :offset)
end
@@ -415,7 +415,7 @@ module ActiveRecord
end
def using_limitable_reflections?(reflections)
- reflections.none? { |r| r.collection? }
+ reflections.none?(&:collection?)
end
protected
@@ -498,7 +498,7 @@ module ActiveRecord
end
def find_nth!(index)
- find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
def find_nth_with_limit(offset, limit)
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index a27f990f74..afb0b208c3 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# build a relation to merge in rather than directly merging
# the values.
def other
- other = Relation.create(relation.klass, relation.table)
+ other = Relation.create(relation.klass, relation.table, relation.predicate_builder)
hash.each { |k, v|
if k == :joins
if Hash === v
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index e4b6b49087..567efce8ae 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,82 +1,44 @@
module ActiveRecord
class PredicateBuilder # :nodoc:
- @handlers = []
-
- autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler'
- autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
-
- def self.resolve_column_aliases(klass, hash)
- hash = hash.dup
- hash.keys.grep(Symbol) do |key|
- if klass.attribute_alias? key
- hash[klass.attribute_alias(key)] = hash.delete key
- end
- end
- hash
+ require 'active_record/relation/predicate_builder/array_handler'
+ require 'active_record/relation/predicate_builder/association_query_handler'
+ require 'active_record/relation/predicate_builder/base_handler'
+ require 'active_record/relation/predicate_builder/basic_object_handler'
+ require 'active_record/relation/predicate_builder/class_handler'
+ require 'active_record/relation/predicate_builder/range_handler'
+ require 'active_record/relation/predicate_builder/relation_handler'
+
+ delegate :resolve_column_aliases, to: :table
+
+ def initialize(table)
+ @table = table
+ @handlers = []
+
+ register_handler(BasicObject, BasicObjectHandler.new(self))
+ register_handler(Class, ClassHandler.new(self))
+ register_handler(Base, BaseHandler.new(self))
+ register_handler(Range, RangeHandler.new(self))
+ register_handler(Relation, RelationHandler.new)
+ register_handler(Array, ArrayHandler.new(self))
+ register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
end
- def self.build_from_hash(klass, attributes, default_table)
- queries = []
-
- attributes.each do |column, value|
- table = default_table
-
- if value.is_a?(Hash)
- if value.empty?
- queries << '1=0'
- else
- table = Arel::Table.new(column, default_table.engine)
- association = klass._reflect_on_association(column)
-
- value.each do |k, v|
- queries.concat expand(association && association.klass, table, k, v)
- end
- end
- else
- column = column.to_s
-
- if column.include?('.')
- table_name, column = column.split('.', 2)
- table = Arel::Table.new(table_name, default_table.engine)
- end
-
- queries.concat expand(klass, table, column, value)
- end
- end
-
- queries
+ def build_from_hash(attributes)
+ attributes = convert_dot_notation_to_hash(attributes.stringify_keys)
+ expand_from_hash(attributes)
end
- def self.expand(klass, table, column, value)
- queries = []
-
+ def expand(column, value)
# Find the foreign key when using queries such as:
# Post.where(author: author)
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && reflection = klass._reflect_on_association(column)
- if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
- queries << build(table[reflection.foreign_type], base_class)
- end
-
- column = reflection.foreign_key
+ if table.associated_with?(column)
+ value = AssociationQueryValue.new(table.associated_table(column), value)
end
- queries << build(table[column], value)
- queries
- end
-
- def self.polymorphic_base_class_from_value(value)
- case value
- when Relation
- value.klass.base_class
- when Array
- val = value.compact.first
- val.class.base_class if val.is_a?(Base)
- when Base
- value.class.base_class
- end
+ build(table.arel_attribute(column), value)
end
def self.references(attributes)
@@ -101,26 +63,49 @@ module ActiveRecord
# )
# end
# ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
- def self.register_handler(klass, handler)
+ def register_handler(klass, handler)
@handlers.unshift([klass, handler])
end
- register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
- # FIXME: I think we need to deprecate this behavior
- register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
- register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
- register_handler(Range, ->(attribute, value) { attribute.between(value) })
- register_handler(Relation, RelationHandler.new)
- register_handler(Array, ArrayHandler.new)
-
- def self.build(attribute, value)
+ def build(attribute, value)
handler_for(value).call(attribute, value)
end
- private_class_method :build
- def self.handler_for(object)
+ protected
+
+ attr_reader :table
+
+ def expand_from_hash(attributes)
+ return ["1=0"] if attributes.empty?
+
+ attributes.flat_map do |key, value|
+ if value.is_a?(Hash)
+ builder = self.class.new(table.associated_table(key))
+ builder.expand_from_hash(value)
+ else
+ expand(key, value)
+ end
+ end
+ end
+
+ private
+
+ def convert_dot_notation_to_hash(attributes)
+ dot_notation = attributes.keys.select { |s| s.include?(".") }
+
+ dot_notation.each do |key|
+ table_name, column_name = key.split(".")
+ value = attributes.delete(key)
+ attributes[table_name] ||= {}
+
+ attributes[table_name] = attributes[table_name].merge(column_name => value)
+ end
+
+ attributes
+ end
+
+ def handler_for(object)
@handlers.detect { |klass, _| klass === object }.last
end
- private_class_method :handler_for
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index b8f3285c3e..95dbd6a77f 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -1,22 +1,14 @@
-require 'active_support/core_ext/string/filters'
-
module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
def call(attribute, value)
values = value.map { |x| x.is_a?(Base) ? x.id : x }
nils, values = values.partition(&:nil?)
- if values.any? { |val| val.is_a?(Array) }
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a nested array to Active Record finder methods is
- deprecated and will be removed. Flatten your array before using
- it for 'IN' conditions.
- MSG
-
- values = values.flatten
- end
-
return attribute.in([]) if values.empty? && nils.empty?
ranges, values = values.partition { |v| v.is_a?(Range) }
@@ -24,20 +16,24 @@ module ActiveRecord
values_predicate =
case values.length
when 0 then NullPredicate
- when 1 then attribute.eq(values.first)
+ when 1 then predicate_builder.build(attribute, values.first)
else attribute.in(values)
end
unless nils.empty?
- values_predicate = values_predicate.or(attribute.eq(nil))
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
end
- array_predicates = ranges.map { |range| attribute.between(range) }
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
array_predicates.unshift(values_predicate)
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
- module NullPredicate
+ protected
+
+ attr_reader :predicate_builder
+
+ module NullPredicate # :nodoc:
def self.or(other)
other
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
new file mode 100644
index 0000000000..aabcf20c1d
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -0,0 +1,58 @@
+module ActiveRecord
+ class PredicateBuilder
+ class AssociationQueryHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ queries = {}
+
+ table = value.associated_table
+ if value.base_class
+ queries[table.association_foreign_type] = value.base_class.name
+ end
+
+ queries[table.association_foreign_key] = value.ids
+ predicate_builder.build_from_hash(queries)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+
+ class AssociationQueryValue # :nodoc:
+ attr_reader :associated_table, :value
+
+ def initialize(associated_table, value)
+ @associated_table = associated_table
+ @value = value
+ end
+
+ def ids
+ value
+ end
+
+ def base_class
+ if associated_table.polymorphic_association?
+ @base_class ||= polymorphic_base_class_from_value
+ end
+ end
+
+ private
+
+ def polymorphic_base_class_from_value
+ case value
+ when Relation
+ value.klass.base_class
+ when Array
+ val = value.compact.first
+ val.class.base_class if val.is_a?(Base)
+ when Base
+ value.class.base_class
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
new file mode 100644
index 0000000000..6fa5b16f73
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class BaseHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ predicate_builder.build(attribute, value.id)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
new file mode 100644
index 0000000000..6cec75dc0a
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class BasicObjectHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ attribute.eq(value)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
new file mode 100644
index 0000000000..ed313fc9d4
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ class PredicateBuilder
+ class ClassHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ print_deprecation_warning
+ predicate_builder.build(attribute, value.name)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+
+ private
+
+ def print_deprecation_warning
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing a class as a value in an Active Record query is deprecated and
+ will be removed. Pass a string instead.
+ MSG
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
new file mode 100644
index 0000000000..1b3849e3ad
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class RangeHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ attribute.between(value)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index cb4e33f1b1..6892cc114c 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -62,15 +62,14 @@ module ActiveRecord
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
- check_cached_relation
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ assert_mutability! # assert_mutability!
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
CODE
end
@@ -85,23 +84,12 @@ module ActiveRecord
Relation::SINGLE_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_value=(value) # def readonly_value=(value)
- raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
- check_cached_relation
+ assert_mutability! # assert_mutability!
@values[:#{name}] = value # @values[:readonly] = value
end # end
CODE
end
- def check_cached_relation # :nodoc:
- if defined?(@arel) && @arel
- @arel = nil
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Modifying already cached Relation. The cache will be reset. Use a
- cloned Relation to prevent this warning.
- MSG
- end
- end
-
def create_with_value # :nodoc:
@values[:create_with] || {}
end
@@ -427,14 +415,12 @@ module ActiveRecord
# => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
def joins(*args)
check_if_method_has_arguments!(:joins, args)
-
- args.compact!
- args.flatten!
-
spawn.joins!(*args)
end
def joins!(*args) # :nodoc:
+ args.compact!
+ args.flatten!
self.joins_values += args
self
end
@@ -859,8 +845,13 @@ module ActiveRecord
private
+ def assert_mutability!
+ raise ImmutableRelation if @loaded
+ raise ImmutableRelation if defined?(@arel) && @arel
+ end
+
def build_arel
- arel = Arel::SelectManager.new(table.engine, table)
+ arel = Arel::SelectManager.new(table)
build_joins(arel, joins_values.flatten) unless joins_values.empty?
@@ -911,7 +902,7 @@ module ActiveRecord
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 == target_value
+ subrelation.name.to_s == target_value
end
end
@@ -949,7 +940,7 @@ module ActiveRecord
when String, Array
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
+ opts = predicate_builder.resolve_column_aliases(opts)
tmp_opts, bind_values = create_binds(opts)
self.bind_values += bind_values
@@ -957,7 +948,7 @@ module ActiveRecord
attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
add_relations_to_bind_values(attributes)
- PredicateBuilder.build_from_hash(klass, attributes, table)
+ predicate_builder.build_from_hash(attributes)
else
[opts]
end
@@ -1059,8 +1050,13 @@ module ActiveRecord
def build_select(arel, selects)
if !selects.empty?
expanded_select = selects.map do |field|
- columns_hash.key?(field.to_s) ? arel_table[field] : field
+ if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
+ arel_table[field]
+ else
+ field
+ end
end
+
arel.project(*expanded_select)
else
arel.project(@klass.arel_table[Arel.star])
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 57d66bce4b..01bddea6c9 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -67,7 +67,7 @@ module ActiveRecord
private
def relation_with(values) # :nodoc:
- result = Relation.create(klass, table, values)
+ result = Relation.create(klass, table, predicate_builder, values)
result.extend(*extending_values) if extending_values.any?
result
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 6a130c145b..768a72a947 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -72,41 +72,14 @@ module ActiveRecord
expanded_attrs
end
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
- # { name: "foo'bar", group_id: 4 }
- # # => "name='foo''bar' and group_id= 4"
- # { status: nil, group_id: [1,2,3] }
- # # => "status IS NULL and group_id IN (1,2,3)"
- # { age: 13..18 }
- # # => "age BETWEEN 13 AND 18"
- # { 'other_records.id' => 7 }
- # # => "`other_records`.`id` = 7"
- # { other_records: { id: 7 } }
- # # => "`other_records`.`id` = 7"
- # And for value objects on a composed_of relationship:
- # { address: Address.new("123 abc st.", "chicago") }
- # # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
- ActiveSupport::Deprecation.warn(<<-EOWARN)
-sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
- EOWARN
- attrs = PredicateBuilder.resolve_column_aliases self, attrs
- attrs = expand_hash_conditions_for_aggregates(attrs)
-
- table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
- PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
- connection.visitor.compile b
- }.join(' AND ')
- end
- alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
# { status: nil, group_id: 1 }
# # => "status = NULL , group_id = 1"
def sanitize_sql_hash_for_assignment(attrs, table)
c = connection
attrs.map do |attr, value|
- "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
+ value = type_for_attribute(attr.to_s).type_cast_for_database(value)
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
end.join(', ')
end
@@ -162,10 +135,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
end
end
- def quote_bound_value(value, c = connection, column = nil) #:nodoc:
- if column
- c.quote(value, column)
- elsif value.respond_to?(:map) && !value.acts_like?(:string)
+ def quote_bound_value(value, c = connection) #:nodoc:
+ if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
else
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 261fb9d992..da95920571 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -44,7 +44,6 @@ module ActiveRecord
def initialize(connection, options = {})
@connection = connection
- @types = @connection.native_database_types
@version = Migrator::current_version rescue nil
@options = options
end
@@ -118,23 +117,24 @@ HEADER
if pkcol
if pk != 'id'
tbl.print %Q(, primary_key: "#{pk}")
- elsif pkcol.sql_type == 'bigint'
- tbl.print ", id: :bigserial"
- elsif pkcol.sql_type == 'uuid'
- tbl.print ", id: :uuid"
- tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
+ end
+ pkcolspec = @connection.column_spec_for_primary_key(pkcol)
+ if pkcolspec
+ pkcolspec.each do |key, value|
+ tbl.print ", #{key}: #{value}"
+ end
end
else
tbl.print ", id: false"
end
- tbl.print ", force: true"
+ tbl.print ", force: :cascade"
tbl.puts " do |t|"
# then dump all non-primary key columns
column_specs = columns.map do |column|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
- @connection.column_spec(column, @types)
+ @connection.column_spec(column)
end.compact
# find all migration keys used in this table
@@ -244,12 +244,7 @@ HEADER
def ignored?(table_name)
['schema_migrations', ignore_tables].flatten.any? do |ignored|
- case ignored
- when String; remove_prefix_and_suffix(table_name) == ignored
- when Regexp; remove_prefix_and_suffix(table_name) =~ ignored
- else
- raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
- end
+ ignored === remove_prefix_and_suffix(table_name)
end
end
end
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
new file mode 100644
index 0000000000..23d4292cbb
--- /dev/null
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -0,0 +1,49 @@
+module ActiveRecord
+ module SecureToken
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Example using has_secure_token
+ #
+ # # Schema: User(toke:string, auth_token:string)
+ # class User < ActiveRecord::Base
+ # has_secure_token
+ # has_secure_token :auth_token
+ # end
+ #
+ # user = User.new
+ # user.save
+ # user.token # => "44539a6a59835a4ee9d7b112"
+ # user.auth_token # => "e2426a93718d1817a43abbaa"
+ # user.regenerate_token # => true
+ # user.regenerate_auth_token # => true
+ #
+ # SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely.
+ # We'll check to see if the generated token has been used already using #exists?, and retry up to 10
+ # times to find another unused token. After that a RuntimeError is raised if the problem persists.
+ #
+ # Note that it's still possible to generate a race condition in the database in the same way that
+ # validates_presence_of can. You're encouraged to add a unique index in the database to deal with
+ # this even more unlikely scenario.
+ def has_secure_token(attribute = :token)
+ # Load securerandom only when has_secure_key is used.
+ require 'securerandom'
+ define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
+ before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) }
+ end
+
+ def generate_unique_secure_token(attribute)
+ 10.times do |i|
+ SecureRandom.hex(12).tap do |token|
+ if exists?(attribute => token)
+ raise "Couldn't generate a unique token in 10 attempts!" if i == 9
+ else
+ return token
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index bd9079b596..48c12dcf9f 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -11,7 +11,7 @@ module ActiveRecord #:nodoc:
def serializable_hash(options = nil)
options = options.try(:clone) || {}
- options[:except] = Array(options[:except]).map { |n| n.to_s }
+ options[:except] = Array(options[:except]).map(&:to_s)
options[:except] |= Array(self.class.inheritance_column)
super(options)
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index aece446384..192a19f05d 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -1,22 +1,33 @@
module ActiveRecord
# Statement cache is used to cache a single statement in order to avoid creating the AST again.
- # Initializing the cache is done by passing the statement in the initialization block:
+ # Initializing the cache is done by passing the statement in the create block:
#
- # cache = ActiveRecord::StatementCache.new do
- # Book.where(name: "my book").limit(100)
+ # cache = StatementCache.create(Book.connection) do |params|
+ # Book.where(name: "my book").where("author_id > 3")
# end
#
# The cached statement is executed by using the +execute+ method:
#
- # cache.execute
+ # cache.execute([], Book, Book.connection)
#
# The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
# Database is queried when +to_a+ is called on the relation.
- class StatementCache
- class Substitute; end
+ #
+ # If you want to cache the statement without the values you can use the +bind+ method of the
+ # block parameter.
+ #
+ # cache = StatementCache.create(Book.connection) do |params|
+ # Book.where(name: params.bind)
+ # end
+ #
+ # And pass the bind values as the first argument of +execute+ call.
+ #
+ # cache.execute(["my book"], Book, Book.connection)
+ class StatementCache # :nodoc:
+ class Substitute; end # :nodoc:
- class Query
+ class Query # :nodoc:
def initialize(sql)
@sql = sql
end
@@ -26,7 +37,7 @@ module ActiveRecord
end
end
- class PartialQuery < Query
+ class PartialQuery < Query # :nodoc:
def initialize values
@values = values
@indexes = values.each_with_index.find_all { |thing,i|
@@ -51,11 +62,11 @@ module ActiveRecord
PartialQuery.new collected
end
- class Params
+ class Params # :nodoc:
def bind; Substitute.new; end
end
- class BindMap
+ class BindMap # :nodoc:
def initialize(bind_values)
@indexes = []
@bind_values = bind_values
@@ -68,7 +79,7 @@ module ActiveRecord
end
def bind(values)
- bvs = @bind_values.map { |pair| pair.dup }
+ bvs = @bind_values.map(&:dup)
@indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
bvs
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 3c291f28e3..919bc58ba5 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -15,11 +15,15 @@ module ActiveRecord
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
#
- # NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
+ # NOTE: If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
# the serialization provided by +store+. Simply use +store_accessor+ instead to generate
# the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
# using a symbol.
#
+ # NOTE: The default validations with the exception of +uniqueness+ will work.
+ # For example, if you want to check for +uniqueness+ with +hstore+ you will
+ # need to use a custom validation to handle it.
+ #
# Examples:
#
# class User < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
new file mode 100644
index 0000000000..11e33e8dfe
--- /dev/null
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -0,0 +1,53 @@
+module ActiveRecord
+ class TableMetadata # :nodoc:
+ delegate :foreign_type, :foreign_key, to: :association, prefix: true
+
+ def initialize(klass, arel_table, association = nil)
+ @klass = klass
+ @arel_table = arel_table
+ @association = association
+ end
+
+ def resolve_column_aliases(hash)
+ hash = hash.dup
+ hash.keys.grep(Symbol) do |key|
+ if klass.attribute_alias? key
+ hash[klass.attribute_alias(key)] = hash.delete key
+ end
+ end
+ hash
+ end
+
+ def arel_attribute(column_name)
+ arel_table[column_name]
+ end
+
+ def associated_with?(association_name)
+ klass && klass._reflect_on_association(association_name)
+ end
+
+ def associated_table(table_name)
+ return self if table_name == arel_table.name
+
+ association = klass._reflect_on_association(table_name)
+ if association && !association.polymorphic?
+ association_klass = association.klass
+ arel_table = association_klass.arel_table
+ else
+ type_caster = TypeCaster::Connection.new(klass.connection, table_name)
+ association_klass = nil
+ arel_table = Arel::Table.new(table_name, type_caster: type_caster)
+ end
+
+ TableMetadata.new(association_klass, arel_table, association)
+ end
+
+ def polymorphic_association?
+ association && association.polymorphic?
+ end
+
+ protected
+
+ attr_reader :klass, :arel_table, :association
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 1228de2bfd..69aceb66b1 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -188,44 +188,39 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
end
- def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- This method will act on a specific connection in the future.
- To act on the current connection, use `load_schema_current` instead.
- MSG
-
- load_schema_current(format, file)
- end
-
- def schema_file(format = ActiveSupport::Base.schema_format)
- case format
- when :ruby
- File.join(db_dir, "schema.rb")
- when :sql
- File.join(db_dir, "structure.sql")
- end
- end
-
- # This method is the successor of +load_schema+. We should rename it
- # after +load_schema+ went through a deprecation cycle. (Rails > 4.2)
- def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
+ def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
file ||= schema_file(format)
case format
when :ruby
check_schema_file(file)
- purge(configuration)
ActiveRecord::Base.establish_connection(configuration)
load(file)
when :sql
check_schema_file(file)
- purge(configuration)
structure_load(configuration, file)
else
raise ArgumentError, "unknown format #{format.inspect}"
end
end
+ def load_schema_for(*args)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ This method was renamed to `#load_schema` and will be removed in the future.
+ Use `#load_schema` instead.
+ MSG
+ load_schema(*args)
+ end
+
+ def schema_file(format = ActiveSupport::Base.schema_format)
+ case format
+ when :ruby
+ File.join(db_dir, "schema.rb")
+ when :sql
+ File.join(db_dir, "structure.sql")
+ end
+ end
+
def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
if File.exist?(file || schema_file(format))
load_schema_current(format, file, environment)
@@ -234,7 +229,7 @@ module ActiveRecord
def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
each_current_configuration(environment) { |configuration|
- load_schema_for configuration, format, file
+ load_schema configuration, format, file
}
ActiveRecord::Base.establish_connection(environment.to_sym)
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 936a18d99a..20e4235788 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -57,8 +57,8 @@ module ActiveRecord
super
end
- def _update_record(*args)
- if should_record_timestamps?
+ def _update_record(*args, touch: true, **options)
+ if touch && should_record_timestamps?
current_time = current_time_from_proper_timezone
timestamp_attributes_for_update_in_model.each do |column|
@@ -67,7 +67,7 @@ module ActiveRecord
write_attribute(column, current_time)
end
end
- super
+ super(*args)
end
def should_record_timestamps?
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index f92e1de03b..1c48196762 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -2,24 +2,12 @@ module ActiveRecord
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
extend ActiveSupport::Concern
+ #:nodoc:
ACTIONS = [:create, :destroy, :update]
- CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \
- "within `after_rollback`/`after_commit` callbacks and only print them to " \
- "the logs. In the next version, these errors will no longer be suppressed. " \
- "Instead, the errors will propagate normally just like in other Active " \
- "Record callbacks.\n" \
- "\n" \
- "You can opt into the new behavior and remove this warning by setting:\n" \
- "\n" \
- " config.active_record.raise_in_transactional_callbacks = true\n\n"
included do
define_callbacks :commit, :rollback,
- terminator: ->(_, result) { result == false },
scope: [:kind, :name]
-
- mattr_accessor :raise_in_transactional_callbacks, instance_writer: false
- self.raise_in_transactional_callbacks = false
end
# = Active Record Transactions
@@ -235,9 +223,6 @@ module ActiveRecord
def after_commit(*args, &block)
set_options_for_callbacks!(args)
set_callback(:commit, :after, *args, &block)
- unless ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
- end
end
# This callback is called after a create, update, or destroy are rolled back.
@@ -246,9 +231,16 @@ module ActiveRecord
def after_rollback(*args, &block)
set_options_for_callbacks!(args)
set_callback(:rollback, :after, *args, &block)
- unless ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
- end
+ end
+
+ def raise_in_transactional_callbacks
+ ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.')
+ true
+ end
+
+ def raise_in_transactional_callbacks=(value)
+ ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated, has no effect and will be removed without replacement.')
+ value
end
private
@@ -265,7 +257,7 @@ module ActiveRecord
def assert_valid_transaction_action(actions)
if (actions - ACTIONS).any?
- raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
+ raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
end
end
end
@@ -358,14 +350,12 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
@_start_transaction_state[:id] = id
- unless @_start_transaction_state.include?(:new_record)
- @_start_transaction_state[:new_record] = @new_record
- end
- unless @_start_transaction_state.include?(:destroyed)
- @_start_transaction_state[:destroyed] = @destroyed
- end
+ @_start_transaction_state.reverse_merge!(
+ new_record: @new_record,
+ destroyed: @destroyed,
+ frozen?: frozen?,
+ )
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- @_start_transaction_state[:frozen?] = frozen?
end
# Clear the new record state and id of a record.
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index e5acbbb6b3..250e8d5b23 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -17,6 +17,7 @@ require 'active_record/type/serialized'
require 'active_record/type/string'
require 'active_record/type/text'
require 'active_record/type/time'
+require 'active_record/type/unsigned_integer'
require 'active_record/type/type_map'
require 'active_record/type/hash_lookup_type_map'
diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb
index 978d16d524..f6a75512fd 100644
--- a/activerecord/lib/active_record/type/boolean.rb
+++ b/activerecord/lib/active_record/type/boolean.rb
@@ -10,19 +10,8 @@ module ActiveRecord
def cast_value(value)
if value == ''
nil
- elsif ConnectionAdapters::Column::TRUE_VALUES.include?(value)
- true
else
- if !ConnectionAdapters::Column::FALSE_VALUES.include?(value)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You attempted to assign a value which is not explicitly `true` or `false`
- to a boolean column. Currently this value casts to `false`. This will
- change to match Ruby's semantics, and will cast to `true` in Rails 5.
- If you would like to maintain the current behavior, you should
- explicitly handle the values you would like cast to `false`.
- MSG
- end
- false
+ !ConnectionAdapters::Column::FALSE_VALUES.include?(value)
end
end
end
diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb
index 5f19608a33..0a737815bc 100644
--- a/activerecord/lib/active_record/type/date_time.rb
+++ b/activerecord/lib/active_record/type/date_time.rb
@@ -11,7 +11,11 @@ module ActiveRecord
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
if value.acts_like?(:time)
- value.send(zone_conversion_method)
+ if value.respond_to?(zone_conversion_method)
+ value.send(zone_conversion_method)
+ else
+ value
+ end
else
super
end
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index d10778eeb6..7b2bee2c42 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -16,7 +16,7 @@ module ActiveRecord
def cast_value(value)
case value
when ::Float
- BigDecimal(value, float_precision)
+ convert_float_to_big_decimal(value)
when ::Numeric, ::String
BigDecimal(value, precision.to_i)
else
@@ -28,6 +28,14 @@ module ActiveRecord
end
end
+ def convert_float_to_big_decimal(value)
+ if precision
+ BigDecimal(value, float_precision)
+ else
+ value.to_d
+ end
+ end
+
def float_precision
if precision.to_i > ::Float::DIG + 1
::Float::DIG + 1
diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
index 2a146d38a9..82d9327fc0 100644
--- a/activerecord/lib/active_record/type/hash_lookup_type_map.rb
+++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
@@ -1,36 +1,16 @@
module ActiveRecord
module Type
class HashLookupTypeMap < TypeMap # :nodoc:
-
- def initialize
- @cache = {}
- super
- end
-
delegate :key?, to: :@mapping
- def lookup(type, *args)
- @mapping.fetch(type, proc { default_value }).call(type, *args)
- end
-
- def fetch(type, *args, &block)
- cache = (@cache[type] ||= {})
- resolved = cache[args]
-
- unless resolved
- resolved = cache[args] = @mapping.fetch(type, block).call(type, *args)
- end
-
- resolved
- end
-
def alias_type(type, alias_type)
register_type(type) { |_, *args| lookup(alias_type, *args) }
end
- def register_type(key, value=nil, &block)
- @cache = {}
- super(key, value, &block)
+ private
+
+ def perform_fetch(type, *args, &block)
+ @mapping.fetch(type, block).call(type, *args)
end
end
end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb
index 36bbd9cd5e..fc260a081a 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activerecord/lib/active_record/type/integer.rb
@@ -5,7 +5,7 @@ module ActiveRecord
def initialize(*)
super
- @range = -max_value...max_value
+ @range = min_value...max_value
end
def type
@@ -38,7 +38,7 @@ module ActiveRecord
def ensure_in_range(value)
unless range.cover?(value)
- raise RangeError, "#{value} is too large for #{self.class} with limit #{limit || 4}"
+ raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || 4}"
end
end
@@ -46,6 +46,10 @@ module ActiveRecord
limit = self.limit || 4
1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign
end
+
+ def min_value
+ -max_value
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
index fa43266504..674f996f38 100644
--- a/activerecord/lib/active_record/type/numeric.rb
+++ b/activerecord/lib/active_record/type/numeric.rb
@@ -29,7 +29,7 @@ module ActiveRecord
# '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/
+ value.to_s !~ /\A-?\d+\.?\d*\z/
end
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 3191a868ef..3cac03464e 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- subtype.changed_in_place?(raw_old_value, coder.dump(value))
+ subtype.changed_in_place?(raw_old_value, type_cast_for_database(value))
end
def accessor
diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb
index fbc0af2c5a..cf95e25be0 100644
--- a/activerecord/lib/active_record/type/string.rb
+++ b/activerecord/lib/active_record/type/string.rb
@@ -21,6 +21,10 @@ module ActiveRecord
end
end
+ def text?
+ true
+ end
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index 88c5f9c497..09f5ba6b74 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -1,24 +1,28 @@
+require 'thread_safe'
+
module ActiveRecord
module Type
class TypeMap # :nodoc:
def initialize
@mapping = {}
+ @cache = ThreadSafe::Cache.new do |h, key|
+ h.fetch_or_store(key, ThreadSafe::Cache.new)
+ end
end
def lookup(lookup_key, *args)
- matching_pair = @mapping.reverse_each.detect do |key, _|
- key === lookup_key
- end
+ fetch(lookup_key, *args) { default_value }
+ end
- if matching_pair
- matching_pair.last.call(lookup_key, *args)
- else
- default_value
+ def fetch(lookup_key, *args, &block)
+ @cache[lookup_key].fetch_or_store(args) do
+ perform_fetch(lookup_key, *args, &block)
end
end
def register_type(key, value = nil, &block)
raise ::ArgumentError unless value || block
+ @cache.clear
if block
@mapping[key] = block
@@ -40,6 +44,18 @@ module ActiveRecord
private
+ def perform_fetch(lookup_key, *args)
+ matching_pair = @mapping.reverse_each.detect do |key, _|
+ key === lookup_key
+ end
+
+ if matching_pair
+ matching_pair.last.call(lookup_key, *args)
+ else
+ yield lookup_key, *args
+ end
+ end
+
def default_value
@default_value ||= Value.new
end
diff --git a/activerecord/lib/active_record/type/unsigned_integer.rb b/activerecord/lib/active_record/type/unsigned_integer.rb
new file mode 100644
index 0000000000..ed3e527483
--- /dev/null
+++ b/activerecord/lib/active_record/type/unsigned_integer.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module Type
+ class UnsignedInteger < Integer # :nodoc:
+ private
+
+ def max_value
+ super * 2
+ end
+
+ def min_value
+ 0
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 9456a4a56c..60ae47db3d 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -50,6 +50,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
@@ -91,8 +95,8 @@ module ActiveRecord
# 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`.
+ # +type_cast_from_database+ and +type_cast_from_user+ for all values
+ # except +nil+.
def cast_value(value) # :doc:
value
end
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
new file mode 100644
index 0000000000..63ba10c289
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -0,0 +1,7 @@
+require 'active_record/type_caster/map'
+require 'active_record/type_caster/connection'
+
+module ActiveRecord
+ module TypeCaster
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
new file mode 100644
index 0000000000..9e4a130b40
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module TypeCaster
+ class Connection
+ def initialize(connection, table_name)
+ @connection = connection
+ @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)
+ end
+
+ protected
+
+ attr_reader :connection, :table_name
+
+ private
+
+ def type_for(attribute_name)
+ if connection.schema_cache.table_exists?(table_name)
+ column_for(attribute_name).cast_type
+ else
+ Type::Value.new
+ 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
new file mode 100644
index 0000000000..03c9e8ff83
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module TypeCaster
+ class Map
+ def initialize(types)
+ @types = types
+ end
+
+ 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)
+ end
+
+ protected
+
+ attr_reader :types
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 7f7d49cdb4..f27adc9c40 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -5,13 +5,14 @@ module ActiveRecord
# +record+ method to retrieve the record which did not validate.
#
# begin
- # complex_operation_that_calls_save!_internally
+ # complex_operation_that_internally_calls_save!
# rescue ActiveRecord::RecordInvalid => invalid
# puts invalid.record.errors
# end
class RecordInvalid < ActiveRecordError
- attr_reader :record # :nodoc:
- def initialize(record) # :nodoc:
+ attr_reader :record
+
+ def initialize(record)
@record = record
errors = @record.errors.full_messages.join(", ")
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
@@ -87,3 +88,4 @@ end
require "active_record/validations/associated"
require "active_record/validations/uniqueness"
require "active_record/validations/presence"
+require "active_record/validations/length"
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
new file mode 100644
index 0000000000..ef5a6cbbe7
--- /dev/null
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module Validations
+ class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
+ def validate_each(record, attribute, association_or_value)
+ 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
+ end
+
+ module ClassMethods
+ # See <tt>ActiveModel::Validation::LengthValidator</tt> for more information.
+ def validates_length_of(*attr_names)
+ validates_with LengthValidator, _merge_attributes(attr_names)
+ end
+
+ alias_method :validates_size_of, :validates_length_of
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index c7aa814ba8..61b30749d9 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -8,7 +8,7 @@ module ActiveRecord
associated_records = Array.wrap(record.send(attribute))
# Superclass validates presence. Ensure present records aren't about to be destroyed.
- if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
+ if associated_records.present? && associated_records.all?(&:marked_for_destruction?)
record.errors.add(attribute, :blank, options)
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2dba4c7b94..f52f91e89c 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -16,9 +16,8 @@ module ActiveRecord
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
+ relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted?
relation = scope_relation(record, table, relation)
- relation = finder_class.unscoped.where(relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
if relation.exists?
@@ -60,17 +59,21 @@ module ActiveRecord
end
column = klass.columns_hash[attribute_name]
- value = klass.connection.type_cast(value, column)
+ value = klass.type_for_attribute(attribute_name).type_cast_for_database(value)
+ value = klass.connection.type_cast(value)
if value.is_a?(String) && column.limit
value = value.to_s[0, column.limit]
end
- if !options[:case_sensitive] && value.is_a?(String)
+ value = Arel::Nodes::Quoted.new(value)
+
+ comparison = if !options[:case_sensitive] && value && column.text?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
+ klass.unscoped.where(comparison)
end
def scope_relation(record, table, relation)
@@ -79,9 +82,9 @@ module ActiveRecord
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
else
- scope_value = record.read_attribute(scope_item)
+ scope_value = record._read_attribute(scope_item)
end
- relation = relation.and(table[scope_item].eq(scope_value))
+ relation = relation.where(scope_item => scope_value)
end
relation
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
index f7bf6987c4..fd94a2d038 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
@@ -9,7 +9,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<% end -%>
<% end -%>
<% if options[:timestamps] %>
- t.timestamps null: false
+ t.timestamps
<% end -%>
end
<% attributes_with_index.each do |attribute| -%>
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 6f84bae432..99e3d7021d 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -213,6 +213,16 @@ module ActiveRecord
test "type_to_sql returns a String for unmapped types" do
assert_equal "special_db_type", @connection.type_to_sql(:special_db_type)
end
+
+ unless current_adapter?(:PostgreSQLAdapter)
+ def test_log_invalid_encoding
+ assert_raise ActiveRecord::StatementInvalid do
+ @connection.send :log, "SELECT 'ы' FROM DUAL" do
+ raise 'ы'.force_encoding(Encoding::ASCII_8BIT)
+ end
+ end
+ end
+ end
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index a84673e452..6577d56240 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -92,7 +92,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
with_real_execute do
begin
ActiveRecord::Base.connection.create_table :delete_me
- ActiveRecord::Base.connection.add_timestamps :delete_me
+ ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
ensure
@@ -107,7 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.create_table :delete_me do |t|
t.timestamps null: true
end
- ActiveRecord::Base.connection.remove_timestamps :delete_me
+ ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true }
assert !column_present?('delete_me', 'updated_at', 'datetime')
assert !column_present?('delete_me', 'created_at', 'datetime')
ensure
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 52eebe1886..ce01b16362 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -47,9 +47,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert !@connection.active?
# Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ @fixture_connections.each(&:verify!)
end
def test_successful_reconnection_after_timeout_with_manual_reconnect
diff --git a/activerecord/test/cases/adapters/mysql/datetime_test.rb b/activerecord/test/cases/adapters/mysql/datetime_test.rb
new file mode 100644
index 0000000000..ae00f4e131
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/datetime_test.rb
@@ -0,0 +1,87 @@
+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 28106d3772..85db8f4614 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -99,6 +99,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do
+ assert_nil @conn.primary_key('ex')
+ end
+ end
+
def test_tinyint_integer_typecasting
with_example_table '`status` TINYINT(4)' do
insert(@conn, { 'status' => 2 }, 'ex')
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index d8a954efa8..a2206153e9 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -9,15 +9,11 @@ module ActiveRecord
end
def test_type_cast_true
- c = Column.new(nil, 1, Type::Boolean.new)
- assert_equal 1, @conn.type_cast(true, nil)
- assert_equal 1, @conn.type_cast(true, c)
+ assert_equal 1, @conn.type_cast(true)
end
def test_type_cast_false
- c = Column.new(nil, 1, Type::Boolean.new)
- assert_equal 0, @conn.type_cast(false, nil)
- assert_equal 0, @conn.type_cast(false, c)
+ assert_equal 0, @conn.type_cast(false)
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 61ae0abfd1..403f7cbc74 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -101,7 +101,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
- assert(gs.collect{|x| x.id}.sort == [2, 3])
+ assert(gs.collect(&:id).sort == [2, 3])
end
# has_and_belongs_to_many with reserved-word table name
@@ -110,7 +110,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
- assert(s.collect{|x|x.id}.sort == [1, 2])
+ assert(s.collect(&:id).sort == [1, 2])
end
# activerecord model introspection with reserved-word table and column names
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index 87c5277e64..ab547747df 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -81,7 +81,7 @@ module ActiveRecord
table = 'key_tests'
- indexes = @connection.indexes(table).sort_by {|i| i.name}
+ indexes = @connection.indexes(table).sort_by(&:name)
assert_equal 3,indexes.size
index_a = indexes.select{|i| i.name == index_a_name}[0]
diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
new file mode 100644
index 0000000000..8f521e9181
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+
+class UnsignedTypeTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class UnsignedType < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table("unsigned_types", force: true) do |t|
+ t.column :unsigned_integer, "int unsigned"
+ end
+ end
+
+ teardown do
+ @connection.drop_table "unsigned_types"
+ end
+
+ test "unsigned int max value is in range" do
+ assert expected = UnsignedType.create(unsigned_integer: 4294967295)
+ assert_equal expected, UnsignedType.find_by(unsigned_integer: 4294967295)
+ end
+
+ test "minus value is out of range" do
+ assert_raise(RangeError) do
+ UnsignedType.create(unsigned_integer: -10)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 49cfafd7a5..e87cd3886a 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -92,7 +92,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
with_real_execute do
begin
ActiveRecord::Base.connection.create_table :delete_me
- ActiveRecord::Base.connection.add_timestamps :delete_me
+ ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
ensure
@@ -107,7 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.create_table :delete_me do |t|
t.timestamps null: true
end
- ActiveRecord::Base.connection.remove_timestamps :delete_me
+ ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true }
assert !column_present?('delete_me', 'updated_at', 'datetime')
assert !column_present?('delete_me', 'created_at', 'datetime')
ensure
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 03627135b2..0e641ba3bf 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -47,8 +47,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
- assert_equal 1, @connection.type_cast(true, boolean_column)
- assert_equal "1", @connection.type_cast(true, string_column)
+ assert_equal 1, @connection.type_cast(true)
end
test "test type casting without emulated booleans" do
@@ -60,8 +59,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
- assert_equal 1, @connection.type_cast(true, boolean_column)
- assert_equal "1", @connection.type_cast(true, string_column)
+ assert_equal 1, @connection.type_cast(true)
end
test "with booleans stored as 1 and 0" do
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 62c5abbf41..d261e2db55 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -44,9 +44,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert !@connection.active?
# Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ @fixture_connections.each(&:verify!)
end
def test_successful_reconnection_after_timeout_with_manual_reconnect
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_test.rb
new file mode 100644
index 0000000000..ae00f4e131
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/datetime_test.rb
@@ -0,0 +1,87 @@
+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/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index f67f21fab1..2b01d941b8 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -18,7 +18,7 @@ module ActiveRecord
explain = Developer.where(:id => 1).includes(:audit_logs).explain
assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
assert_match %r(audit_logs |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 799d927ee4..7f97b454bb 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -100,7 +100,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
- assert(gs.collect{|x| x.id}.sort == [2, 3])
+ assert(gs.collect(&:id).sort == [2, 3])
end
# has_and_belongs_to_many with reserved-word table name
@@ -109,7 +109,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
- assert(s.collect{|x|x.id}.sort == [1, 2])
+ assert(s.collect(&:id).sort == [1, 2])
end
# activerecord model introspection with reserved-word table and column names
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 1b7e60565d..47707b7d4f 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -51,7 +51,7 @@ module ActiveRecord
table = 'key_tests'
- indexes = @connection.indexes(table).sort_by {|i| i.name}
+ indexes = @connection.indexes(table).sort_by(&:name)
assert_equal 3,indexes.size
index_a = indexes.select{|i| i.name == index_a_name}[0]
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
new file mode 100644
index 0000000000..8f521e9181
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+
+class UnsignedTypeTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class UnsignedType < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table("unsigned_types", force: true) do |t|
+ t.column :unsigned_integer, "int unsigned"
+ end
+ end
+
+ teardown do
+ @connection.drop_table "unsigned_types"
+ end
+
+ test "unsigned int max value is in range" do
+ assert expected = UnsignedType.create(unsigned_integer: 4294967295)
+ assert_equal expected, UnsignedType.find_by(unsigned_integer: 4294967295)
+ end
+
+ test "minus value is out of range" do
+ assert_raise(RangeError) do
+ UnsignedType.create(unsigned_integer: -10)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index fa7bebf08b..77055f5b7a 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -1,7 +1,9 @@
# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlArrayTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
include InTimeZone
OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
@@ -33,13 +35,13 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def test_column
assert_equal :string, @column.type
assert_equal "character varying", @column.sql_type
- assert @column.array
+ assert @column.array?
assert_not @column.number?
assert_not @column.binary?
ratings_column = PgArray.columns_hash['ratings']
assert_equal :integer, ratings_column.type
- assert ratings_column.array
+ assert ratings_column.array?
assert_not ratings_column.number?
end
@@ -72,7 +74,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal :text, column.type
assert_equal [], PgArray.column_defaults['snippets']
- assert column.array
+ assert column.array?
end
def test_change_column_cant_make_non_array_column_to_array
@@ -108,6 +110,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal([1, 2], x.ratings)
end
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema "pg_arrays"
+ assert_match %r[t.string\s+"tags",\s+array: true], output
+ assert_match %r[t.integer\s+"ratings",\s+array: true], output
+ end
+
def test_select_with_strings
@connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')"
x = PgArray.first
@@ -256,11 +264,41 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def test_assigning_non_array_value
record = PgArray.new(tags: "not-an-array")
- assert_equal "not-an-array", record.tags
- e = assert_raises(ActiveRecord::StatementInvalid) do
- record.save!
+ assert_equal [], record.tags
+ assert_equal "not-an-array", record.tags_before_type_cast
+ assert record.save
+ assert_equal record.tags, record.reload.tags
+ end
+
+ def test_assigning_empty_string
+ record = PgArray.new(tags: "")
+ assert_equal [], record.tags
+ assert_equal "", record.tags_before_type_cast
+ assert record.save
+ assert_equal record.tags, record.reload.tags
+ end
+
+ def test_assigning_valid_pg_array_literal
+ record = PgArray.new(tags: "{1,2,3}")
+ assert_equal ["1", "2", "3"], record.tags
+ assert_equal "{1,2,3}", record.tags_before_type_cast
+ assert record.save
+ assert_equal record.tags, record.reload.tags
+ end
+
+ def test_uniqueness_validation
+ klass = Class.new(PgArray) do
+ validates_uniqueness_of :tags
+
+ def self.model_name; ActiveModel::Name.new(PgArray) end
end
- assert_instance_of PG::InvalidTextRepresentation, e.original_exception
+ e1 = klass.create("tags" => ["black", "blue"])
+ assert e1.persisted?, "Saving e1"
+
+ e2 = klass.create("tags" => ["black", "blue"])
+ assert !e2.persisted?, "e2 shouldn't be valid"
+ assert e2.errors[:tags].any?, "Should have errors for tags"
+ assert_equal ["has already been taken"], e2.errors[:tags], "Should have uniqueness message for tags"
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index 72222c01fd..f154ba4cdc 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -28,7 +28,7 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase
assert_equal "bit(8)", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_bit_string_varying_column
@@ -37,7 +37,7 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase
assert_equal "bit varying(4)", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_default
diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
new file mode 100644
index 0000000000..6c1b29f7fe
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
@@ -0,0 +1,31 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class PGChangeSchemaTest < ActiveRecord::TestCase
+ attr_reader :connection
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ connection.create_table(:strings) do |t|
+ t.string :somedate
+ end
+ end
+
+ def teardown
+ connection.drop_table :strings
+ end
+
+ def test_change_string_to_date
+ connection.change_column :strings, :somedate, :timestamp, using: 'CAST("somedate" AS timestamp)'
+ assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type
+ end
+
+ def test_change_type_with_symbol
+ connection.change_column :strings, :somedate, :timestamp, cast_as: :timestamp
+ assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
new file mode 100644
index 0000000000..54b679d3ab
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+require "ipaddr"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter
+ class CidrTest < ActiveRecord::TestCase
+ test "type casting IPAddr for database" do
+ type = OID::Cidr.new
+ 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)
+ end
+
+ test "casting does nothing with non-IPAddr objects" do
+ type = OID::Cidr.new
+
+ assert_equal "foo", type.type_cast_for_database("foo")
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index cb024463c9..5a8083f7a7 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -1,8 +1,10 @@
# encoding: utf-8
require 'cases/helper'
+require 'support/schema_dumping_helper'
if ActiveRecord::Base.connection.supports_extensions?
class PostgresqlCitextTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
class Citext < ActiveRecord::Base
self.table_name = 'citexts'
end
@@ -32,7 +34,7 @@ if ActiveRecord::Base.connection.supports_extensions?
assert_equal 'citext', column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_change_table_supports_json
@@ -67,5 +69,10 @@ if ActiveRecord::Base.connection.supports_extensions?
x = Citext.where(cival: 'cased text').first
assert_equal 'Cased Text', x.cival
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("citexts")
+ assert_match %r[t.citext "cival"], output
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index cfab5ca902..24c1969dee 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -52,7 +52,7 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
assert_equal "full_address", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_composite_mapping
@@ -113,7 +113,7 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
assert_equal "full_address", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
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 7d179944d4..ab7fd3c6d5 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -177,9 +177,7 @@ module ActiveRecord
"successfully querying with the same connection pid."
# Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ @fixture_connections.each(&:verify!)
end
def test_set_session_variable_true
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 2c4839338c..4f48a7bce3 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -2,9 +2,6 @@ require "cases/helper"
require 'support/ddl_helper'
-class PostgresqlNumber < ActiveRecord::Base
-end
-
class PostgresqlTime < ActiveRecord::Base
end
@@ -20,13 +17,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)")
- @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (2, '-Infinity', 'Infinity')")
- @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (3, 123.456, 'NaN')")
- @first_number = PostgresqlNumber.find(1)
- @second_number = PostgresqlNumber.find(2)
- @third_number = PostgresqlNumber.find(3)
-
@connection.execute("INSERT INTO postgresql_times (id, time_interval, scaled_time_interval) VALUES (1, '1 year 2 days ago', '3 weeks ago')")
@first_time = PostgresqlTime.find(1)
@@ -35,12 +25,7 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
teardown do
- [PostgresqlNumber, PostgresqlTime, PostgresqlOid].each(&:delete_all)
- end
-
- def test_data_type_of_number_types
- assert_equal :float, @first_number.column_for_attribute(:single).type
- assert_equal :float, @first_number.column_for_attribute(:double).type
+ [PostgresqlTime, PostgresqlOid].each(&:delete_all)
end
def test_data_type_of_time_types
@@ -52,14 +37,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
- def test_number_values
- assert_equal 123.456, @first_number.single
- assert_equal 123456.789, @first_number.double
- assert_equal(-::Float::INFINITY, @second_number.single)
- assert_equal ::Float::INFINITY, @second_number.double
- assert_same ::Float::NAN, @third_number.double
- end
-
def test_time_values
assert_equal '-1 years -2 days', @first_time.time_interval
assert_equal '-21 days', @first_time.scaled_time_interval
@@ -69,17 +46,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal 1234, @first_oid.obj_id
end
- def test_update_number
- new_single = 789.012
- new_double = 789012.345
- @first_number.single = new_single
- @first_number.double = new_double
- assert @first_number.save
- assert @first_number.reload
- assert_equal new_single, @first_number.single
- assert_equal new_double, @first_number.double
- end
-
def test_update_time
@first_time.time_interval = '2 years 3 minutes'
assert @first_time.save
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 1500adb42d..ebb04814bb 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -31,7 +31,7 @@ class PostgresqlDomainTest < ActiveRecord::TestCase
assert_equal "custom_money", column.sql_type
assert column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
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 83cedc5a7b..88b3b2cc0e 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -33,7 +33,7 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
assert_equal "mood", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_enum_defaults
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index d2dd04b84b..6ffb4c9f33 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -18,7 +18,7 @@ module ActiveRecord
explain = Developer.where(:id => 1).includes(:audit_logs).explain
assert_match %(QUERY PLAN), explain
assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index 9dadb177ca..a370a5adc6 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -1,21 +1,34 @@
# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlFullTextTest < ActiveRecord::TestCase
- class PostgresqlTsvector < ActiveRecord::Base; end
+ include SchemaDumpingHelper
+ class Tsvector < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('tsvectors') do |t|
+ t.tsvector 'text_vector'
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS tsvectors;'
+ end
def test_tsvector_column
- column = PostgresqlTsvector.columns_hash["text_vector"]
+ 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
+ assert_not column.array?
end
def test_update_tsvector
- PostgresqlTsvector.create text_vector: "'text' 'vector'"
- tsvector = PostgresqlTsvector.first
+ Tsvector.create text_vector: "'text' 'vector'"
+ tsvector = Tsvector.first
assert_equal "'text' 'vector'", tsvector.text_vector
tsvector.text_vector = "'new' 'text' 'vector'"
@@ -23,4 +36,9 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase
assert tsvector.reload
assert_equal "'new' 'text' 'vector'", tsvector.text_vector
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("tsvectors")
+ assert_match %r{t.tsvector "text_vector"}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 6c0adbbeaa..ed2bf554bb 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -11,12 +11,10 @@ class PostgresqlPointTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- @connection.transaction do
- @connection.create_table('postgresql_points') do |t|
- t.point :x
- t.point :y, default: [12.2, 13.3]
- t.point :z, default: "(14.4,15.5)"
- end
+ @connection.create_table('postgresql_points') do |t|
+ t.point :x
+ t.point :y, default: [12.2, 13.3]
+ t.point :z, default: "(14.4,15.5)"
end
end
@@ -30,7 +28,7 @@ class PostgresqlPointTest < ActiveRecord::TestCase
assert_equal "point", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_default
@@ -70,3 +68,72 @@ class PostgresqlPointTest < ActiveRecord::TestCase
assert_not p.changed?
end
end
+
+class PostgresqlGeometricTest < ActiveRecord::TestCase
+ class PostgresqlGeometric < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table("postgresql_geometrics") do |t|
+ t.column :a_line_segment, :lseg
+ t.column :a_box, :box
+ t.column :a_path, :path
+ t.column :a_polygon, :polygon
+ t.column :a_circle, :circle
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_geometrics'
+ end
+
+ def test_geometric_types
+ g = PostgresqlGeometric.new(
+ :a_line_segment => '(2.0, 3), (5.5, 7.0)',
+ :a_box => '2.0, 3, 5.5, 7.0',
+ :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]',
+ :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
+ :a_circle => '<(5.3, 10.4), 2>'
+ )
+
+ g.save!
+
+ h = PostgresqlGeometric.find(g.id)
+
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
+ end
+
+ def test_alternative_format
+ g = PostgresqlGeometric.new(
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
+ :a_box => '(2.0, 3), (5.5, 7.0)',
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
+ :a_circle => '((5.3, 10.4), 2)'
+ )
+
+ g.save!
+
+ h = PostgresqlGeometric.find(g.id)
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
+ end
+
+ def test_geometric_function
+ PostgresqlGeometric.create! a_path: '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]' # [ ] is an open path
+ PostgresqlGeometric.create! a_path: '((2.0, 3), (5.5, 7.0), (8.5, 11.0))' # ( ) is a closed path
+
+ objs = PostgresqlGeometric.find_by_sql "SELECT isopen(a_path) FROM postgresql_geometrics ORDER BY id ASC"
+ assert_equal [true, false], objs.map(&:isopen)
+
+ objs = PostgresqlGeometric.find_by_sql "SELECT isclosed(a_path) FROM postgresql_geometrics ORDER BY id ASC"
+ assert_equal [false, true], objs.map(&:isclosed)
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 6a9c6483fe..a0aa10630c 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -1,8 +1,10 @@
# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
if ActiveRecord::Base.connection.supports_extensions?
class PostgresqlHstoreTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
class Hstore < ActiveRecord::Base
self.table_name = 'hstores'
@@ -54,7 +56,7 @@ if ActiveRecord::Base.connection.supports_extensions?
assert_equal "hstore", @column.sql_type
assert_not @column.number?
assert_not @column.binary?
- assert_not @column.array
+ assert_not @column.array?
end
def test_default
@@ -313,6 +315,11 @@ if ActiveRecord::Base.connection.supports_extensions?
assert_equal({"one" => "two"}, dupe.tags.to_hash)
end
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("hstores")
+ assert_match %r[t.hstore "tags",\s+default: {}], output
+ end
+
private
def assert_array_cycle(array)
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index 22e8873333..74163ac712 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -1,6 +1,8 @@
require "cases/helper"
class PostgresqlInfinityTest < ActiveRecord::TestCase
+ include InTimeZone
+
class PostgresqlInfinity < ActiveRecord::Base
end
@@ -41,4 +43,18 @@ class PostgresqlInfinityTest < ActiveRecord::TestCase
record.reload
assert_equal Float::INFINITY, record.datetime
end
+
+ test "assigning 'infinity' on a datetime column with TZ aware attributes" do
+ begin
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = PostgresqlInfinity.create!(datetime: "infinity")
+ assert_equal Float::INFINITY, record.datetime
+ assert_equal record.datetime, record.reload.datetime
+ end
+ ensure
+ # setting time_zone_aware_attributes causes the types to change.
+ # There is no way to do this automatically since it can be set on a superclass
+ PostgresqlInfinity.reset_column_information
+ end
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb
new file mode 100644
index 0000000000..7f8751281e
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+require "active_support/core_ext/numeric/bytes"
+
+class PostgresqlIntegerTest < ActiveRecord::TestCase
+ class PgInteger < ActiveRecord::Base
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ @connection.transaction do
+ @connection.create_table "pg_integers", force: true do |t|
+ t.integer :quota, limit: 8, default: 2.gigabytes
+ end
+ end
+ end
+
+ teardown do
+ @connection.execute "drop table if exists pg_integers"
+ end
+
+ test "schema properly respects bigint ranges" do
+ assert_equal 2.gigabytes, PgInteger.new.quota
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 340ca29c0e..7be7e00463 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -36,7 +36,7 @@ module PostgresqlJSONSharedTestCases
assert_equal column_type.to_s, column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_default
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 2968109346..771a825840 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -1,7 +1,9 @@
# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlLtreeTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
class Ltree < ActiveRecord::Base
self.table_name = 'ltrees'
end
@@ -30,7 +32,7 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
assert_equal "ltree", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_write
@@ -43,4 +45,9 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
ltree = Ltree.first
assert_equal '1.2.3', ltree.path
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("ltrees")
+ assert_match %r[t.ltree "path"], output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 87183174f2..f3a24eee85 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -10,7 +10,7 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.execute("set lc_monetary = 'C'")
- @connection.create_table('postgresql_moneys') do |t|
+ @connection.create_table('postgresql_moneys', force: true) do |t|
t.column "wealth", "money"
t.column "depth", "money", default: "150.55"
end
@@ -27,7 +27,7 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
assert_equal 2, column.scale
assert column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_default
diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
index 4f4c1103fa..daa590f369 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -1,8 +1,22 @@
# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlNetworkTest < ActiveRecord::TestCase
- class PostgresqlNetworkAddress < ActiveRecord::Base
+ include SchemaDumpingHelper
+ class PostgresqlNetworkAddress < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('postgresql_network_addresses', force: true) do |t|
+ t.inet 'inet_address', default: "192.168.1.1"
+ t.cidr 'cidr_address', default: "192.168.1.0/24"
+ t.macaddr 'mac_address', default: "ff:ff:ff:ff:ff:ff"
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_network_addresses'
end
def test_cidr_column
@@ -11,7 +25,7 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase
assert_equal "cidr", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_inet_column
@@ -20,7 +34,7 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase
assert_equal "inet", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_macaddr_column
@@ -29,7 +43,7 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase
assert_equal "macaddr", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_network_types
@@ -68,4 +82,11 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase
assert_nil invalid_address.cidr_address_before_type_cast
assert_nil invalid_address.inet_address_before_type_cast
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("postgresql_network_addresses")
+ assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output
+ assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output
+ assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
new file mode 100644
index 0000000000..70aa898439
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
@@ -0,0 +1,49 @@
+require "cases/helper"
+
+class PostgresqlNumberTest < ActiveRecord::TestCase
+ class PostgresqlNumber < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('postgresql_numbers', force: true) do |t|
+ t.column 'single', 'REAL'
+ t.column 'double', 'DOUBLE PRECISION'
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_numbers'
+ end
+
+ def test_data_type
+ assert_equal :float, PostgresqlNumber.columns_hash["single"].type
+ assert_equal :float, PostgresqlNumber.columns_hash["double"].type
+ end
+
+ def test_values
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)")
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (2, '-Infinity', 'Infinity')")
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (3, 123.456, 'NaN')")
+
+ first, second, third = PostgresqlNumber.find(1, 2, 3)
+
+ assert_equal 123.456, first.single
+ assert_equal 123456.789, first.double
+ assert_equal(-::Float::INFINITY, second.single)
+ assert_equal ::Float::INFINITY, second.double
+ assert_same ::Float::NAN, third.double
+ end
+
+ def test_update
+ record = PostgresqlNumber.create! single: "123.456", double: "123456.789"
+ new_single = 789.012
+ new_double = 789012.345
+ record.single = new_single
+ record.double = new_double
+ record.save!
+
+ record.reload
+ assert_equal new_single, record.single
+ assert_equal new_double, record.double
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index c3c696b871..6bb2b26cd5 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -54,6 +54,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do
+ assert_nil @connection.primary_key('ex')
+ end
+ end
+
def test_primary_key_raises_error_if_table_not_found
assert_raises(ActiveRecord::StatementInvalid) do
@connection.primary_key('unobtainium')
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 11d5173d37..9ac0036d66 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -10,47 +10,21 @@ module ActiveRecord
end
def test_type_cast_true
- c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
- assert_equal 't', @conn.type_cast(true, nil)
- assert_equal 't', @conn.type_cast(true, c)
+ assert_equal 't', @conn.type_cast(true)
end
def test_type_cast_false
- c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
- assert_equal 'f', @conn.type_cast(false, nil)
- assert_equal 'f', @conn.type_cast(false, c)
- end
-
- def test_type_cast_cidr
- ip = IPAddr.new('255.0.0.0/8')
- c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'cidr')
- assert_equal ip, @conn.type_cast(ip, c)
- end
-
- def test_type_cast_inet
- ip = IPAddr.new('255.1.0.0/8')
- c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet')
- assert_equal ip, @conn.type_cast(ip, c)
+ assert_equal 'f', @conn.type_cast(false)
end
def test_quote_float_nan
nan = 0.0/0
- c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
- assert_equal "'NaN'", @conn.quote(nan, c)
+ assert_equal "'NaN'", @conn.quote(nan)
end
def test_quote_float_infinity
infinity = 1.0/0
- c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
- assert_equal "'Infinity'", @conn.quote(infinity, c)
- end
-
- def test_quote_cast_numeric
- fixnum = 666
- c = PostgreSQLColumn.new(nil, nil, Type::String.new, 'varchar')
- assert_equal "'666'", @conn.quote(fixnum, c)
- c = PostgreSQLColumn.new(nil, nil, Type::Text.new, 'text')
- assert_equal "'666'", @conn.quote(fixnum, c)
+ assert_equal "'Infinity'", @conn.quote(infinity)
end
def test_quote_time_usec
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index d812cd01c4..70cf21100a 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -230,36 +230,14 @@ _SQL
assert_nil_round_trip(@first_range, :int8_range, 39999...39999)
end
- def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated
- tz = ::ActiveRecord::Base.default_timezone
-
- silence_warnings {
- assert_deprecated {
- range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']")
- assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']")
- assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']")
- assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(int4_range: "(1, 10]")
- assert_equal 2..10, range.int4_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(int8_range: "(10, 100]")
- assert_equal 11..100, range.int8_range
- }
- }
- end
-
def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported
assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") }
assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(int4_range: "(1, 10]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(int8_range: "(10, 100]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") }
end
def test_update_all_with_ranges
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
new file mode 100644
index 0000000000..056a035622
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -0,0 +1,34 @@
+require "cases/helper"
+
+class PostgresqlRenameTableTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :before_rename, force: true
+ end
+
+ def teardown
+ @connection.execute 'DROP TABLE IF EXISTS "before_rename"'
+ @connection.execute 'DROP TABLE IF EXISTS "after_rename"'
+ end
+
+ test "renaming a table also renames the primary key index" do
+ # sanity check
+ assert_equal 1, num_indices_named("before_rename_pkey")
+ assert_equal 0, num_indices_named("after_rename_pkey")
+
+ @connection.rename_table :before_rename, :after_rename
+
+ assert_equal 0, num_indices_named("before_rename_pkey")
+ assert_equal 1, num_indices_named("after_rename_pkey")
+ end
+
+ private
+
+ def num_indices_named(name)
+ @connection.execute(<<-SQL).values.length
+ SELECT 1 FROM "pg_index"
+ JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid"
+ WHERE "pg_class"."relname" = '#{name}'
+ SQL
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 350cb3e065..e99f1e2867 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/default'
require 'support/schema_dumping_helper'
class SchemaTest < ActiveRecord::TestCase
@@ -88,7 +89,7 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_schema_names
- assert_equal ["public", "schema_1", "test_schema", "test_schema2"], @connection.schema_names
+ assert_equal ["public", "test_schema", "test_schema2"], @connection.schema_names
end
def test_create_schema
@@ -412,7 +413,7 @@ class SchemaTest < ActiveRecord::TestCase
def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
with_schema_search_path(this_schema_name) do
- indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
+ indexes = @connection.indexes(TABLE_NAME).sort_by(&:name)
assert_equal 4,indexes.size
do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
@@ -460,3 +461,52 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase
@connection.execute "DROP SCHEMA IF EXISTS my_schema"
end
end
+
+class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE"
+ @connection.execute "CREATE SCHEMA schema_1"
+ @connection.execute "CREATE DOMAIN schema_1.text AS text"
+ @connection.execute "CREATE DOMAIN schema_1.varchar AS varchar"
+ @connection.execute "CREATE DOMAIN schema_1.bpchar AS bpchar"
+
+ @old_search_path = @connection.schema_search_path
+ @connection.schema_search_path = "schema_1, pg_catalog"
+ @connection.create_table "defaults" do |t|
+ t.text "text_col", default: "some value"
+ t.string "string_col", default: "some value"
+ end
+ Default.reset_column_information
+ end
+
+ teardown do
+ @connection.schema_search_path = @old_search_path
+ @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE"
+ Default.reset_column_information
+ end
+
+ def test_text_defaults_in_new_schema_when_overriding_domain
+ assert_equal "some value", Default.new.text_col, "Default of text column was not correctly parsed"
+ end
+
+ def test_string_defaults_in_new_schema_when_overriding_domain
+ assert_equal "some value", Default.new.string_col, "Default of string column was not correctly parsed"
+ end
+
+ def test_bpchar_defaults_in_new_schema_when_overriding_domain
+ @connection.execute "ALTER TABLE defaults ADD bpchar_col bpchar DEFAULT 'some value'"
+ Default.reset_column_information
+ assert_equal "some value", Default.new.bpchar_col, "Default of bpchar column was not correctly parsed"
+ end
+
+ def test_text_defaults_after_updating_column_default
+ @connection.execute "ALTER TABLE defaults ALTER COLUMN text_col SET DEFAULT 'some text'::schema_1.text"
+ assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parsed after updating default using '::text' since postgreSQL will add parens to the default in db"
+ end
+
+ def test_default_containing_quote_and_colons
+ @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'"
+ assert_equal "foo'::bar", Default.new.string_col
+ 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 23817198b1..c88259d274 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -12,4 +12,22 @@ class PostgresqlTypeLookupTest < ActiveRecord::TestCase
assert_equal ';', box_array.delimiter
assert_equal ',', int_array.delimiter
end
+
+ test "array types correctly respect registration of subtypes" do
+ int_array = @connection.type_map.lookup(1007, -1, "integer[]")
+ 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)
+ end
+
+ test "range types correctly respect registration of subtypes" do
+ int_range = @connection.type_map.lookup(3904, -1, "int4range")
+ 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)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 5753da1173..7d2fae69d5 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -14,6 +14,7 @@ end
class PostgresqlUUIDTest < ActiveRecord::TestCase
include PostgresqlUUIDHelper
+ include SchemaDumpingHelper
class UUIDType < ActiveRecord::Base
self.table_name = "uuid_data_type"
@@ -50,7 +51,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
assert_equal "uuid", column.sql_type
assert_not column.number?
assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
end
def test_treat_blank_uuid_as_nil
@@ -68,13 +69,18 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
assert_equal 'foobar', uuid.guid_before_type_cast
end
- def test_rfc_4122_regex
+ def test_acceptable_uuid_regex
# Valid uuids
['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11',
'{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}',
'a0eebc999c0b4ef8bb6d6bb9bd380a11',
'a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11',
- '{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}'].each do |valid_uuid|
+ '{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}',
+ # The following is not a valid RFC 4122 UUID, but PG doesn't seem to care,
+ # so we shouldn't block it either. (Pay attention to "fb6d" – the "f" here
+ # is invalid – it must be one of 8, 9, A, B, a, b according to the spec.)
+ '{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}',
+ ].each do |valid_uuid|
uuid = UUIDType.new guid: valid_uuid
assert_not_nil uuid.guid
end
@@ -86,7 +92,6 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
0.0,
true,
'Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11',
- '{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}',
'a0eebc999r0b4ef8ab6d6bb9bd380a11',
'a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11',
'{a0eebc99-bb6d6bb9-bd380a11}'].each do |invalid_uuid|
@@ -106,6 +111,28 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
assert_equal "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", uuid.guid
end
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema "uuid_data_type"
+ assert_match %r{t.uuid "guid"}, output
+ end
+
+ def test_uniqueness_validation_ignores_uuid
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "uuid_data_type"
+ validates :guid, uniqueness: { case_sensitive: false }
+
+ def self.name
+ "UUIDType"
+ end
+ end
+
+ record = klass.create!(guid: "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11")
+ duplicate = klass.new(guid: record.guid)
+
+ assert record.guid.present? # Ensure we actually are testing a UUID
+ assert_not duplicate.valid?
+ end
end
class PostgresqlLargeKeysTest < ActiveRecord::TestCase
@@ -120,7 +147,7 @@ class PostgresqlLargeKeysTest < ActiveRecord::TestCase
def test_omg
schema = dump_table_schema "big_serials"
- assert_match "create_table \"big_serials\", id: :bigserial, force: true", schema
+ assert_match "create_table \"big_serials\", id: :bigserial", schema
end
def teardown
@@ -205,6 +232,7 @@ end
class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
include PostgresqlUUIDHelper
+ include SchemaDumpingHelper
setup do
enable_extension!('uuid-ossp', connection)
@@ -228,6 +256,11 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
assert_nil col_desc["default"]
end
+
+ def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil
+ schema = dump_table_schema "pg_uuids"
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index 4165dd5ac9..5aba118518 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -1,7 +1,9 @@
# encoding: utf-8
require 'cases/helper'
+require 'support/schema_dumping_helper'
class PostgresqlXMLTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
class XmlDataType < ActiveRecord::Base
self.table_name = 'xml_data_type'
end
@@ -45,4 +47,9 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
XmlDataType.update_all(payload: "<bar>baz</bar>")
assert_equal "<bar>baz</bar>", data.reload.payload
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("xml_data_type")
+ assert_match %r{t.xml "payload"}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index de6e35ef57..7d66c44798 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -18,7 +18,7 @@ module ActiveRecord
explain = Developer.where(:id => 1).includes(:audit_logs).explain
assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index ac8332e2fa..df497e761c 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -15,73 +15,52 @@ module ActiveRecord
def test_type_cast_binary_encoding_without_logger
@conn.extend(Module.new { def logger; end })
- column = Column.new(nil, nil, Type::String.new)
binary = SecureRandom.hex
expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary, column)
+ assert_equal expected, @conn.type_cast(binary)
end
def test_type_cast_symbol
- assert_equal 'foo', @conn.type_cast(:foo, nil)
+ assert_equal 'foo', @conn.type_cast(:foo)
end
def test_type_cast_date
date = Date.today
expected = @conn.quoted_date(date)
- assert_equal expected, @conn.type_cast(date, nil)
+ assert_equal expected, @conn.type_cast(date)
end
def test_type_cast_time
time = Time.now
expected = @conn.quoted_date(time)
- assert_equal expected, @conn.type_cast(time, nil)
+ assert_equal expected, @conn.type_cast(time)
end
def test_type_cast_numeric
- assert_equal 10, @conn.type_cast(10, nil)
- assert_equal 2.2, @conn.type_cast(2.2, nil)
+ assert_equal 10, @conn.type_cast(10)
+ assert_equal 2.2, @conn.type_cast(2.2)
end
def test_type_cast_nil
- assert_equal nil, @conn.type_cast(nil, nil)
+ assert_equal nil, @conn.type_cast(nil)
end
def test_type_cast_true
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 't', @conn.type_cast(true, nil)
- assert_equal 1, @conn.type_cast(true, c)
+ assert_equal 't', @conn.type_cast(true)
end
def test_type_cast_false
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 'f', @conn.type_cast(false, nil)
- assert_equal 0, @conn.type_cast(false, c)
- end
-
- def test_type_cast_string
- assert_equal '10', @conn.type_cast('10', nil)
-
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 10, @conn.type_cast('10', c)
-
- c = Column.new(nil, 1, Type::Float.new)
- assert_equal 10.1, @conn.type_cast('10.1', c)
-
- c = Column.new(nil, 1, Type::Binary.new)
- assert_equal '10.1', @conn.type_cast('10.1', c)
-
- c = Column.new(nil, 1, Type::Date.new)
- assert_equal '10.1', @conn.type_cast('10.1', c)
+ assert_equal 'f', @conn.type_cast(false)
end
def test_type_cast_bigdecimal
bd = BigDecimal.new '10.0'
- assert_equal bd.to_f, @conn.type_cast(bd, nil)
+ assert_equal bd.to_f, @conn.type_cast(bd)
end
def test_type_cast_unknown_should_raise_error
obj = Class.new.new
- assert_raise(TypeError) { @conn.type_cast(obj, nil) }
+ assert_raise(TypeError) { @conn.type_cast(obj) }
end
def test_type_cast_object_which_responds_to_quoted_id
@@ -94,14 +73,14 @@ module ActiveRecord
10
end
}.new
- assert_equal 10, @conn.type_cast(quoted_id_obj, nil)
+ assert_equal 10, @conn.type_cast(quoted_id_obj)
quoted_id_obj = Class.new {
def quoted_id
"'zomg'"
end
}.new
- assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) }
+ assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
end
def test_quoting_binary_strings
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index d83c65cf0e..029663e7f4 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -327,11 +327,11 @@ module ActiveRecord
def test_columns
with_example_table do
- columns = @conn.columns('ex').sort_by { |x| x.name }
+ columns = @conn.columns('ex').sort_by(&:name)
assert_equal 2, columns.length
- assert_equal %w{ id number }.sort, columns.map { |x| x.name }
- assert_equal [nil, nil], columns.map { |x| x.default }
- assert_equal [true, true], columns.map { |x| x.null }
+ assert_equal %w{ id number }.sort, columns.map(&:name)
+ assert_equal [nil, nil], columns.map(&:default)
+ assert_equal [true, true], columns.map(&:null)
end
end
@@ -405,6 +405,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do
+ assert_nil @conn.primary_key('ex')
+ end
+ end
+
def test_supports_extensions
assert_not @conn.supports_extensions?, 'does not support extensions'
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index b6333240a8..f4e8003bc3 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -93,60 +93,38 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947")
end
- def test_timestamps_without_null_is_deprecated_on_create_table
- assert_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps do |t|
- t.timestamps
- end
+ def test_timestamps_without_null_set_null_to_false_on_create_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps do |t|
+ t.timestamps
end
end
- end
- def test_timestamps_without_null_is_deprecated_on_change_table
- assert_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps
-
- change_table :has_timestamps do |t|
- t.timestamps
- end
- end
- end
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null
end
- def test_no_deprecation_warning_from_timestamps_on_create_table
- assert_not_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps do |t|
- t.timestamps null: true
- end
-
- drop_table :has_timestamps
+ def test_timestamps_without_null_set_null_to_false_on_change_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
- create_table :has_timestamps do |t|
- t.timestamps null: false
- end
+ change_table :has_timestamps do |t|
+ t.timestamps default: Time.now
end
end
- end
-
- def test_no_deprecation_warning_from_timestamps_on_change_table
- assert_not_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps
- change_table :has_timestamps do |t|
- t.timestamps null: true
- end
- drop_table :has_timestamps
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null
+ end
- create_table :has_timestamps
- change_table :has_timestamps do |t|
- t.timestamps null: false, default: Time.now
- end
- end
+ def test_timestamps_without_null_set_null_to_false_on_add_timestamps
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
+ add_timestamps :has_timestamps, default: Time.now
end
+
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null
end
end
end
diff --git a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb b/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb
deleted file mode 100644
index 48f7ddbe83..0000000000
--- a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require "cases/helper"
-
-class DeprecatedCounterCacheOnHasManyThroughTest < ActiveRecord::TestCase
- class Post < ActiveRecord::Base
- has_many :taggings, as: :taggable
- has_many :tags, through: :taggings
- end
-
- class Tagging < ActiveRecord::Base
- belongs_to :taggable, polymorphic: true
- belongs_to :tag
- end
-
- class Tag < ActiveRecord::Base
- end
-
- test "counter caches are updated in the database if the belongs_to association doesn't specify a counter cache" do
- post = Post.create!(title: 'Hello', body: 'World!')
- assert_deprecated { post.tags << Tag.create!(name: 'whatever') }
-
- assert_equal 1, post.tags.size
- assert_equal 1, post.tags_count
- assert_equal 1, post.reload.tags.size
- assert_equal 1, post.reload.tags_count
- end
-end
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 0ff87d53ea..f571198079 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -70,9 +70,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
teardown do
[Circle, Square, Triangle, PaintColor, PaintTexture,
- ShapeExpression, NonPolyOne, NonPolyTwo].each do |c|
- c.delete_all
- end
+ ShapeExpression, NonPolyOne, NonPolyTwo].each(&:delete_all)
end
def generate_test_object_graphs
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 4539b99504..371635d20a 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -270,6 +270,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_three_level_nested_preloading_does_not_raise_exception_when_association_does_not_exist
+ post_id = Comment.where(author_id: nil).where.not(post_id: nil).first.post_id
+
+ assert_nothing_raised do
+ Post.preload(:comments => [{:author => :essays}]).find(post_id)
+ end
+ end
+
def test_nested_loading_through_has_one_association
aa = AuthorAddress.all.merge!(:includes => {:author => :posts}).find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
@@ -330,31 +338,31 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_limit
comments = Comment.all.merge!(:includes => :post, :limit => 5, :order => 'comments.id').to_a
assert_equal 5, comments.length
- assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
+ assert_equal [1,2,3,5,6], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [5,6,7], comments.collect { |c| c.id }
+ assert_equal [5,6,7], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset
comments = Comment.all.merge!(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [3,5,6], comments.collect { |c| c.id }
+ assert_equal [3,5,6], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [6,7,8], comments.collect { |c| c.id }
+ assert_equal [6,7,8], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
comments = Comment.all.merge!(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [6,7,8], comments.collect { |c| c.id }
+ assert_equal [6,7,8], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name
@@ -369,7 +377,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
comments = Comment.all.merge!(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').to_a
end
assert_equal 3, comments.length
- assert_equal [5,6,7], comments.collect { |c| c.id }
+ assert_equal [5,6,7], comments.collect(&:id)
assert_no_queries do
comments.first.post
end
@@ -398,13 +406,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').to_a
assert_equal 1, posts.length
- assert_equal [1], posts.collect { |p| p.id }
+ assert_equal [1], posts.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').to_a
assert_equal 1, posts.length
- assert_equal [2], posts.collect { |p| p.id }
+ assert_equal [2], posts.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name
@@ -495,8 +503,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
- author = Author.all.merge!(:includes => :special_nonexistant_post_comments, :order => 'authors.id').first
- assert_equal [], author.special_nonexistant_post_comments
+ author = Author.all.merge!(:includes => :special_nonexistent_post_comments, :order => 'authors.id').first
+ assert_equal [], author.special_nonexistent_post_comments
end
def test_eager_with_has_many_through_join_model_with_conditions
@@ -537,13 +545,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_conditions
posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a
assert_equal 2, posts.size
- assert_equal [4,5], posts.collect { |p| p.id }
+ assert_equal [4,5], posts.collect(&:id)
end
def test_eager_with_has_many_and_limit_and_conditions_array
posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a
assert_equal 2, posts.size
- assert_equal [4,5], posts.collect { |p| p.id }
+ assert_equal [4,5], posts.collect(&:id)
end
def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
@@ -818,18 +826,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
)
end
- def test_preload_with_interpolation
- assert_deprecated do
- post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
- end
-
- assert_deprecated do
- post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
- end
- end
-
def test_polymorphic_type_condition
post = Post.all.merge!(:includes => :taggings).find(posts(:thinking).id)
assert post.taggings.include?(taggings(:thinking_general))
@@ -904,6 +900,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries {assert_equal posts(:sti_comments), comment.post}
end
+ def test_eager_association_with_scope_with_joins
+ assert_nothing_raised do
+ Post.includes(:very_special_comment_with_post_with_joins).to_a
+ end
+ end
+
def test_preconfigured_includes_with_has_many
posts = authors(:david).posts_with_comments
one = posts.detect { |p| p.id == 1 }
@@ -1280,23 +1282,22 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet
end
- test "include instance dependent associations is deprecated" do
+ test "preloading and eager loading of instance dependent associations is not supported" do
message = "association scope 'posts_with_signature' is"
- assert_deprecated message do
- begin
- Author.includes(:posts_with_signature).to_a
- rescue NoMethodError
- # it's expected that preloading of this association fails
- end
+ error = assert_raises(ArgumentError) do
+ Author.includes(:posts_with_signature).to_a
end
+ assert_match message, error.message
- assert_deprecated message do
- Author.preload(:posts_with_signature).to_a rescue NoMethodError
+ error = assert_raises(ArgumentError) do
+ Author.preload(:posts_with_signature).to_a
end
+ assert_match message, error.message
- assert_deprecated message do
+ error = assert_raises(ArgumentError) do
Author.eager_load(:posts_with_signature).to_a
end
+ assert_match message, error.message
end
test "preloading readonly association" do
@@ -1314,7 +1315,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
test "eager-loading readonly association" do
- skip "eager_load does not yet preserve readonly associations"
# has-one
firm = Firm.where(id: "1").eager_load(:readonly_account).first!
assert firm.readonly_account.readonly?
@@ -1326,6 +1326,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
# has-many :through
david = Author.where(id: "1").eager_load(:readonly_comments).first!
assert david.readonly_comments.first.readonly?
+
+ # belongs_to
+ post = Post.where(id: "1").eager_load(:author).first!
+ assert post.author.readonly?
end
test "preloading a polymorphic association with references to the associated table" do
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 9d373cd73b..b161cde335 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -76,7 +76,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { }
- builder.define_extensions(model)
+ ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 9a57683ee3..aea9207bfe 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -79,6 +79,10 @@ class SubDeveloper < Developer
:association_foreign_key => "developer_id"
end
+class DeveloperWithSymbolClassName < Developer
+ has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
+end
+
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers
@@ -551,7 +555,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_dynamic_find_all_should_respect_readonly_access
projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
- projects(:active_record).readonly_developers.each { |d| d.readonly? }
+ projects(:active_record).readonly_developers.each(&:readonly?)
end
def test_new_with_values_in_collection
@@ -892,4 +896,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers")
assert_equal developers(:david).shared_computers.first, computers(:laptop)
end
+
+ def test_with_symbol_class_name
+ assert_nothing_raised NoMethodError do
+ DeveloperWithSymbolClassName.new
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 5a963b6458..21a45042fa 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -7,6 +7,7 @@ require 'models/contract'
require 'models/topic'
require 'models/reply'
require 'models/category'
+require 'models/image'
require 'models/post'
require 'models/author'
require 'models/essay'
@@ -30,6 +31,8 @@ require 'models/student'
require 'models/pirate'
require 'models/ship'
require 'models/tyre'
+require 'models/subscriber'
+require 'models/subscription'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -42,12 +45,59 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa
end
end
+class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
+ fixtures :authors, :essays, :subscribers, :subscriptions, :people
+
+ def test_custom_primary_key_on_new_record_should_fetch_with_query
+ subscriber = Subscriber.new(nick: 'webster132')
+ assert !subscriber.subscriptions.loaded?
+
+ assert_queries 1 do
+ assert_equal 2, subscriber.subscriptions.size
+ end
+
+ assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: 'webster132')
+ end
+
+ def test_association_primary_key_on_new_record_should_fetch_with_query
+ author = Author.new(:name => "David")
+ assert !author.essays.loaded?
+
+ assert_queries 1 do
+ assert_equal 1, author.essays.size
+ end
+
+ assert_equal author.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_custom_primary_key
+ david = authors(:david)
+ assert_equal david.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_assignment_with_custom_primary_key
+ david = people(:david)
+
+ assert_equal ["A Modest Proposal"], david.essays.map(&:name)
+ david.essays = [Essay.create!(name: "Remote Work" )]
+ assert_equal ["Remote Work"], david.essays.map(&:name)
+ end
+
+ def test_blank_custom_primary_key_on_new_record_should_not_run_queries
+ author = Author.new
+ assert !author.essays.loaded?
+
+ assert_queries 0 do
+ assert_equal 0, author.essays.size
+ end
+ end
+end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings, :cars, :essays,
- :categorizations, :jobs, :tags
+ :posts, :readers, :taggings, :cars, :jobs, :tags,
+ :categorizations
def setup
Client.destroyed_client_ids.clear
@@ -589,17 +639,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_with_bang_on_has_many_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
firm = Firm.new
firm.plain_clients.create! :name=>"Whoever"
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_regular_create_on_has_many_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
firm = Firm.new
firm.plain_clients.create :name=>"Whoever"
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_create_with_bang_on_has_many_raises_when_record_not_saved
@@ -610,9 +664,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_with_bang_on_habtm_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
Developer.new("name" => "Aredridel").projects.create!
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_adding_a_mismatch_class
@@ -1213,7 +1269,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !clients.empty?, "37signals has clients after load"
destroyed = companies(:first_firm).clients_of_firm.destroy_all
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
- assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
+ assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
end
@@ -1353,10 +1409,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !account.valid?
assert !orig_accounts.empty?
- assert_raise ActiveRecord::RecordNotSaved do
+ error = assert_raise ActiveRecord::RecordNotSaved do
firm.accounts = [account]
end
+
assert_equal orig_accounts, firm.accounts
+ assert_equal "Failed to replace accounts because one or more of the " \
+ "new records could not be saved.", error.message
end
def test_replace_with_same_content
@@ -1568,39 +1627,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_custom_primary_key_on_new_record_should_fetch_with_query
- author = Author.new(:name => "David")
- assert !author.essays.loaded?
-
- assert_queries 1 do
- assert_equal 1, author.essays.size
- end
-
- assert_equal author.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_custom_primary_key
- david = authors(:david)
- assert_equal david.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_assignment_with_custom_primary_key
- david = people(:david)
-
- assert_equal ["A Modest Proposal"], david.essays.map(&:name)
- david.essays = [Essay.create!(name: "Remote Work" )]
- assert_equal ["Remote Work"], david.essays.map(&:name)
- end
-
- def test_blank_custom_primary_key_on_new_record_should_not_run_queries
- author = Author.new
- assert !author.essays.loaded?
-
- assert_queries 0 do
- assert_equal 0, author.essays.size
- end
- end
-
def test_calling_first_or_last_with_integer_on_association_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(:name => 'Foo')
@@ -1774,6 +1800,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [tagging], post.taggings
end
+ def test_with_polymorphic_has_many_with_custom_columns_name
+ post = Post.create! :title => 'foo', :body => 'bar'
+ image = Image.create!
+
+ post.images << image
+
+ assert_equal [image], post.images
+ end
+
def test_build_with_polymorphic_has_many_does_not_allow_to_override_type_and_id
welcome = posts(:welcome)
tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange')
@@ -1997,4 +2032,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, car.bulbs.count
assert_equal 1, car.tyres.count
end
+
+ test 'associations replace in memory when records have the same id' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ new_bulb.name = "foo"
+ car.bulbs = [new_bulb]
+
+ assert_equal "foo", car.bulbs.first.name
+ end
+
+ test 'in memory replacement executes no queries' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+
+ assert_no_queries do
+ car.bulbs = [new_bulb]
+ end
+ end
+
+ test 'in memory replacements do not execute callbacks' do
+ raise_after_add = false
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = :cars
+ has_many :bulbs, after_add: proc { raise if raise_after_add }
+
+ def self.name
+ "Car"
+ end
+ end
+ bulb = Bulb.create!
+ car = klass.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ raise_after_add = true
+
+ assert_nothing_raised do
+ car.bulbs = [new_bulb]
+ end
+ end
+
+ test 'in memory replacements sets inverse instance' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ car.bulbs = [new_bulb]
+
+ assert_same car, new_bulb.car
+ end
+
+ test 'in memory replacement maintains order' do
+ first_bulb = Bulb.create!
+ second_bulb = Bulb.create!
+ car = Car.create!(bulbs: [first_bulb, second_bulb])
+
+ same_bulb = Bulb.find(first_bulb.id)
+ car.bulbs = [second_bulb, same_bulb]
+
+ assert_equal [first_bulb, second_bulb], car.bulbs
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index df4a30ae9b..6729a5a9fc 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -25,12 +25,13 @@ require 'models/categorization'
require 'models/member'
require 'models/membership'
require 'models/club'
+require 'models/organization'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
:owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses,
:subscribers, :books, :subscriptions, :developers, :categorizations, :essays,
- :categories_posts, :clubs, :memberships
+ :categories_posts, :clubs, :memberships, :organizations
# Dummies to force column loads so query counts are clean.
def setup
@@ -41,7 +42,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_preload_sti_rhs_class
developers = Developer.includes(:firms).all.to_a
assert_no_queries do
- developers.each { |d| d.firms }
+ developers.each(&:firms)
end
end
@@ -615,8 +616,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_create_on_new_record
p = Post.new
- assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") }
- assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") }
+ error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") }
+ assert_equal "You cannot call create unless the parent is saved", error.message
+
+ error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") }
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_associate_with_create_and_invalid_options
@@ -1148,4 +1152,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club.members << member
assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count
end
+
+ def test_build_for_has_many_through_association
+ organization = organizations(:nsa)
+ author = organization.author
+ post_direct = author.posts.build
+ post_through = organization.posts.build
+ assert_equal post_direct.author_id, post_through.author_id
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 2ecfcb521d..9b6757e256 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -8,6 +8,7 @@ require 'models/pirate'
require 'models/car'
require 'models/bulb'
require 'models/author'
+require 'models/image'
require 'models/post'
class HasOneAssociationsTest < ActiveRecord::TestCase
@@ -272,6 +273,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.reload.account
end
+ def test_create_with_inexistent_foreign_key_failing
+ firm = Firm.create(name: 'GlobalMegaCorp')
+
+ assert_raises(ActiveRecord::UnknownAttributeError) do
+ firm.create_account_with_inexistent_foreign_key
+ end
+ end
+
def test_build
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@@ -410,9 +419,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
pirate = pirates(:redbeard)
new_ship = Ship.new
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = new_ship
end
+
+ assert_equal "Failed to save the new associated ship.", error.message
assert_nil pirate.ship
assert_nil new_ship.pirate_id
end
@@ -422,20 +433,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
pirate.ship.name = nil
assert !pirate.ship.valid?
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = ships(:interceptor)
end
+
assert_equal ships(:black_pearl), pirate.ship
assert_equal pirate.id, pirate.ship.pirate_id
+ assert_equal "Failed to remove the existing associated ship. " +
+ "The record failed to save after its foreign key was set to nil.", error.message
end
def test_replacement_failure_due_to_new_record_should_raise_error
pirate = pirates(:blackbeard)
new_ship = Ship.new
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = new_ship
end
+
+ assert_equal "Failed to save the new associated ship.", error.message
assert_equal ships(:black_pearl), pirate.ship
assert_equal pirate.id, pirate.ship.pirate_id
assert_equal pirate.id, ships(:black_pearl).reload.pirate_id
@@ -558,6 +574,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal author.post, post
end
+ def test_has_one_loading_for_new_record
+ post = Post.create!(author_id: 42, title: 'foo', body: 'bar')
+ author = Author.new(id: 42)
+ assert_equal post, author.post
+ end
+
def test_has_one_relationship_cannot_have_a_counter_cache
assert_raise(ArgumentError) do
Class.new(ActiveRecord::Base) do
@@ -566,6 +588,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_with_polymorphic_has_one_with_custom_columns_name
+ post = Post.create! :title => 'foo', :body => 'bar'
+ image = Image.create!
+
+ post.main_image = image
+ post.reload
+
+ assert_equal image, post.main_image
+ end
+
test 'dangerous association name raises ArgumentError' do
[:errors, 'errors', :save, 'save'].each do |name|
assert_raises(ArgumentError, "Association #{name} should not be allowed") do
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 19d1aa87a8..f8772547a2 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -15,6 +15,7 @@ require 'models/essay'
require 'models/owner'
require 'models/post'
require 'models/comment'
+require 'models/categorization'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 07cf65a760..b3fe759ad9 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -54,7 +54,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_find_with_implicit_inner_joins_without_select_does_not_imply_readonly
authors = Author.joins(:posts)
assert_not authors.empty?, "expected authors to be non-empty"
- assert authors.none? {|a| a.readonly? }, "expected no authors to be readonly"
+ assert authors.none?(&:readonly?), "expected no authors to be readonly"
end
def test_find_with_implicit_inner_joins_honors_readonly_with_select
@@ -102,7 +102,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_find_with_conditions_on_reflection
assert !posts(:welcome).comments.empty?
- assert Post.joins(:nonexistant_comments).where(:id => posts(:welcome).id).empty? # [sic!]
+ assert Post.joins(:nonexistent_comments).where(:id => posts(:welcome).id).empty? # [sic!]
end
def test_find_with_conditions_on_through_reflection
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 60df4e14dd..423b8238b1 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -10,6 +10,9 @@ require 'models/comment'
require 'models/car'
require 'models/bulb'
require 'models/mixed_case_monkey'
+require 'models/admin'
+require 'models/admin/account'
+require 'models/admin/user'
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
@@ -27,6 +30,15 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection"
end
+ def test_has_many_and_belongs_to_should_find_inverse_automatically_for_model_in_module
+ account_reflection = Admin::Account.reflect_on_association(:users)
+ user_reflection = Admin::User.reflect_on_association(:account)
+
+ assert_respond_to account_reflection, :has_inverse?
+ assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse"
+ assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection"
+ end
+
def test_has_one_and_belongs_to_should_find_inverse_automatically
car_reflection = Car.reflect_on_association(:bulb)
bulb_reflection = Bulb.reflect_on_association(:car)
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index cace7ba142..9918601623 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -393,18 +393,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).taggings_2
+ assert_equal Tagging.find(1,2).sort_by(&:id), authors(:david).taggings_2
end
def test_has_many_through_polymorphic_has_many
- assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by { |t| t.id }
+ assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by(&:id)
end
def test_include_has_many_through_polymorphic_has_many
author = Author.includes(:taggings).find authors(:david).id
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
- assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
+ assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id)
end
end
@@ -444,7 +444,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
author = Author.first
assert author.comments.present?
- assert author.nonexistant_comments.blank?
+ assert author.nonexistent_comments.blank?
end
def test_has_many_through_uses_correct_attributes
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 04ef50e58a..72963fd56c 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -48,7 +48,7 @@ class AssociationsTest < ActiveRecord::TestCase
assert_kind_of Firm, firm
firm.clear_association_cache
- assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
+ assert_equal Firm.find(1).clients.collect(&:name).sort, firm.clients.collect(&:name).sort
end
def test_clear_association_cache_new_record
@@ -264,6 +264,11 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
end
+ test "first! works on loaded associations" do
+ david = authors(:david)
+ assert_equal david.posts.first, david.posts.reload.first!
+ end
+
def test_reset_unloads_target
david = authors(:david)
david.posts.reload
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 731d433706..01ee1234a2 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -502,7 +502,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_typecast_attribute_from_select_to_false
Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
- if current_adapter?(:OracleAdapter)
+ if current_adapter?(:OracleAdapter, :FbAdapter)
topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first
else
topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first
@@ -513,7 +513,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_typecast_attribute_from_select_to_true
Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
- if current_adapter?(:OracleAdapter)
+ if current_adapter?(:OracleAdapter, :FbAdapter)
topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first
else
topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first
@@ -531,20 +531,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_deprecated_cache_attributes
- assert_deprecated do
- Topic.cache_attributes :replies_count
- end
-
- assert_deprecated do
- Topic.cached_attributes
- end
-
- assert_deprecated do
- Topic.cache_attribute? :replies_count
- end
- end
-
def test_converted_values_are_returned_after_assignment
developer = Developer.new(name: 1337, salary: "50000")
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index de63672963..ba53612d30 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -178,5 +178,13 @@ module ActiveRecord
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
builder.build_from_database(foo: '1.1')
end
+
+ test "freezing doesn't prevent the set from materializing" do
+ builder = AttributeSet::Builder.new(foo: Type::String.new)
+ attributes = builder.build_from_database(foo: "1")
+
+ attributes.freeze
+ assert_equal({ foo: "1" }, attributes.to_hash)
+ end
end
end
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 7b325abf1d..39a976fcc8 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -5,6 +5,7 @@ module ActiveRecord
class AttributeTest < ActiveRecord::TestCase
setup do
@type = Minitest::Mock.new
+ @type.expect(:==, false, [false])
end
teardown do
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 71c34d816a..04d5a2869c 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -774,13 +774,13 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
- assert !@pirate.birds.any? { |child| child.marked_for_destruction? }
+ assert !@pirate.birds.any?(&:marked_for_destruction?)
- @pirate.birds.each { |child| child.mark_for_destruction }
+ @pirate.birds.each(&:mark_for_destruction)
klass = @pirate.birds.first.class
ids = @pirate.birds.map(&:id)
- assert @pirate.birds.all? { |child| child.marked_for_destruction? }
+ assert @pirate.birds.all?(&:marked_for_destruction?)
ids.each { |id| assert klass.find_by_id(id) }
@pirate.save
@@ -814,14 +814,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.birds.each { |bird| bird.name = '' }
assert !@pirate.valid?
- @pirate.birds.each { |bird| bird.destroy }
+ @pirate.birds.each(&:destroy)
assert @pirate.valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many
@pirate.birds.create!(:name => "birds_1")
- @pirate.birds.each { |bird| bird.mark_for_destruction }
+ @pirate.birds.each(&:mark_for_destruction)
assert @pirate.save
@pirate.birds.each { |bird| bird.expects(:destroy).never }
@@ -888,7 +888,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
association_name_with_callbacks = "birds_with_#{callback_type}_callbacks"
@pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
- @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
+ @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction)
child_id = @pirate.send(association_name_with_callbacks).first.id
@pirate.ship_log.clear
@@ -906,8 +906,8 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
- assert !@pirate.parrots.any? { |parrot| parrot.marked_for_destruction? }
- @pirate.parrots.each { |parrot| parrot.mark_for_destruction }
+ assert !@pirate.parrots.any?(&:marked_for_destruction?)
+ @pirate.parrots.each(&:mark_for_destruction)
assert_no_difference "Parrot.count" do
@pirate.save
@@ -940,14 +940,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.parrots.each { |parrot| parrot.name = '' }
assert !@pirate.valid?
- @pirate.parrots.each { |parrot| parrot.destroy }
+ @pirate.parrots.each(&:destroy)
assert @pirate.valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm
@pirate.parrots.create!(:name => "parrots_1")
- @pirate.parrots.each { |parrot| parrot.mark_for_destruction }
+ @pirate.parrots.each(&:mark_for_destruction)
assert @pirate.save
Pirate.transaction do
@@ -992,7 +992,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks"
@pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
- @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
+ @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction)
child_id = @pirate.send(association_name_with_callbacks).first.id
@pirate.ship_log.clear
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 098d5b8451..0debd30e5c 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -88,6 +88,7 @@ class BasicsTest < ActiveRecord::TestCase
'Mysql2Adapter' => '`',
'PostgreSQLAdapter' => '"',
'OracleAdapter' => '"',
+ 'FbAdapter' => '"'
}.fetch(classname) {
raise "need a bad char for #{classname}"
}
@@ -111,7 +112,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil Edge.primary_key
end
- unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
+ unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter, :FbAdapter)
def test_limit_with_comma
assert Topic.limit("1,2").to_a
end
@@ -898,93 +899,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 'a text field', default.char3
end
end
-
- class Geometric < ActiveRecord::Base; end
- def test_geometric_content
-
- # accepted format notes:
- # ()'s aren't required
- # values can be a mix of float or integer
-
- g = Geometric.new(
- :a_point => '(5.0, 6.1)',
- #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
- :a_line_segment => '(2.0, 3), (5.5, 7.0)',
- :a_box => '2.0, 3, 5.5, 7.0',
- :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
- :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
- :a_circle => '<(5.3, 10.4), 2>'
- )
-
- assert g.save
-
- # Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
-
- assert_equal [5.0, 6.1], h.a_point
- assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
- assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
- assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
- assert_equal '<(5.3,10.4),2>', h.a_circle
-
- # use a geometric function to test for an open path
- objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
-
- assert_equal true, objs[0].isopen
-
- # test alternate formats when defining the geometric types
-
- g = Geometric.new(
- :a_point => '5.0, 6.1',
- #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
- :a_line_segment => '((2.0, 3), (5.5, 7.0))',
- :a_box => '(2.0, 3), (5.5, 7.0)',
- :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
- :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
- :a_circle => '((5.3, 10.4), 2)'
- )
-
- assert g.save
-
- # Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
-
- assert_equal [5.0, 6.1], h.a_point
- assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
- assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
- assert_equal '<(5.3,10.4),2>', h.a_circle
-
- # use a geometric function to test for an closed path
- objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
-
- assert_equal true, objs[0].isclosed
-
- # test native ruby formats when defining the geometric types
- g = Geometric.new(
- :a_point => [5.0, 6.1],
- #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
- :a_line_segment => '((2.0, 3), (5.5, 7.0))',
- :a_box => '(2.0, 3), (5.5, 7.0)',
- :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
- :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
- :a_circle => '((5.3, 10.4), 2)'
- )
-
- assert g.save
-
- # Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
-
- assert_equal [5.0, 6.1], h.a_point
- assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
- assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
- assert_equal '<(5.3,10.4),2>', h.a_circle
- end
end
class NumericData < ActiveRecord::Base
@@ -1614,4 +1528,14 @@ class BasicsTest < ActiveRecord::TestCase
test "records without an id have unique hashes" do
assert_not_equal Post.new.hash, Post.new.hash
end
+
+ test "resetting column information doesn't remove attribute methods" do
+ topic = topics(:first)
+
+ assert_not topic.id_changed?
+
+ Topic.reset_column_information
+
+ assert_not topic.id_changed?
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index e886268a72..299217214e 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -10,6 +10,7 @@ require 'models/reply'
require 'models/minivan'
require 'models/speedometer'
require 'models/ship_part'
+require 'models/treasure'
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
@@ -609,4 +610,25 @@ class CalculationsTest < ActiveRecord::TestCase
.pluck('topics.title', 'replies_topics.title')
assert_equal expected, actual
end
+
+ def test_calculation_with_polymorphic_relation
+ part = ShipPart.create!(name: "has trinket")
+ part.trinkets.create!
+
+ assert_equal part.id, ShipPart.joins(:trinkets).sum(:id)
+ end
+
+ def test_pluck_joined_with_polymorphic_relation
+ part = ShipPart.create!(name: "has trinket")
+ part.trinkets.create!
+
+ assert_equal [part.id], ShipPart.joins(:trinkets).pluck(:id)
+ end
+
+ def test_grouped_calculation_with_polymorphic_relation
+ part = ShipPart.create!(name: "has trinket")
+ part.trinkets.create!
+
+ assert_equal({ "has trinket" => part.id }, ShipPart.joins(:trinkets).group("ship_parts.name").sum(:id))
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 5e07f8a03c..670d94dc06 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -49,6 +49,11 @@ class CallbackDeveloperWithFalseValidation < CallbackDeveloper
before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
end
+class CallbackDeveloperWithHaltedValidation < CallbackDeveloper
+ before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) }
+ before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
+end
+
class ParentDeveloper < ActiveRecord::Base
self.table_name = 'developers'
attr_accessor :after_save_called
@@ -59,27 +64,6 @@ class ChildDeveloper < ParentDeveloper
end
-class RecursiveCallbackDeveloper < ActiveRecord::Base
- self.table_name = 'developers'
-
- before_save :on_before_save
- after_save :on_after_save
-
- attr_reader :on_before_save_called, :on_after_save_called
-
- def on_before_save
- @on_before_save_called ||= 0
- @on_before_save_called += 1
- save unless @on_before_save_called > 1
- end
-
- def on_after_save
- @on_after_save_called ||= 0
- @on_after_save_called += 1
- save unless @on_after_save_called > 1
- end
-end
-
class ImmutableDeveloper < ActiveRecord::Base
self.table_name = 'developers'
@@ -88,35 +72,24 @@ class ImmutableDeveloper < ActiveRecord::Base
before_save :cancel
before_destroy :cancel
- def cancelled?
- @cancelled == true
- end
-
private
def cancel
- @cancelled = true
false
end
end
-class ImmutableMethodDeveloper < ActiveRecord::Base
+class DeveloperWithCanceledCallbacks < ActiveRecord::Base
self.table_name = 'developers'
- validates_inclusion_of :salary, :in => 50000..200000
-
- def cancelled?
- @cancelled == true
- end
+ validates_inclusion_of :salary, in: 50000..200000
- before_save do
- @cancelled = true
- false
- end
+ before_save :cancel
+ before_destroy :cancel
- before_destroy do
- @cancelled = true
- false
- end
+ private
+ def cancel
+ throw(:abort)
+ end
end
class OnCallbacksDeveloper < ActiveRecord::Base
@@ -182,6 +155,23 @@ class CallbackCancellationDeveloper < ActiveRecord::Base
after_destroy { @after_destroy_called = true }
end
+class CallbackHaltedDeveloper < ActiveRecord::Base
+ self.table_name = 'developers'
+
+ attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called
+ attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy
+
+ before_save { throw(:abort) if defined?(@cancel_before_save) }
+ before_create { throw(:abort) if @cancel_before_create }
+ before_update { throw(:abort) if @cancel_before_update }
+ before_destroy { throw(:abort) if @cancel_before_destroy }
+
+ after_save { @after_save_called = true }
+ after_update { @after_update_called = true }
+ after_create { @after_create_called = true }
+ after_destroy { @after_destroy_called = true }
+end
+
class CallbacksTest < ActiveRecord::TestCase
fixtures :developers
@@ -439,11 +429,14 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
- def test_before_save_returning_false
+ def test_deprecated_before_save_returning_false
david = ImmutableDeveloper.find(1)
- assert david.valid?
- assert !david.save
- assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
+ assert_deprecated do
+ assert david.valid?
+ assert !david.save
+ exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
+ assert_equal exc.record, david
+ end
david = ImmutableDeveloper.find(1)
david.salary = 10_000_000
@@ -453,37 +446,48 @@ class CallbacksTest < ActiveRecord::TestCase
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_save = true
- assert someone.valid?
- assert !someone.save
+ assert_deprecated do
+ assert someone.valid?
+ assert !someone.save
+ end
assert_save_callbacks_not_called(someone)
end
- def test_before_create_returning_false
+ def test_deprecated_before_create_returning_false
someone = CallbackCancellationDeveloper.new
someone.cancel_before_create = true
- assert someone.valid?
- assert !someone.save
+ assert_deprecated do
+ assert someone.valid?
+ assert !someone.save
+ end
assert_save_callbacks_not_called(someone)
end
- def test_before_update_returning_false
+ def test_deprecated_before_update_returning_false
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_update = true
- assert someone.valid?
- assert !someone.save
+ assert_deprecated do
+ assert someone.valid?
+ assert !someone.save
+ end
assert_save_callbacks_not_called(someone)
end
- def test_before_destroy_returning_false
+ def test_deprecated_before_destroy_returning_false
david = ImmutableDeveloper.find(1)
- assert !david.destroy
- assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
+ assert_deprecated do
+ assert !david.destroy
+ exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
+ assert_equal exc.record, david
+ end
assert_not_nil ImmutableDeveloper.find_by_id(1)
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_destroy = true
- assert !someone.destroy
- assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
+ assert_deprecated do
+ assert !someone.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
+ end
assert !someone.after_destroy_called
end
@@ -494,9 +498,59 @@ class CallbacksTest < ActiveRecord::TestCase
end
private :assert_save_callbacks_not_called
+ def test_before_create_throwing_abort
+ someone = CallbackHaltedDeveloper.new
+ someone.cancel_before_create = true
+ assert someone.valid?
+ assert !someone.save
+ assert_save_callbacks_not_called(someone)
+ end
+
+ def test_before_save_throwing_abort
+ david = DeveloperWithCanceledCallbacks.find(1)
+ assert david.valid?
+ assert !david.save
+ exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
+ assert_equal exc.record, david
+
+ david = DeveloperWithCanceledCallbacks.find(1)
+ david.salary = 10_000_000
+ assert !david.valid?
+ assert !david.save
+ assert_raise(ActiveRecord::RecordInvalid) { david.save! }
+
+ someone = CallbackHaltedDeveloper.find(1)
+ someone.cancel_before_save = true
+ assert someone.valid?
+ assert !someone.save
+ assert_save_callbacks_not_called(someone)
+ end
+
+ def test_before_update_throwing_abort
+ someone = CallbackHaltedDeveloper.find(1)
+ someone.cancel_before_update = true
+ assert someone.valid?
+ assert !someone.save
+ assert_save_callbacks_not_called(someone)
+ end
+
+ def test_before_destroy_throwing_abort
+ david = DeveloperWithCanceledCallbacks.find(1)
+ assert !david.destroy
+ exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
+ assert_equal exc.record, david
+ assert_not_nil ImmutableDeveloper.find_by_id(1)
+
+ someone = CallbackHaltedDeveloper.find(1)
+ someone.cancel_before_destroy = true
+ assert !someone.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
+ assert !someone.after_destroy_called
+ end
+
def test_callback_returning_false
david = CallbackDeveloperWithFalseValidation.find(1)
- david.save
+ assert_deprecated { david.save }
assert_equal [
[ :after_find, :method ],
[ :after_find, :string ],
@@ -522,6 +576,34 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
+ def test_callback_throwing_abort
+ david = CallbackDeveloperWithHaltedValidation.find(1)
+ david.save
+ assert_equal [
+ [ :after_find, :method ],
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :method ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_validation, :method ],
+ [ :before_validation, :string ],
+ [ :before_validation, :proc ],
+ [ :before_validation, :object ],
+ [ :before_validation, :block ],
+ [ :before_validation, :throwing_abort ],
+ [ :after_rollback, :block ],
+ [ :after_rollback, :object ],
+ [ :after_rollback, :proc ],
+ [ :after_rollback, :string ],
+ [ :after_rollback, :method ],
+ ], david.history
+ end
+
def test_inheritance_of_callbacks
parent = ParentDeveloper.new
assert !parent.after_save_called
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 3e33b30144..b72f8ca88c 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -44,9 +44,7 @@ module ActiveRecord
end
def test_connection_pools
- assert_deprecated do
- assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools)
- end
+ assert_equal([@pool], @handler.connection_pools)
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 37ad469476..9ee92a3cd2 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -51,34 +51,6 @@ module ActiveRecord
assert_equal expected, actual
end
- def test_resolver_with_database_uri_and_and_current_env_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve_spec("default_env", config) }
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_and_current_env_string_key_and_rails_env
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- ENV['RAILS_ENV'] = "foo"
-
- config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve_spec("foo", config) }
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_and_current_env_string_key_and_rack_env
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- ENV['RACK_ENV'] = "foo"
-
- config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve_spec("foo", config) }
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
- assert_equal expected, actual
- end
-
def test_resolver_with_database_uri_and_known_key
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
@@ -95,16 +67,6 @@ module ActiveRecord
end
end
- def test_resolver_with_database_uri_and_unknown_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- assert_deprecated do
- assert_raises AdapterNotSpecified do
- resolve_spec("production", config)
- end
- end
- end
-
def test_resolver_with_database_uri_and_supplied_url
ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo"
config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index c0491bbee5..330232cee2 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -3,6 +3,8 @@ require 'models/topic'
require 'models/task'
class DateTimeTest < ActiveRecord::TestCase
+ include InTimeZone
+
def test_saves_both_date_and_time
with_env_tz 'America/New_York' do
with_timezone_config default: :utc do
@@ -29,6 +31,14 @@ class DateTimeTest < ActiveRecord::TestCase
assert_nil task.ending
end
+ def test_assign_bad_date_time_with_timezone
+ in_time_zone "Pacific Time (US & Canada)" do
+ task = Task.new
+ task.starting = '2014-07-01T24:59:59GMT'
+ assert_nil task.starting
+ end
+ end
+
def test_assign_empty_date
topic = Topic.new
topic.last_read = ''
@@ -40,4 +50,12 @@ class DateTimeTest < ActiveRecord::TestCase
topic.bonus_time = ''
assert_nil topic.bonus_time
end
+
+ def test_assign_in_local_timezone
+ now = DateTime.now
+ with_timezone_config default: :local do
+ task = Task.new starting: now
+ assert now, task.starting
+ end
+ end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index c089e63128..e9bc583bf4 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -18,25 +18,48 @@ class DefaultTest < ActiveRecord::TestCase
end
end
- if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
- def test_default_integers
- default = Default.new
- assert_instance_of Fixnum, default.positive_integer
- assert_equal 1, default.positive_integer
- assert_instance_of Fixnum, default.negative_integer
- assert_equal(-1, default.negative_integer)
- assert_instance_of BigDecimal, default.decimal_number
- assert_equal BigDecimal.new("2.78"), default.decimal_number
- end
- end
-
if current_adapter?(:PostgreSQLAdapter)
def test_multiline_default_text
+ record = Default.new
# older postgres versions represent the default with escapes ("\\012" for a newline)
- assert( "--- []\n\n" == Default.columns_hash['multiline_default'].default ||
- "--- []\\012\\012" == Default.columns_hash['multiline_default'].default)
+ assert("--- []\n\n" == record.multiline_default || "--- []\\012\\012" == record.multiline_default)
+ end
+ end
+end
+
+class DefaultNumbersTest < ActiveRecord::TestCase
+ class DefaultNumber < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :default_numbers do |t|
+ t.integer :positive_integer, default: 7
+ t.integer :negative_integer, default: -5
+ t.decimal :decimal_number, default: "2.78", precision: 5, scale: 2
end
end
+
+ teardown do
+ @connection.drop_table "default_numbers" if @connection.table_exists? 'default_numbers'
+ end
+
+ def test_default_positive_integer
+ record = DefaultNumber.new
+ assert_equal 7, record.positive_integer
+ assert_equal "7", record.positive_integer_before_type_cast
+ end
+
+ def test_default_negative_integer
+ record = DefaultNumber.new
+ assert_equal (-5), record.negative_integer
+ assert_equal "-5", record.negative_integer_before_type_cast
+ end
+
+ def test_default_decimal_number
+ record = DefaultNumber.new
+ assert_equal BigDecimal.new("2.78"), record.decimal_number
+ assert_equal "2.78", record.decimal_number_before_type_cast
+ end
end
class DefaultStringsTest < ActiveRecord::TestCase
@@ -99,19 +122,21 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_mysql_text_not_null_defaults_non_strict
using_strict(false) do
with_text_blob_not_null_table do |klass|
- assert_equal '', klass.columns_hash['non_null_blob'].default
- assert_equal '', klass.columns_hash['non_null_text'].default
+ record = klass.new
+ assert_equal '', record.non_null_blob
+ assert_equal '', record.non_null_text
- assert_nil klass.columns_hash['null_blob'].default
- assert_nil klass.columns_hash['null_text'].default
+ assert_nil record.null_blob
+ assert_nil record.null_text
- instance = klass.create!
+ record.save!
+ record.reload
- assert_equal '', instance.non_null_text
- assert_equal '', instance.non_null_blob
+ assert_equal '', record.non_null_text
+ assert_equal '', record.non_null_blob
- assert_nil instance.null_text
- assert_nil instance.null_blob
+ assert_nil record.null_text
+ assert_nil record.null_blob
end
end
end
@@ -119,10 +144,11 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_mysql_text_not_null_defaults_strict
using_strict(true) do
with_text_blob_not_null_table do |klass|
- assert_nil klass.columns_hash['non_null_blob'].default
- assert_nil klass.columns_hash['non_null_text'].default
- assert_nil klass.columns_hash['null_blob'].default
- assert_nil klass.columns_hash['null_text'].default
+ record = klass.new
+ assert_nil record.non_null_blob
+ assert_nil record.non_null_text
+ assert_nil record.null_blob
+ assert_nil record.null_text
assert_raises(ActiveRecord::StatementInvalid) { klass.create }
end
@@ -172,48 +198,3 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
end
end
end
-
-if current_adapter?(:PostgreSQLAdapter)
- class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase
- def setup
- @connection = ActiveRecord::Base.connection
-
- @old_search_path = @connection.schema_search_path
- @connection.schema_search_path = "schema_1, pg_catalog"
- @connection.create_table "defaults" do |t|
- t.text "text_col", :default => "some value"
- t.string "string_col", :default => "some value"
- end
- Default.reset_column_information
- end
-
- def test_text_defaults_in_new_schema_when_overriding_domain
- assert_equal "some value", Default.new.text_col, "Default of text column was not correctly parse"
- end
-
- def test_string_defaults_in_new_schema_when_overriding_domain
- assert_equal "some value", Default.new.string_col, "Default of string column was not correctly parse"
- end
-
- def test_bpchar_defaults_in_new_schema_when_overriding_domain
- @connection.execute "ALTER TABLE defaults ADD bpchar_col bpchar DEFAULT 'some value'"
- Default.reset_column_information
- assert_equal "some value", Default.new.bpchar_col, "Default of bpchar column was not correctly parse"
- end
-
- def test_text_defaults_after_updating_column_default
- @connection.execute "ALTER TABLE defaults ALTER COLUMN text_col SET DEFAULT 'some text'::schema_1.text"
- assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db"
- end
-
- def test_default_containing_quote_and_colons
- @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'"
- assert_equal "foo'::bar", Default.new.string_col
- end
-
- teardown do
- @connection.schema_search_path = @old_search_path
- Default.reset_column_information
- end
- end
-end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index eb9b1a2d74..5b71bd7e67 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -165,18 +165,6 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal parrot.name_change, parrot.title_change
end
- def test_reset_attribute!
- pirate = Pirate.create!(:catchphrase => 'Yar!')
- pirate.catchphrase = 'Ahoy!'
-
- assert_deprecated do
- pirate.reset_catchphrase!
- end
- assert_equal "Yar!", pirate.catchphrase
- assert_equal Hash.new, pirate.changes
- assert !pirate.catchphrase_changed?
- end
-
def test_restore_attribute!
pirate = Pirate.create!(:catchphrase => 'Yar!')
pirate.catchphrase = 'Ahoy!'
@@ -688,7 +676,14 @@ class DirtyTest < ActiveRecord::TestCase
serialize :data
end
- klass.create!(data: "foo")
+ binary = klass.create!(data: "\\\\foo")
+
+ assert_not binary.changed?
+
+ binary.data = binary.data.dup
+
+ assert_not binary.changed?
+
binary = klass.last
assert_not binary.changed?
@@ -698,6 +693,21 @@ class DirtyTest < ActiveRecord::TestCase
assert binary.changed?
end
+ test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do
+ test_type_class = Class.new(ActiveRecord::Type::Value) do
+ define_method(:changed_in_place?) do |*|
+ raise
+ end
+ end
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'people'
+ attribute :foo, test_type_class.new
+ end
+
+ model = klass.new(first_name: "Jim")
+ assert model.first_name_changed?
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 33a59d4678..39308866ee 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -15,9 +15,11 @@ require 'models/customer'
require 'models/toy'
require 'models/matey'
require 'models/dog'
+require 'models/car'
+require 'models/tyre'
class FinderTest < ActiveRecord::TestCase
- fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations
+ fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations, :cars
def test_find_by_id_with_hash
assert_raises(ActiveRecord::StatementInvalid) do
@@ -53,10 +55,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_symbols_table_ref
+ gc_disabled = GC.disable
Post.where("author_id" => nil) # warm up
x = Symbol.all_symbols.count
Post.where("title" => {"xxxqqqq" => "bar"})
assert_equal x, Symbol.all_symbols.count
+ ensure
+ GC.enable if gc_disabled == false
end
# find should handle strings that come from URLs
@@ -489,6 +494,12 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
end
+ def test_find_on_combined_explicit_and_hashed_table_names
+ assert Topic.where('topics.approved' => false, topics: { author_name: "David" }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true, topics: { author_name: "David" }).find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => false, topics: { author_name: "Melanie" }).find(1) }
+ end
+
def test_find_with_hash_conditions_on_joined_table
firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
assert_equal 1, firms.size
@@ -537,30 +548,6 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [1,2,6,7,8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
end
- def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges
- assert_deprecated do
- assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1..2], 3, [5], 6..8, 9]).to_a.map(&:id).sort
- end
- end
-
- def test_find_on_hash_conditions_with_array_of_integers_and_arrays
- assert_deprecated do
- assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1, 2], 3, 5, [6, [7], 8], 9]).to_a.map(&:id).sort
- end
- end
-
- def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_nils
- assert_deprecated do
- assert_equal [1,3,4,5], Topic.where(parent_id: [[2..6], nil]).to_a.map(&:id).sort
- end
- end
-
- def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_more_nils
- assert_deprecated do
- assert_equal [], Topic.where(parent_id: [[7..10, nil, [nil]], [nil]]).to_a.map(&:id).sort
- end
- end
-
def test_find_on_multiple_hash_conditions
assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
@@ -934,7 +921,7 @@ class FinderTest < ActiveRecord::TestCase
joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').
where('project_id=1').to_a
assert_equal 3, developers_on_project_one.length
- developer_names = developers_on_project_one.map { |d| d.name }
+ developer_names = developers_on_project_one.map(&:name)
assert developer_names.include?('David')
assert developer_names.include?('Jamis')
end
@@ -989,7 +976,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_select_values
- assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
+ assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map!(&:to_s)
assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
end
@@ -1015,7 +1002,7 @@ class FinderTest < ActiveRecord::TestCase
where(client_of: [2, 1, nil],
name: ['37signals', 'Summit', 'Microsoft']).
order('client_of DESC').
- map { |x| x.client_of }
+ map(&:client_of)
assert client_of.include?(nil)
assert_equal [2, 1].sort, client_of.compact.sort
@@ -1025,7 +1012,7 @@ class FinderTest < ActiveRecord::TestCase
client_of = Company.
where(client_of: [nil]).
order('client_of DESC').
- map { |x| x.client_of }
+ map(&:client_of)
assert_equal [], client_of.compact
end
@@ -1116,6 +1103,26 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ test "find on a scope does not perform statement caching" do
+ honda = cars(:honda)
+ zyke = cars(:zyke)
+ tyre = honda.tyres.create!
+ tyre2 = zyke.tyres.create!
+
+ assert_equal tyre, honda.tyres.custom_find(tyre.id)
+ assert_equal tyre2, zyke.tyres.custom_find(tyre2.id)
+ end
+
+ test "find_by on a scope does not perform statement caching" do
+ honda = cars(:honda)
+ zyke = cars(:zyke)
+ tyre = honda.tyres.create!
+ tyre2 = zyke.tyres.create!
+
+ assert_equal tyre, honda.tyres.custom_find_by(id: tyre.id)
+ assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id)
+ end
+
protected
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 4b0ebe13d1..9edeb8b47f 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -5,6 +5,7 @@ require 'models/admin/randomly_named_c1'
require 'models/admin/user'
require 'models/binary'
require 'models/book'
+require 'models/bulb'
require 'models/category'
require 'models/company'
require 'models/computer'
@@ -853,3 +854,16 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
end
end
+
+class FixturesWithDefaultScopeTest < ActiveRecord::TestCase
+ fixtures :bulbs
+
+ test "inserts fixtures excluded by a default scope" do
+ assert_equal 1, Bulb.count
+ assert_equal 2, Bulb.unscoped.count
+ end
+
+ test "allows access to fixtures excluded by a default scope" do
+ assert_equal "special", bulbs(:special).name
+ end
+end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 80ac57ec7c..925491acbd 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -24,9 +24,6 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-# Enable raise errors in after_commit and after_rollback.
-ActiveRecord::Base.raise_in_transactional_callbacks = true
-
# Connect to the database
ARTest.connect
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 792950d24d..fe6323ab02 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -22,7 +22,7 @@ class InheritanceTest < ActiveRecord::TestCase
company = Company.first
company = company.dup
company.extend(Module.new {
- def read_attribute(name)
+ def _read_attribute(name)
return ' ' if name == 'type'
super
end
@@ -304,7 +304,7 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_primary_key_quoting
con = Account.connection
- assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do
+ assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1/) do
Account.all.merge!(:includes => :firm).find(1)
end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 5a4b1fb919..ee43f07dd7 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -33,8 +33,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
p1 = Person.find(1)
assert_equal 0, p1.lock_version
- Person.expects(:quote_value).with(0, Person.columns_hash[Person.locking_column]).returns('0').once
-
p1.first_name = 'anika2'
p1.save!
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index d91e7142b3..b3129a8984 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -82,7 +82,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array
+ assert array_column.array?
end
def test_create_table_with_array_column
@@ -93,7 +93,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array
+ assert array_column.array?
end
end
@@ -195,32 +195,29 @@ module ActiveRecord
end
def test_create_table_with_timestamps_should_create_datetime_columns
- # FIXME: Remove the silence when we change the default `null` behavior
- ActiveSupport::Deprecation.silence do
- connection.create_table table_name do |t|
- t.timestamps
- end
+ connection.create_table table_name do |t|
+ t.timestamps
end
created_columns = connection.columns(table_name)
created_at_column = created_columns.detect {|c| c.name == 'created_at' }
updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
- assert created_at_column.null
- assert updated_at_column.null
+ assert !created_at_column.null
+ assert !updated_at_column.null
end
def test_create_table_with_timestamps_should_create_datetime_columns_with_options
connection.create_table table_name do |t|
- t.timestamps :null => false
+ t.timestamps null: true
end
created_columns = connection.columns(table_name)
created_at_column = created_columns.detect {|c| c.name == 'created_at' }
updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
- assert !created_at_column.null
- assert !updated_at_column.null
+ assert created_at_column.null
+ assert updated_at_column.null
end
def test_create_table_without_a_block
@@ -415,5 +412,36 @@ module ActiveRecord
yield
end
end
+
+ if ActiveRecord::Base.connection.supports_foreign_keys?
+ class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :trains
+ @connection.create_table(:wagons) { |t| t.references :train }
+ @connection.add_foreign_key :wagons, :trains
+ end
+
+ teardown do
+ [:wagons, :trains].each do |table|
+ @connection.drop_table(table) if @connection.table_exists?(table)
+ end
+ end
+
+ def test_create_table_with_force_cascade_drops_dependent_objects
+ skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ # can't re-create table referenced by foreign key
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.create_table :trains, force: true
+ end
+
+ # can recreate referenced table with force: :cascade
+ @connection.create_table :trains, force: :cascade
+ assert_equal [], @connection.foreign_keys(:wagons)
+ end
+ end
+ end
end
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 6613783fba..7010af5434 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -95,8 +95,8 @@ module ActiveRecord
def test_remove_timestamps_creates_updated_at_and_created_at
with_change_table do |t|
- @connection.expect :remove_timestamps, nil, [:delete_me]
- t.remove_timestamps
+ @connection.expect :remove_timestamps, nil, [:delete_me, { null: true }]
+ t.remove_timestamps({ null: true })
end
end
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
index 77a752f050..62186e13a5 100644
--- a/activerecord/test/cases/migration/column_positioning_test.rb
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -25,30 +25,30 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_column_positioning
- assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first second third), conn.columns(:testings).map(&:name)
end
def test_add_column_with_positioning
conn.add_column :testings, :new_col, :integer
- assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first second third new_col), conn.columns(:testings).map(&:name)
end
def test_add_column_with_positioning_first
conn.add_column :testings, :new_col, :integer, :first => true
- assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(new_col first second third), conn.columns(:testings).map(&:name)
end
def test_add_column_with_positioning_after
conn.add_column :testings, :new_col, :integer, :after => :first
- assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first new_col second third), conn.columns(:testings).map(&:name)
end
def test_change_column_with_positioning
conn.change_column :testings, :second, :integer, :first => true
- assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(second first third), conn.columns(:testings).map(&:name)
conn.change_column :testings, :second, :integer, :after => :third
- assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first third second), conn.columns(:testings).map(&:name)
end
end
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index e955beae1a..8cba777fe2 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -237,8 +237,8 @@ module ActiveRecord
end
def test_invert_remove_timestamps
- add = @recorder.inverse_of :remove_timestamps, [:table]
- assert_equal [:add_timestamps, [:table], nil], add
+ add = @recorder.inverse_of :remove_timestamps, [:table, { null: true }]
+ assert_equal [:add_timestamps, [:table, {null: true }], nil], add
end
def test_invert_add_reference
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 52e963a14f..ad35d690bd 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -29,7 +29,7 @@ module ActiveRecord
teardown do
if defined?(@connection)
- @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts'
+ @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts'
@connection.drop_table "rockets" if @connection.table_exists? 'rockets'
end
end
@@ -162,6 +162,14 @@ module ActiveRecord
assert_equal [], @connection.foreign_keys("astronauts")
end
+ def test_remove_foreign_key_by_symbol_column
+ @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id
+
+ assert_equal 1, @connection.foreign_keys("astronauts").size
+ @connection.remove_foreign_key :astronauts, column: :rocket_id
+ assert_equal [], @connection.foreign_keys("astronauts")
+ end
+
def test_remove_foreign_key_by_name
@connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
@@ -212,6 +220,18 @@ module ActiveRecord
ensure
silence_stream($stdout) { migration.migrate(:down) }
end
+
+ private
+
+ def silence_stream(stream)
+ old_stream = stream.dup
+ stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
+ stream.sync = true
+ yield
+ ensure
+ stream.reopen(old_stream)
+ old_stream.close
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index ac932378fd..b23b9a679f 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -36,6 +36,20 @@ module ActiveRecord
assert connection.index_name_exists?(table_name, 'new_idx', true)
end
+ def test_rename_index_too_long
+ too_long_index_name = good_index_name + 'x'
+ # keep the names short to make Oracle and similar behave
+ connection.add_index(table_name, [:foo], :name => 'old_idx')
+ e = assert_raises(ArgumentError) {
+ connection.rename_index(table_name, 'old_idx', too_long_index_name)
+ }
+ assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message)
+
+ # if the adapter doesn't support the indexes call, pick defaults that let the test pass
+ assert connection.index_name_exists?(table_name, 'old_idx', false)
+ end
+
+
def test_double_add_index
connection.add_index(table_name, [:foo], :name => 'some_idx')
assert_raises(ArgumentError) {
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
new file mode 100644
index 0000000000..bba8897a0d
--- /dev/null
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -0,0 +1,101 @@
+require 'cases/helper'
+
+if ActiveRecord::Base.connection.supports_foreign_keys?
+module ActiveRecord
+ class Migration
+ class ReferencesForeignKeyTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:testing_parents, force: true)
+ end
+
+ teardown do
+ @connection.execute("drop table if exists testings")
+ @connection.execute("drop table if exists testing_parents")
+ end
+
+ test "foreign keys can be created with the table" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, foreign_key: true
+ end
+
+ fk = @connection.foreign_keys("testings").first
+ assert_equal "testings", fk.from_table
+ assert_equal "testing_parents", fk.to_table
+ end
+
+ test "no foreign key is created by default" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent
+ end
+
+ assert_equal [], @connection.foreign_keys("testings")
+ end
+
+ test "options hash can be passed" do
+ @connection.change_table :testing_parents do |t|
+ t.integer :other_id
+ t.index :other_id, unique: true
+ end
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, foreign_key: { primary_key: :other_id }
+ end
+
+ fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" }
+ assert_equal "other_id", fk.primary_key
+ end
+
+ test "foreign keys cannot be added to polymorphic relations when creating the table" do
+ @connection.create_table :testings do |t|
+ assert_raises(ArgumentError) do
+ t.references :testing_parent, polymorphic: true, foreign_key: true
+ end
+ end
+ end
+
+ test "foreign keys can be created while changing the table" do
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ t.references :testing_parent, foreign_key: true
+ end
+
+ fk = @connection.foreign_keys("testings").first
+ assert_equal "testings", fk.from_table
+ assert_equal "testing_parents", fk.to_table
+ end
+
+ test "foreign keys are not added by default when changing the table" do
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ t.references :testing_parent
+ end
+
+ assert_equal [], @connection.foreign_keys("testings")
+ end
+
+ test "foreign keys accept options when changing the table" do
+ @connection.change_table :testing_parents do |t|
+ t.integer :other_id
+ t.index :other_id, unique: true
+ end
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ t.references :testing_parent, foreign_key: { primary_key: :other_id }
+ end
+
+ fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" }
+ assert_equal "other_id", fk.primary_key
+ end
+
+ test "foreign keys cannot be added to polymorphic relations when changing the table" do
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ assert_raises(ArgumentError) do
+ t.references :testing_parent, polymorphic: true, foreign_key: true
+ end
+ end
+ 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 c8b3f75e10..a018bac43d 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -39,33 +39,35 @@ module ActiveRecord
end
end
- def test_rename_table
- rename_table :test_models, :octopi
+ unless current_adapter?(:FbAdapter) # Firebird cannot rename tables
+ def test_rename_table
+ rename_table :test_models, :octopi
- connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+ connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
- end
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+ end
- def test_rename_table_with_an_index
- add_index :test_models, :url
+ def test_rename_table_with_an_index
+ add_index :test_models, :url
- rename_table :test_models, :octopi
+ rename_table :test_models, :octopi
- connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+ connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
- index = connection.indexes(:octopi).first
- assert index.columns.include?("url")
- assert_equal 'index_octopi_on_url', index.name
- end
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+ index = connection.indexes(:octopi).first
+ assert index.columns.include?("url")
+ assert_equal 'index_octopi_on_url', index.name
+ end
- def test_rename_table_does_not_rename_custom_named_index
- add_index :test_models, :url, name: 'special_url_idx'
+ def test_rename_table_does_not_rename_custom_named_index
+ add_index :test_models, :url, name: 'special_url_idx'
- rename_table :test_models, :octopi
+ rename_table :test_models, :octopi
- assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name)
+ assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name)
+ end
end
if current_adapter?(:PostgreSQLAdapter)
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 3192b797b4..5829ef2100 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -936,4 +936,14 @@ class CopyMigrationsTest < ActiveRecord::TestCase
end
end
end
+
+ def silence_stream(stream)
+ old_stream = stream.dup
+ stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
+ stream.sync = true
+ yield
+ ensure
+ stream.reopen(old_stream)
+ old_stream.close
+ end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index f05ca900aa..c0daa83e9c 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -149,7 +149,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
ActiveRecord::Migrator.new(:up, migrations).migrate
- assert migrations.all? { |m| m.went_up }
+ assert migrations.all?(&:went_up)
assert migrations.all? { |m| !m.went_down }
assert_equal 2, ActiveRecord::Migrator.current_version
end
@@ -160,7 +160,7 @@ class MigratorTest < ActiveRecord::TestCase
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
ActiveRecord::Migrator.new(:down, migrations).migrate
assert migrations.all? { |m| !m.went_up }
- assert migrations.all? { |m| m.went_down }
+ assert migrations.all?(&:went_down)
assert_equal 0, ActiveRecord::Migrator.current_version
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index cf96c3fccf..5c7e8a65d2 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -13,7 +13,7 @@ require 'active_support/hash_with_indifferent_access'
class TestNestedAttributesInGeneral < ActiveRecord::TestCase
teardown do
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_base_should_have_an_empty_nested_attributes_options
@@ -300,13 +300,13 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc(&:empty?)
@pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' })
assert_equal @ship, @pirate.reload.ship
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_should_also_work_with_a_HashWithIndifferentAccess
@@ -494,12 +494,12 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
+ Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc(&:empty?)
@ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' })
assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
ensure
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_should_work_with_update_as_well
@@ -855,7 +855,7 @@ end
module NestedAttributesLimitTests
def teardown
- Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_limit_with_less_records
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 2bcd496415..d6816041bc 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -127,7 +127,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_difference('Topic.count', -topics_by_mary.size) do
destroyed = Topic.destroy_all(conditions).sort_by(&:id)
assert_equal topics_by_mary, destroyed
- assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen"
+ assert destroyed.all?(&:frozen?), "destroyed topics should be frozen"
end
end
@@ -137,7 +137,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_difference('Client.count', -2) do
destroyed = Client.destroy([2, 3]).sort_by(&:id)
assert_equal clients, destroyed
- assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
+ assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
end
end
@@ -252,8 +252,10 @@ class PersistenceTest < ActiveRecord::TestCase
def test_create_columns_not_equal_attributes
topic = Topic.instantiate(
- 'title' => 'Another New Topic',
- 'does_not_exist' => 'test'
+ 'attributes' => {
+ 'title' => 'Another New Topic',
+ 'does_not_exist' => 'test'
+ }
)
assert_nothing_raised { topic.save }
end
@@ -878,4 +880,35 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal "Welcome to the weblog", post.title
assert_not post.new_record?
end
+
+ class SaveTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def test_save_touch_false
+ widget = Class.new(ActiveRecord::Base) do
+ connection.create_table :widgets, force: true do |t|
+ t.string :name
+ t.timestamps null: false
+ end
+
+ self.table_name = :widgets
+ end
+
+ instance = widget.create!({
+ name: 'Bob',
+ created_at: 1.day.ago,
+ updated_at: 1.day.ago
+ })
+
+ created_at = instance.created_at
+ updated_at = instance.updated_at
+
+ instance.name = 'Barb'
+ instance.save!(touch: false)
+ assert_equal instance.created_at, created_at
+ assert_equal instance.updated_at, updated_at
+ ensure
+ ActiveRecord::Base.connection.drop_table :widgets
+ end
+ end
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 8eea10143f..287a3f33ea 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -13,7 +13,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
teardown do
ActiveRecord::Base.clear_all_connections!
ActiveRecord::Base.establish_connection(@connection)
- @per_test_teardown.each {|td| td.call }
+ @per_test_teardown.each(&:call)
end
# Will deadlock due to lack of Monitor timeouts in 1.9
@@ -35,6 +35,22 @@ class PooledConnectionsTest < ActiveRecord::TestCase
end
end
+ def checkout_checkin_connections_loop(pool_size, loops)
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5}))
+ @connection_count = 0
+ @timed_out = 0
+ loops.times do
+ begin
+ conn = ActiveRecord::Base.connection_pool.checkout
+ ActiveRecord::Base.connection_pool.checkin conn
+ @connection_count += 1
+ ActiveRecord::Base.connection.tables
+ rescue ActiveRecord::ConnectionTimeoutError
+ @timed_out += 1
+ end
+ end
+ end
+
def test_pooled_connection_checkin_one
checkout_checkin_connections 1, 2
assert_equal 2, @connection_count
@@ -42,6 +58,20 @@ class PooledConnectionsTest < ActiveRecord::TestCase
assert_equal 1, ActiveRecord::Base.connection_pool.connections.size
end
+ def test_pooled_connection_checkin_two
+ checkout_checkin_connections_loop 2, 3
+ assert_equal 3, @connection_count
+ assert_equal 0, @timed_out
+ assert_equal 2, ActiveRecord::Base.connection_pool.connections.size
+ end
+
+ def test_pooled_connection_remove
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.5}))
+ old_connection = ActiveRecord::Base.connection
+ extra_connection = ActiveRecord::Base.connection_pool.checkout
+ ActiveRecord::Base.connection_pool.remove(extra_connection)
+ assert_equal ActiveRecord::Base.connection, old_connection
+ end
private
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index f19a6ea5e3..751eccc015 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
require 'models/topic'
require 'models/reply'
require 'models/subscriber'
@@ -195,6 +196,37 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
end
end
+class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_fixtures = false
+
+ class Barcode < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true)
+ end
+
+ teardown do
+ @connection.execute("DROP TABLE IF EXISTS barcodes")
+ end
+
+ def test_any_type_primary_key
+ assert_equal "code", Barcode.primary_key
+
+ column_type = Barcode.type_for_attribute(Barcode.primary_key)
+ assert_equal :string, column_type.type
+ assert_equal 42, column_type.limit
+ end
+
+ test "schema dump primary key includes type and options" do
+ schema = dump_table_schema "barcodes"
+ assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema
+ end
+end
+
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -209,8 +241,10 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
end
end
-if current_adapter?(:PostgreSQLAdapter)
+if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyBigSerialTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
self.use_transactional_fixtures = false
class Widget < ActiveRecord::Base
@@ -218,19 +252,35 @@ if current_adapter?(:PostgreSQLAdapter)
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table(:widgets, id: :bigserial) { |t| }
+ if current_adapter?(:PostgreSQLAdapter)
+ @connection.create_table(:widgets, id: :bigserial, force: true)
+ else
+ @connection.create_table(:widgets, id: :bigint, force: true)
+ end
end
teardown do
- @connection.drop_table :widgets
+ @connection.execute("DROP TABLE IF EXISTS widgets")
end
- def test_bigserial_primary_key
- assert_equal "id", Widget.primary_key
- assert_equal :integer, Widget.columns_hash[Widget.primary_key].type
+ test "primary key column type with bigserial" do
+ column_type = Widget.type_for_attribute(Widget.primary_key)
+ assert_equal :integer, column_type.type
+ assert_equal 8, column_type.limit
+ end
+ test "primary key with bigserial are automatically numbered" do
widget = Widget.create!
assert_not_nil widget.id
end
+
+ test "schema dump primary key with bigserial" do
+ schema = dump_table_schema "widgets"
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_match %r{create_table "widgets", id: :bigserial}, schema
+ else
+ assert_match %r{create_table "widgets", id: :bigint}, schema
+ end
+ end
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 9d89d6a1e8..744f9edc47 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -212,6 +212,38 @@ class QueryCacheTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.configurations = conf
end
+
+ def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
+ ActiveRecord::Base.connection.enable_query_cache!
+ post = Post.first
+
+ Post.transaction do
+ post.update_attributes(title: 'rollback')
+ assert_equal 1, Post.where(title: 'rollback').to_a.count
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+
+ ActiveRecord::Base.connection.uncached do
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+ end
+
+ begin
+ Post.transaction do
+ post.update_attributes(title: 'rollback')
+ assert_equal 1, Post.where(title: 'rollback').to_a.count
+ raise 'broken'
+ end
+ rescue Exception
+ end
+
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+
+ ActiveRecord::Base.connection.uncached do
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+ end
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index f52fd22489..cccfc6774e 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -60,7 +60,7 @@ module ActiveRecord
def test_connection_pool_starts_reaper
spec = ActiveRecord::Base.connection_pool.spec.dup
- spec.config[:reaping_frequency] = 0.0001
+ spec.config[:reaping_frequency] = '0.0001'
pool = ConnectionPool.new spec
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 84abaf0291..a2252a836f 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -50,13 +50,13 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_columns_are_returned_in_the_order_they_were_declared
- column_names = Topic.columns.map { |column| column.name }
+ column_names = Topic.columns.map(&:name)
assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
end
def test_content_columns
content_columns = Topic.content_columns
- content_column_names = content_columns.map {|column| column.name}
+ content_column_names = content_columns.map(&:name)
assert_equal 13, content_columns.length
assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort
end
@@ -80,10 +80,24 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal :integer, @first.column_for_attribute("id").type
end
- def test_non_existent_columns_return_nil
- assert_deprecated do
- assert_nil @first.column_for_attribute("attribute_that_doesnt_exist")
- end
+ def test_non_existent_columns_return_null_object
+ column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
+ 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")
+ 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)
end
def test_reflection_klass_for_nested_class_name
@@ -209,6 +223,10 @@ class ReflectionTest < ActiveRecord::TestCase
assert_not_equal Object.new, Firm._reflections['clients']
end
+ def test_reflections_should_return_keys_as_strings
+ assert Category.reflections.keys.all? { |key| key.is_a? String }, "Model.reflections is expected to return string for keys"
+ end
+
def test_has_and_belongs_to_many_reflection
assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro
assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 4c94c2fd0d..2443f10269 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
end
def relation
- @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table
+ @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table, Post.predicate_builder
end
(Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
@@ -99,7 +99,7 @@ module ActiveRecord
end
test '#reorder!' do
- relation = self.relation.order('foo')
+ @relation = self.relation.order('foo')
assert relation.reorder!('bar').equal?(relation)
assert_equal ['bar'], relation.order_values
@@ -116,7 +116,7 @@ module ActiveRecord
end
test 'reverse_order!' do
- relation = Post.order('title ASC, comments_count DESC')
+ @relation = Post.order('title ASC, comments_count DESC')
relation.reverse_order!
diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb
index 4057835688..0cc081fced 100644
--- a/activerecord/test/cases/relation/predicate_builder_test.rb
+++ b/activerecord/test/cases/relation/predicate_builder_test.rb
@@ -4,11 +4,13 @@ require 'models/topic'
module ActiveRecord
class PredicateBuilderTest < ActiveRecord::TestCase
def test_registering_new_handlers
- PredicateBuilder.register_handler(Regexp, proc do |column, value|
+ Topic.predicate_builder.register_handler(Regexp, proc do |column, value|
Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source))
end)
assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql
+ ensure
+ Topic.reset_column_information
end
end
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index a453203e15..d675953da6 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -181,12 +181,6 @@ module ActiveRecord
assert_equal 0, Post.where(:id => []).count
end
- def test_where_with_table_name_and_nested_empty_array
- assert_deprecated do
- assert_equal [], Post.where(:id => [[]]).to_a
- end
- end
-
def test_where_with_empty_hash_and_no_foreign_key
assert_equal 0, Edge.where(:sink => {}).count
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 3280945d09..f7cb471984 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -23,19 +23,19 @@ module ActiveRecord
end
def test_construction
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal FakeKlass, relation.klass
assert_equal :b, relation.table
assert !relation.loaded, 'relation is not loaded'
end
def test_responds_to_model_and_returns_klass
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal FakeKlass, relation.model
end
def test_initialize_single_values
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
@@ -43,19 +43,19 @@ module ActiveRecord
end
def test_multi_value_initialize
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
Relation::MULTI_VALUE_METHODS.each do |method|
assert_equal [], relation.send("#{method}_values"), method.to_s
end
end
def test_extensions
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal [], relation.extensions
end
def test_empty_where_values_hash
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal({}, relation.where_values_hash)
relation.where! :hello
@@ -63,19 +63,20 @@ module ActiveRecord
end
def test_has_values
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
relation.where! relation.table[:id].eq(10)
assert_equal({:id => 10}, relation.where_values_hash)
end
def test_values_wrong_table
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
def test_tree_is_not_traversed
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
@@ -84,24 +85,25 @@ module ActiveRecord
end
def test_table_name_delegates_to_klass
- relation = Relation.new FakeKlass.new('posts'), :b
+ relation = Relation.new(FakeKlass.new('posts'), :b, Post.predicate_builder)
assert_equal 'posts', relation.table_name
end
def test_scope_for_create
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal({}, relation.scope_for_create)
end
def test_create_with_value
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
hash = { :hello => 'world' }
relation.create_with_value = hash
assert_equal hash, relation.scope_for_create
end
def test_create_with_value_with_wheres
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
@@ -109,9 +111,10 @@ module ActiveRecord
# FIXME: is this really wanted or expected behavior?
def test_scope_for_create_is_cached
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
assert_equal({}, relation.scope_for_create)
+ # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
@@ -126,31 +129,31 @@ module ActiveRecord
end
def test_empty_eager_loading?
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert !relation.eager_loading?
end
def test_eager_load_values
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
relation.eager_load! :b
assert relation.eager_loading?
end
def test_references_values
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal [], relation.references_values
relation = relation.references(:foo).references(:omg, :lol)
assert_equal ['foo', 'omg', 'lol'], relation.references_values
end
def test_references_values_dont_duplicate
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
relation = relation.references(:foo).references(:foo)
assert_equal ['foo'], relation.references_values
end
test 'merging a hash into a relation' do
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
relation = relation.merge where: :lol, readonly: true
assert_equal [:lol], relation.where_values
@@ -158,7 +161,7 @@ module ActiveRecord
end
test 'merging an empty hash into a relation' do
- assert_equal [], Relation.new(FakeKlass, :b).merge({}).where_values
+ assert_equal [], Relation.new(FakeKlass, :b, nil).merge({}).where_values
end
test 'merging a hash with unknown keys raises' do
@@ -166,7 +169,7 @@ module ActiveRecord
end
test '#values returns a dup of the values' do
- relation = Relation.new(FakeKlass, :b).where! :foo
+ relation = Relation.new(FakeKlass, :b, nil).where! :foo
values = relation.values
values[:where] = nil
@@ -174,12 +177,12 @@ module ActiveRecord
end
test 'relations can be created with a values hash' do
- relation = Relation.new(FakeKlass, :b, where: [:foo])
+ 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)
+ relation = Relation.new(FakeKlass, :b, nil)
relation.merge!(where: :foo)
assert_equal [:foo], relation.where_values
end
@@ -192,13 +195,13 @@ module ActiveRecord
end
end
- relation = Relation.new(klass, :b)
+ relation = Relation.new(klass, :b, nil)
relation.merge!(where: ['foo = ?', 'bar'])
assert_equal ['foo = bar'], relation.where_values
end
def test_merging_readonly_false
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
readonly_false_relation = relation.readonly(false)
# test merging in both directions
assert_equal false, relation.merge(readonly_false_relation).readonly_value
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index ca86d58b35..9631ea79be 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -22,6 +22,11 @@ class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
:tags, :taggings, :cars, :minivans
+ class TopicWithCallbacks < ActiveRecord::Base
+ self.table_name = :topics
+ before_update { |topic| topic.author_name = 'David' if topic.author_name.blank? }
+ end
+
def test_do_not_double_quote_string_id
van = Minivan.last
assert van
@@ -249,7 +254,7 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_reorder
topics = Topic.order('author_name').order('title').reorder('id').to_a
- topics_titles = topics.map{ |t| t.title }
+ topics_titles = topics.map(&:title)
assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles
end
@@ -353,7 +358,7 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_calculations_methods
assert_no_queries(ignore_none: false) do
assert_equal 0, Developer.none.count
- assert_equal 0, Developer.none.calculate(:count, nil, {})
+ assert_equal 0, Developer.none.calculate(:count, nil)
assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
@@ -441,7 +446,7 @@ class RelationTest < ActiveRecord::TestCase
where('project_id=1').to_a
assert_equal 3, developers_on_project_one.length
- developer_names = developers_on_project_one.map { |d| d.name }
+ developer_names = developers_on_project_one.map(&:name)
assert developer_names.include?('David')
assert developer_names.include?('Jamis')
end
@@ -652,8 +657,8 @@ class RelationTest < ActiveRecord::TestCase
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
- assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
- assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
+ assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id)
+ assert_equal expected_taggings, author.taggings.uniq.sort_by(&:id)
end
authors = Author.all
@@ -712,7 +717,9 @@ class RelationTest < ActiveRecord::TestCase
def test_find_by_classname
Author.create!(:name => Mary.name)
- assert_equal 1, Author.where(:name => Mary).size
+ assert_deprecated do
+ assert_equal 1, Author.where(:name => Mary).size
+ end
end
def test_find_by_id_with_list_of_ar
@@ -1375,12 +1382,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "id", Post.all.primary_key
end
- def test_disable_implicit_join_references_is_deprecated
- assert_deprecated do
- ActiveRecord::Base.disable_implicit_join_references = true
- end
- end
-
def test_ordering_with_extra_spaces
assert_equal authors(:david), Author.order('id DESC , name DESC').last
end
@@ -1427,6 +1428,19 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:welcome), comments(:greetings).post
end
+ def test_update_on_relation
+ topic1 = TopicWithCallbacks.create! title: 'arel', author_name: nil
+ topic2 = TopicWithCallbacks.create! title: 'activerecord', author_name: nil
+ topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
+ topics.update(title: 'adequaterecord')
+
+ assert_equal 'adequaterecord', topic1.reload.title
+ assert_equal 'adequaterecord', topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal 'David', topic1.reload.author_name
+ assert_equal 'David', topic2.reload.author_name
+ end
+
def test_distinct
tag1 = Tag.create(:name => 'Foo')
tag2 = Tag.create(:name => 'Foo')
@@ -1637,6 +1651,14 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ test "relations with cached arel can't be mutated [internal API]" do
+ relation = Post.all
+ relation.count
+
+ assert_raises(ActiveRecord::ImmutableRelation) { relation.limit!(5) }
+ assert_raises(ActiveRecord::ImmutableRelation) { relation.where!("1 = 2") }
+ end
+
test "relations show the records in #inspect" do
relation = Post.limit(2)
assert_equal "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>", relation.inspect
@@ -1661,7 +1683,9 @@ class RelationTest < ActiveRecord::TestCase
test 'using a custom table affects the wheres' do
table_alias = Post.arel_table.alias('omg_posts')
- relation = ActiveRecord::Relation.new Post, table_alias
+ table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
+ relation = ActiveRecord::Relation.new(Post, table_alias, predicate_builder)
relation.where!(:foo => "bar")
node = relation.arel.constraints.first.grep(Arel::Attributes::Attribute).first
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index f477cf7d92..262e0abc22 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -7,17 +7,6 @@ class SanitizeTest < ActiveRecord::TestCase
def setup
end
- def test_sanitize_sql_hash_handles_associations
- quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
- quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name")
- quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals")
- expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}"
-
- assert_deprecated do
- assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}})
- end
- end
-
def test_sanitize_sql_array_handles_string_interpolation
quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"])
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 6303393c22..b52c66356c 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -40,6 +40,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "schema_migrations"}, output
end
+ def test_schema_dump_uses_force_cascade_on_create_table
+ output = dump_table_schema "authors"
+ assert_match %r{create_table "authors", force: :cascade}, output
+ end
+
def test_schema_dump_excludes_sqlite_sequence
output = standard_dump
assert_no_match %r{create_table "sqlite_sequence"}, output
@@ -162,16 +167,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "schema_migrations"}, output
end
- def test_schema_dump_illegal_ignored_table_value
- stream = StringIO.new
- old_ignore_tables, ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::SchemaDumper.ignore_tables, [5]
- assert_raise(StandardError) do
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- end
- ensure
- ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
- end
-
def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
@@ -237,6 +232,13 @@ 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
@@ -263,77 +265,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{enable_extension}, output
end
end
-
- def test_schema_dump_includes_xml_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_xml_data_type"} =~ output
- assert_match %r{t.xml "data"}, output
- end
- end
-
- def test_schema_dump_includes_inet_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_network_addresses"} =~ output
- assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output
- end
- end
-
- def test_schema_dump_includes_cidr_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_network_addresses"} =~ output
- assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output
- end
- end
-
- def test_schema_dump_includes_macaddr_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_network_addresses"} =~ output
- assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output
- end
- end
-
- def test_schema_dump_includes_uuid_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_uuids"} =~ output
- assert_match %r{t.uuid "guid"}, output
- end
- end
-
- def test_schema_dump_includes_hstores_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_hstores"} =~ output
- assert_match %r[t.hstore "hash_store", default: {}], output
- end
- end
-
- def test_schema_dump_includes_citext_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_citext"} =~ output
- assert_match %r[t.citext "text_citext"], output
- end
- end
-
- def test_schema_dump_includes_ltrees_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_ltrees"} =~ output
- assert_match %r[t.ltree "path"], output
- end
- end
-
- def test_schema_dump_includes_arrays_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_arrays"} =~ output
- assert_match %r[t.text\s+"nicknames",\s+array: true], output
- assert_match %r[t.integer\s+"commission_by_quarter",\s+array: true], output
- end
- end
-
- def test_schema_dump_includes_tsvector_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_tsvectors"} =~ output
- assert_match %r{t.tsvector "text_vector"}, output
- end
- end
end
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
@@ -341,6 +272,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
# Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
if current_adapter?(:OracleAdapter)
assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output
+ elsif current_adapter?(:FbAdapter)
+ assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 18}, output
else
assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 880d0e8293..0738df1b54 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -8,8 +8,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
def test_default_scope
- expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary }
+ expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect(&:salary)
+ received = DeveloperOrderedBySalary.all.collect(&:salary)
assert_equal expected, received
end
@@ -86,14 +86,14 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_scope_overwrites_default
- expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.by_name.to_a.collect { |dev| dev.name }
+ expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect(&:name)
+ received = DeveloperOrderedBySalary.by_name.to_a.collect(&:name)
assert_equal expected, received
end
def test_reorder_overrides_default_scope_order
- expected = Developer.order('name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
+ expected = Developer.order('name DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.reorder('name DESC').collect(&:name)
assert_equal expected, received
end
@@ -143,37 +143,45 @@ class DefaultScopingTest < ActiveRecord::TestCase
expected_5 = Developer.order('salary DESC').collect(&:name)
received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name)
assert_equal expected_5, received_5
+
+ expected_6 = Developer.order('salary DESC').collect(&:name)
+ received_6 = DeveloperOrderedBySalary.where(Developer.arel_table['name'].eq('David')).unscope(where: :name).collect(&:name)
+ assert_equal expected_6, received_6
+
+ expected_7 = Developer.order('salary DESC').collect(&:name)
+ received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq('David')).unscope(where: :name).collect(&:name)
+ assert_equal expected_7, received_7
end
def test_unscope_multiple_where_clauses
- expected = Developer.order('salary DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect { |dev| dev.name }
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect(&:name)
assert_equal expected, received
end
def test_unscope_string_where_clauses_involved
dev_relation = Developer.order('salary DESC').where("created_at > ?", 1.year.ago)
- expected = dev_relation.collect { |dev| dev.name }
+ expected = dev_relation.collect(&:name)
dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago)
- received = dev_ordered_relation.unscope(where: [:name]).collect { |dev| dev.name }
+ received = dev_ordered_relation.unscope(where: [:name]).collect(&:name)
assert_equal expected, received
end
def test_unscope_with_grouping_attributes
- expected = Developer.order('salary DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name }
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name)
assert_equal expected, received
- expected_2 = Developer.order('salary DESC').collect { |dev| dev.name }
- received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect { |dev| dev.name }
+ expected_2 = Developer.order('salary DESC').collect(&:name)
+ received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name)
assert_equal expected_2, received_2
end
def test_unscope_with_limit_in_query
- expected = Developer.order('salary DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect { |dev| dev.name }
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name)
assert_equal expected, received
end
@@ -183,42 +191,42 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_unscope_reverse_order
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.order('salary DESC').reverse_order.unscope(:order).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.order('salary DESC').reverse_order.unscope(:order).collect(&:name)
assert_equal expected, received
end
def test_unscope_select
- expected = Developer.order('salary ASC').collect { |dev| dev.name }
- received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect { |dev| dev.name }
+ expected = Developer.order('salary ASC').collect(&:name)
+ received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect(&:name)
assert_equal expected, received
- expected_2 = Developer.all.collect { |dev| dev.id }
- received_2 = Developer.select(:name).unscope(:select).collect { |dev| dev.id }
+ expected_2 = Developer.all.collect(&:id)
+ received_2 = Developer.select(:name).unscope(:select).collect(&:id)
assert_equal expected_2, received_2
end
def test_unscope_offset
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.offset(5).unscope(:offset).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.offset(5).unscope(:offset).collect(&:name)
assert_equal expected, received
end
def test_unscope_joins_and_select_on_developers_projects
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect(&:name)
assert_equal expected, received
end
def test_unscope_includes
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect(&:name)
assert_equal expected, received
end
def test_unscope_having
- expected = DeveloperOrderedBySalary.all.collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect { |dev| dev.name }
+ expected = DeveloperOrderedBySalary.all.collect(&:name)
+ received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect(&:name)
assert_equal expected, received
end
@@ -281,8 +289,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_order_in_default_scope_should_not_prevail
- expected = Developer.all.merge!(order: 'salary desc').to_a.collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect { |dev| dev.salary }
+ expected = Developer.all.merge!(order: 'salary desc').to_a.collect(&:salary)
+ received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect(&:salary)
assert_equal expected, received
end
diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb
new file mode 100644
index 0000000000..dc12b528dc
--- /dev/null
+++ b/activerecord/test/cases/secure_token_test.rb
@@ -0,0 +1,39 @@
+require 'cases/helper'
+require 'models/user'
+
+class SecureTokenTest < ActiveRecord::TestCase
+ setup do
+ @user = User.new
+ end
+
+ def test_token_values_are_generated_for_specified_attributes_and_persisted_on_save
+ @user.save
+ assert_not_nil @user.token
+ assert_not_nil @user.auth_token
+ end
+
+ def test_regenerating_the_secure_token
+ @user.save
+ old_token = @user.token
+ old_auth_token = @user.auth_token
+ @user.regenerate_token
+ @user.regenerate_auth_token
+
+ assert_not_equal @user.token, old_token
+ assert_not_equal @user.auth_token, old_auth_token
+ end
+
+ def test_raise_after_ten_unsuccessful_attempts_to_generate_a_unique_token
+ User.stubs(:exists?).returns(*Array.new(10, true))
+ assert_raises(RuntimeError) do
+ @user.save
+ end
+ end
+
+ def test_return_unique_token_after_nine_unsuccessful_attempts
+ User.stubs(:exists?).returns(*Array.new(10) { |i| i == 9 ? false : true })
+ @user.save
+ assert_not_nil @user.token
+ assert_not_nil @user.auth_token
+ end
+end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 66f345d805..e29f7462c8 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -22,12 +22,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
end
- def test_list_of_serialized_attributes
- assert_deprecated do
- assert_equal %w(content), Topic.serialized_attributes.keys
- end
- end
-
def test_serialized_attribute
Topic.serialize("content", MyObject)
@@ -243,8 +237,9 @@ class SerializedAttributeTest < ActiveRecord::TestCase
t = Topic.create(content: "first")
assert_equal("first", t.content)
- t.update_column(:content, Topic.type_for_attribute('content').type_cast_for_database("second"))
- assert_equal("second", t.content)
+ t.update_column(:content, ["second"])
+ assert_equal(["second"], t.content)
+ assert_equal(["second"], t.reload.content)
end
def test_serialized_column_should_unserialize_after_update_attribute
@@ -253,5 +248,20 @@ class SerializedAttributeTest < ActiveRecord::TestCase
t.update_attribute(:content, "second")
assert_equal("second", t.content)
+ assert_equal("second", t.reload.content)
+ end
+
+ def test_nil_is_not_changed_when_serialized_with_a_class
+ Topic.serialize(:content, Array)
+
+ topic = Topic.new(content: nil)
+
+ assert_not topic.content_changed?
+ end
+
+ def test_classes_without_no_arg_constructors_are_not_supported
+ assert_raises(ArgumentError) do
+ Topic.serialize(:content, Regexp)
+ end
end
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index eb44c4a83c..5ba17359f0 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -43,7 +43,7 @@ module ActiveRecord
patterns_to_match.each do |pattern|
failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql }
end
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
def assert_queries(num = 1, options = {})
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index b5ac1bdaf9..185fc22e98 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -266,47 +266,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal 2, @first.rollbacks
end
- def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called
- old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveRecord::Base.raise_in_transactional_callbacks = false
-
- def @first.last_after_transaction_error=(e); @last_transaction_error = e; end
- def @first.last_after_transaction_error; @last_transaction_error; end
- @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
- @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
-
- second = TopicWithCallbacks.find(3)
- second.after_commit_block{|r| r.history << :after_commit}
- second.after_rollback_block{|r| r.history << :after_rollback}
-
- Topic.transaction do
- @first.save!
- second.save!
- end
- assert_equal :commit, @first.last_after_transaction_error
- assert_equal [:after_commit], second.history
-
- second.history.clear
- Topic.transaction do
- @first.save!
- second.save!
- raise ActiveRecord::Rollback
- end
- assert_equal :rollback, @first.last_after_transaction_error
- assert_equal [:after_rollback], second.history
- ensure
- ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config
- end
-
- def test_after_commit_should_not_raise_when_raise_in_transactional_callbacks_false
- old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveRecord::Base.raise_in_transactional_callbacks = false
- @first.after_commit_block{ fail "boom" }
- Topic.transaction { @first.save! }
- ensure
- ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config
- end
-
def test_after_commit_callback_should_not_swallow_errors
@first.after_commit_block{ fail "boom" }
assert_raises(RuntimeError) do
@@ -371,10 +330,14 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def test_after_rollback_callbacks_should_validate_on_condition
assert_raise(ArgumentError) { Topic.after_rollback(on: :save) }
+ e = assert_raise(ArgumentError) { Topic.after_rollback(on: 'create') }
+ assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message)
end
def test_after_commit_callbacks_should_validate_on_condition
assert_raise(ArgumentError) { Topic.after_commit(on: :save) }
+ e = assert_raise(ArgumentError) { Topic.after_commit(on: 'create') }
+ assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message)
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 7160e8324d..27a7c9212b 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -13,7 +13,7 @@ class TransactionTest < ActiveRecord::TestCase
fixtures :topics, :developers, :authors, :posts
def setup
- @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
+ @first, @second = Topic.find(1, 2).sort_by(&:id)
end
def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
@@ -194,6 +194,16 @@ class TransactionTest < ActiveRecord::TestCase
assert_equal posts_count, author.posts(true).size
end
+ def test_cancellation_from_returning_false_in_before_filter
+ def @first.before_save_for_transaction
+ false
+ end
+
+ assert_deprecated do
+ @first.save
+ end
+ end
+
def test_cancellation_from_before_destroy_rollbacks_in_destroy
add_cancelling_before_destroy_with_db_side_effect_to_topic @first
nbooks_before_destroy = Book.count
@@ -493,35 +503,32 @@ class TransactionTest < ActiveRecord::TestCase
assert topic.frozen?, 'not frozen'
end
- # The behavior of killed threads having a status of "aborting" was changed
- # in Ruby 2.0, so Thread#kill on 1.9 will prematurely commit the transaction
- # and there's nothing we can do about it.
- unless RUBY_VERSION.start_with? '1.9'
- def test_rollback_when_thread_killed
- queue = Queue.new
- thread = Thread.new do
- Topic.transaction do
- @first.approved = true
- @second.approved = false
- @first.save
+ def test_rollback_when_thread_killed
+ return if in_memory_db?
- queue.push nil
- sleep
+ queue = Queue.new
+ thread = Thread.new do
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
- @second.save
- end
+ queue.push nil
+ sleep
+
+ @second.save
end
+ end
- queue.pop
- thread.kill
- thread.join
+ queue.pop
+ thread.kill
+ thread.join
- assert @first.approved?, "First should still be changed in the objects"
- assert !@second.approved?, "Second should still be changed in the objects"
+ assert @first.approved?, "First should still be changed in the objects"
+ assert !@second.approved?, "Second should still be changed in the objects"
- assert !Topic.find(1).approved?, "First shouldn't have been approved"
- assert Topic.find(2).approved?, "Second should still be approved"
- end
+ assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert Topic.find(2).approved?, "Second should still be approved"
end
def test_restore_active_record_state_for_all_records_in_a_transaction
@@ -562,6 +569,21 @@ class TransactionTest < ActiveRecord::TestCase
assert !@second.destroyed?, 'not destroyed'
end
+ def test_restore_frozen_state_after_double_destroy
+ topic = Topic.create
+ reply = topic.replies.create
+
+ Topic.transaction do
+ topic.destroy # calls #destroy on reply (since dependent: destroy)
+ reply.destroy
+
+ raise ActiveRecord::Rollback
+ end
+
+ assert_not reply.frozen?
+ assert_not topic.frozen?
+ end
+
def test_sqlite_add_column_in_transaction
return true unless current_adapter?(:SQLite3Adapter)
@@ -635,7 +657,7 @@ class TransactionTest < ActiveRecord::TestCase
meta = class << topic; self; end
meta.send("define_method", "before_#{filter}_for_transaction") do
Book.create
- false
+ throw(:abort)
end
end
end
@@ -699,7 +721,7 @@ if current_adapter?(:PostgreSQLAdapter)
end
end
- threads.each { |t| t.join }
+ threads.each(&:join)
end
end
@@ -747,7 +769,7 @@ if current_adapter?(:PostgreSQLAdapter)
Developer.connection.close
end
- threads.each { |t| t.join }
+ threads.each(&:join)
end
assert_equal original_salary, Developer.find(1).salary
diff --git a/activerecord/test/cases/type/decimal_test.rb b/activerecord/test/cases/type/decimal_test.rb
index da30de373e..34ed1d7b19 100644
--- a/activerecord/test/cases/type/decimal_test.rb
+++ b/activerecord/test/cases/type/decimal_test.rb
@@ -15,6 +15,11 @@ module ActiveRecord
assert_equal BigDecimal.new("123.0"), type.type_cast_from_user(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)
+ 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))
@@ -33,6 +38,14 @@ module ActiveRecord
type = Decimal.new
assert_equal BigDecimal("1"), type.type_cast_from_user(value)
end
+
+ def test_changed?
+ type = Decimal.new
+
+ assert type.changed?(5.0, 5.0, '5.0wibble')
+ assert_not type.changed?(5.0, 5.0, '5.0')
+ assert_not type.changed?(-5.0, -5.0, '-5.0')
+ end
end
end
end
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 53d6a3a6aa..ff956b7680 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -41,12 +41,20 @@ module ActiveRecord
assert_nil type.type_cast_from_user(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)
+ end
+
test "changed?" do
type = Type::Integer.new
assert type.changed?(5, 5, '5wibble')
assert_not type.changed?(5, 5, '5')
assert_not type.changed?(5, 5, '5.0')
+ assert_not type.changed?(-5, -5, '-5')
+ assert_not type.changed?(-5, -5, '-5.0')
assert_not type.changed?(nil, nil, nil)
end
@@ -68,7 +76,7 @@ module ActiveRecord
end
end
- test "very large numbers are in range" do
+ test "very large numbers are out of range" do
assert_raises(::RangeError) do
Integer.new.type_cast_from_user("9999999999999999999999999999999")
end
diff --git a/activerecord/test/cases/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb
index 4e32f92dd0..172c6dfc4c 100644
--- a/activerecord/test/cases/type/type_map_test.rb
+++ b/activerecord/test/cases/type/type_map_test.rb
@@ -124,6 +124,53 @@ module ActiveRecord
assert_equal mapping.lookup(3), 'string'
assert_kind_of Type::Value, mapping.lookup(4)
end
+
+ def test_fetch
+ mapping = TypeMap.new
+ mapping.register_type(1, "string")
+
+ assert_equal "string", mapping.fetch(1) { "int" }
+ assert_equal "int", mapping.fetch(2) { "int" }
+ end
+
+ def test_fetch_yields_args
+ mapping = TypeMap.new
+
+ assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") }
+ assert_equal "bar-1-2-3", mapping.fetch("bar", 1, 2, 3) { |*args| args.join("-") }
+ end
+
+ def test_fetch_memoizes
+ mapping = TypeMap.new
+
+ looked_up = false
+ mapping.register_type(1) do
+ fail if looked_up
+ looked_up = true
+ "string"
+ end
+
+ assert_equal "string", mapping.fetch(1)
+ assert_equal "string", mapping.fetch(1)
+ end
+
+ def test_fetch_memoizes_on_args
+ mapping = TypeMap.new
+ mapping.register_type("foo") { |*args| args.join("-") }
+
+ assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") }
+ assert_equal "foo-2-3-4", mapping.fetch("foo", 2, 3, 4) { |*args| args.join("-") }
+ end
+
+ def test_register_clears_cache
+ mapping = TypeMap.new
+
+ mapping.register_type(1, "string")
+ mapping.lookup(1)
+ mapping.register_type(1, "int")
+
+ assert_equal "int", mapping.lookup(1)
+ end
end
end
end
diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb
new file mode 100644
index 0000000000..b6f673109e
--- /dev/null
+++ b/activerecord/test/cases/type/unsigned_integer_test.rb
@@ -0,0 +1,17 @@
+require "cases/helper"
+
+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"))
+ end
+
+ test "minus value is out of range" do
+ assert_raises(::RangeError) do
+ UnsignedInteger.new.type_cast_from_user("-1")
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index b0979cbe1f..73e92addfe 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -17,6 +17,10 @@ module ActiveRecord
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')
# explicitly check for false vs nil
assert_equal false, type.type_cast_from_user(false)
@@ -28,12 +32,6 @@ module ActiveRecord
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_deprecated do
- assert_equal false, type.type_cast_from_user(' ')
- assert_equal false, type.type_cast_from_user("\u3000\r\n")
- assert_equal false, type.type_cast_from_user("\u0000")
- assert_equal false, type.type_cast_from_user('SOMETHING RANDOM')
- end
end
def test_type_cast_float
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 4a92da38ce..2c0e282761 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require 'models/owner'
require 'models/pet'
+require 'models/person'
class LengthValidationTest < ActiveRecord::TestCase
fixtures :owners
@@ -44,4 +45,21 @@ class LengthValidationTest < ActiveRecord::TestCase
assert o.valid?
end
end
+
+ def test_validates_size_of_reprects_records_marked_for_destruction
+ assert_nothing_raised { Owner.validates_size_of :pets, minimum: 1 }
+ owner = Owner.new
+ assert_not owner.save
+ assert owner.errors[:pets].any?
+ pet = owner.pets.build
+ assert owner.valid?
+ assert owner.save
+
+ pet_count = Pet.count
+ assert_not owner.update_attributes pets_attributes: [ {_destroy: 1, id: pet.id} ]
+ assert_not owner.valid?
+ assert owner.errors[:pets].any?
+ assert_equal pet_count, Pet.count
+ end
+
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index c6b58d469d..524f59876e 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -30,11 +30,6 @@ class ReplyWithTitleObject < Reply
def title; ReplyTitle.new; end
end
-class Employee < ActiveRecord::Base
- self.table_name = 'postgresql_arrays'
- validates_uniqueness_of :nicknames
-end
-
class TopicWithUniqEvent < Topic
belongs_to :event, foreign_key: :parent_id
validates :event, uniqueness: true
@@ -378,18 +373,6 @@ class UniquenessValidationTest < ActiveRecord::TestCase
}
end
- if current_adapter? :PostgreSQLAdapter
- def test_validate_uniqueness_with_array_column
- e1 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [1000, 1200])
- assert e1.persisted?, "Saving e1"
-
- e2 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [2200])
- assert !e2.persisted?, "e2 shouldn't be valid"
- assert e2.errors[:nicknames].any?, "Should have errors for nicknames"
- assert_equal ["has already been taken"], e2.errors[:nicknames], "Should have uniqueness message for nicknames"
- end
- end
-
def test_validate_uniqueness_on_existing_relation
event = Event.create
assert TopicWithUniqEvent.create(event: event).valid?
diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb
index 2bbf0f23b3..b30666d876 100644
--- a/activerecord/test/cases/validations_repair_helper.rb
+++ b/activerecord/test/cases/validations_repair_helper.rb
@@ -5,9 +5,7 @@ module ActiveRecord
module ClassMethods
def repair_validations(*model_classes)
teardown do
- model_classes.each do |k|
- k.clear_validators!
- end
+ model_classes.each(&:clear_validators!)
end
end
end
@@ -15,9 +13,7 @@ module ActiveRecord
def repair_validations(*model_classes)
yield if block_given?
ensure
- model_classes.each do |k|
- k.clear_validators!
- end
+ model_classes.each(&:clear_validators!)
end
end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index db8159eff8..959c58aa85 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -149,4 +149,17 @@ class ValidationsTest < ActiveRecord::TestCase
assert_equal 1, Company.validators_on(:name).size
end
+ def test_numericality_validation_with_mutation
+ Topic.class_eval do
+ attribute :wibble, ActiveRecord::Type::String.new
+ validates_numericality_of :wibble, only_integer: true
+ end
+
+ topic = Topic.new(wibble: '123-4567')
+ topic.wibble.gsub!('-', '')
+
+ assert topic.valid?
+ ensure
+ Topic.reset_column_information
+ end
end
diff --git a/activerecord/test/fixtures/bulbs.yml b/activerecord/test/fixtures/bulbs.yml
new file mode 100644
index 0000000000..e5ce2b796c
--- /dev/null
+++ b/activerecord/test/fixtures/bulbs.yml
@@ -0,0 +1,5 @@
+defaulty:
+ name: defaulty
+
+special:
+ name: special
diff --git a/activerecord/test/fixtures/naked/csv/accounts.csv b/activerecord/test/fixtures/naked/csv/accounts.csv
deleted file mode 100644
index 8b13789179..0000000000
--- a/activerecord/test/fixtures/naked/csv/accounts.csv
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 3da3a1fd59..8c1f14bd36 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -50,9 +50,9 @@ class Author < ActiveRecord::Base
has_many :sti_posts, :class_name => 'StiPost'
has_many :sti_post_comments, :through => :sti_posts, :source => :comments
- has_many :special_nonexistant_posts, -> { where("posts.body = 'nonexistant'") }, :class_name => "SpecialPost"
- has_many :special_nonexistant_post_comments, -> { where('comments.post_id' => 0) }, :through => :special_nonexistant_posts, :source => :comments
- has_many :nonexistant_comments, :through => :posts
+ has_many :special_nonexistent_posts, -> { where("posts.body = 'nonexistent'") }, :class_name => "SpecialPost"
+ has_many :special_nonexistent_post_comments, -> { where('comments.post_id' => 0) }, :through => :special_nonexistent_posts, :source => :comments
+ has_many :nonexistent_comments, :through => :posts
has_many :hello_posts, -> { where "posts.body = 'hello'" }, :class_name => "Post"
has_many :hello_post_comments, :through => :hello_posts, :source => :comments
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb
index dff099c1fb..2a51d903b8 100644
--- a/activerecord/test/models/bird.rb
+++ b/activerecord/test/models/bird.rb
@@ -7,6 +7,6 @@ class Bird < ActiveRecord::Base
attr_accessor :cancel_save_from_callback
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 831a0d5387..a6e83fe353 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -46,6 +46,6 @@ end
class FailedBulb < Bulb
before_destroy do
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 42f7fb4680..5a56616eb9 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -72,6 +72,7 @@ class Firm < Company
# Oracle tests were failing because of that as the second fixture was selected
has_one :account_using_primary_key, -> { order('id') }, :primary_key => "firm_id", :class_name => "Account"
has_one :account_using_foreign_and_primary_keys, :foreign_key => "firm_name", :primary_key => "name", :class_name => "Account"
+ has_one :account_with_inexistent_foreign_key, class_name: 'Account', foreign_key: "inexistent"
has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete
has_one :account_limit_500_with_hash_conditions, -> { where :credit_limit => 500 }, :foreign_key => "firm_id", :class_name => "Account"
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 7e8e82542f..afe4b3d707 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -2,7 +2,7 @@ class Customer < ActiveRecord::Base
cattr_accessor :gps_conversion_was_run
composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true
- composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
+ composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new(&:to_money)
composed_of :gps_location, :allow_nil => true
composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location),
:converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)}
diff --git a/activerecord/test/models/image.rb b/activerecord/test/models/image.rb
new file mode 100644
index 0000000000..7ae8e4a7f6
--- /dev/null
+++ b/activerecord/test/models/image.rb
@@ -0,0 +1,3 @@
+class Image < ActiveRecord::Base
+ belongs_to :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class
+end
diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb
index 72e7bade68..f3e92f3067 100644
--- a/activerecord/test/models/organization.rb
+++ b/activerecord/test/models/organization.rb
@@ -8,5 +8,7 @@ class Organization < ActiveRecord::Base
has_one :author, :primary_key => :name
has_one :author_owned_essay_category, :through => :author, :source => :owned_essay_category
+ has_many :posts, :through => :author, :source => :posts
+
scope :clubs, -> { from('clubs') }
end
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index 2e3a9a3681..cedb774b10 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -17,6 +17,8 @@ class Owner < ActiveRecord::Base
after_commit :execute_blocks
+ accepts_nested_attributes_for :pets, allow_destroy: true
+
def blocks
@blocks ||= []
end
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index 8c83de573f..b26035d944 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -11,7 +11,7 @@ class Parrot < ActiveRecord::Base
attr_accessor :cancel_save_from_callback
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 90a3c3ecee..366c70f902 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -36,8 +36,8 @@ class Pirate < ActiveRecord::Base
has_one :foo_bulb, -> { where :name => 'foo' }, :foreign_key => :car_id, :class_name => "Bulb"
- accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
- accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc(&:empty?)
+ accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
accepts_nested_attributes_for :update_only_ship, :update_only => true
accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks,
:birds_with_method_callbacks, :birds_with_proc_callbacks, :allow_destroy => true
@@ -56,7 +56,7 @@ class Pirate < ActiveRecord::Base
attr_accessor :cancel_save_from_callback, :parrots_limit
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
private
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 36cf221d45..7b637c9e3f 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -72,14 +72,11 @@ class Post < ActiveRecord::Base
through: :author_with_address,
source: :author_address_extra
- has_many :comments_with_interpolated_conditions,
- ->(p) { where "#{"#{p.aliased_table_name}." rescue ""}body = ?", 'Thank you for the welcome' },
- :class_name => 'Comment'
-
has_one :very_special_comment
has_one :very_special_comment_with_post, -> { includes(:post) }, :class_name => "VerySpecialComment"
+ has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order('posts.id') }, class_name: "VerySpecialComment"
has_many :special_comments
- has_many :nonexistant_comments, -> { where 'comments.id < 0' }, :class_name => 'Comment'
+ has_many :nonexistent_comments, -> { where 'comments.id < 0' }, :class_name => 'Comment'
has_many :special_comments_ratings, :through => :special_comments, :source => :ratings
has_many :special_comments_ratings_taggings, :through => :special_comments_ratings, :source => :taggings
@@ -127,6 +124,9 @@ class Post < ActiveRecord::Base
has_many :taggings_using_author_id, :primary_key => :author_id, :as => :taggable, :class_name => 'Tagging'
has_many :tags_using_author_id, :through => :taggings_using_author_id, :source => :tag
+ has_many :images, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class
+ has_one :main_image, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class, :class_name => 'Image'
+
has_many :standard_categorizations, :class_name => 'Categorization', :foreign_key => :post_id
has_many :author_using_custom_pk, :through => :standard_categorizations
has_many :authors_using_custom_pk, :through => :standard_categorizations
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 77a4728d0b..c2f6d492d8 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -6,7 +6,7 @@ class Ship < ActiveRecord::Base
has_many :parts, :class_name => 'ShipPart'
accepts_nested_attributes_for :parts, :allow_destroy => true
- accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
accepts_nested_attributes_for :update_only_pirate, :update_only => true
validates_presence_of :name
@@ -14,7 +14,7 @@ class Ship < ActiveRecord::Base
attr_accessor :cancel_save_from_callback
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/tyre.rb b/activerecord/test/models/tyre.rb
index bc3444aa7d..e50a21ca68 100644
--- a/activerecord/test/models/tyre.rb
+++ b/activerecord/test/models/tyre.rb
@@ -1,3 +1,11 @@
class Tyre < ActiveRecord::Base
belongs_to :car
+
+ def self.custom_find(id)
+ find(id)
+ end
+
+ def self.custom_find_by(*args)
+ find_by(*args)
+ end
end
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
new file mode 100644
index 0000000000..23cd2e0e1c
--- /dev/null
+++ b/activerecord/test/models/user.rb
@@ -0,0 +1,4 @@
+class User < ActiveRecord::Base
+ has_secure_token
+ has_secure_token :auth_token
+end
diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb
index a7817772f4..264d9b8910 100644
--- a/activerecord/test/schema/oracle_specific_schema.rb
+++ b/activerecord/test/schema/oracle_specific_schema.rb
@@ -32,10 +32,7 @@ create sequence test_oracle_defaults_seq minvalue 10000
fixed_time date default TO_DATE('2004-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'),
char1 varchar2(1) default 'Y',
char2 varchar2(50) default 'a varchar field',
- char3 clob default 'a text field',
- positive_integer integer default 1,
- negative_integer integer default -1,
- decimal_number number(3,2) default 2.78
+ char3 clob default 'a text field'
)
SQL
execute "create sequence defaults_seq minvalue 10000"
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 7c3b170c08..55360b9aa2 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,9 +1,7 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times
- postgresql_network_addresses postgresql_uuids postgresql_ltrees postgresql_oids postgresql_xml_data_type defaults
- geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent
- postgresql_citext).each do |table_name|
+ %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}"
end
@@ -14,8 +12,6 @@ ActiveRecord::Schema.define do
execute 'DROP FUNCTION IF EXISTS partitioned_insert_trigger()'
- execute "DROP SCHEMA IF EXISTS schema_1 CASCADE"
-
%w(accounts_id_seq developers_id_seq projects_id_seq topics_id_seq customers_id_seq orders_id_seq).each do |seq_name|
execute "SELECT setval('#{seq_name}', 100)"
end
@@ -32,92 +28,13 @@ ActiveRecord::Schema.define do
char1 char(1) default 'Y',
char2 character varying(50) default 'a varchar field',
char3 text default 'a text field',
- positive_integer integer default 1,
- negative_integer integer default -1,
bigint_default bigint default 0::bigint,
- decimal_number decimal(3,2) default 2.78,
multiline_default text DEFAULT '--- []
'::text
);
_SQL
- execute "CREATE SCHEMA schema_1"
- execute "CREATE DOMAIN schema_1.text AS text"
- execute "CREATE DOMAIN schema_1.varchar AS varchar"
- execute "CREATE DOMAIN schema_1.bpchar AS bpchar"
-
- execute <<_SQL
- CREATE TABLE geometrics (
- id serial primary key,
- a_point point,
- -- a_line line, (the line type is currently not implemented in postgresql)
- a_line_segment lseg,
- a_box box,
- a_path path,
- a_polygon polygon,
- a_circle circle
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_arrays (
- id SERIAL PRIMARY KEY,
- commission_by_quarter INTEGER[],
- nicknames TEXT[]
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_uuids (
- id SERIAL PRIMARY KEY,
- guid uuid,
- compact_guid uuid
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_tsvectors (
- id SERIAL PRIMARY KEY,
- text_vector tsvector
- );
-_SQL
-
- if 't' == select_value("select 'hstore'=ANY(select typname from pg_type)")
- execute <<_SQL
- CREATE TABLE postgresql_hstores (
- id SERIAL PRIMARY KEY,
- hash_store hstore default ''::hstore
- );
-_SQL
- end
-
- if 't' == select_value("select 'ltree'=ANY(select typname from pg_type)")
- execute <<_SQL
- CREATE TABLE postgresql_ltrees (
- id SERIAL PRIMARY KEY,
- path ltree
- );
-_SQL
- end
-
- if 't' == select_value("select 'citext'=ANY(select typname from pg_type)")
- execute <<_SQL
- CREATE TABLE postgresql_citext (
- id SERIAL PRIMARY KEY,
- text_citext citext default ''::citext
- );
-_SQL
- end
-
- execute <<_SQL
- CREATE TABLE postgresql_numbers (
- id SERIAL PRIMARY KEY,
- single REAL,
- double DOUBLE PRECISION
- );
-_SQL
-
execute <<_SQL
CREATE TABLE postgresql_times (
id SERIAL PRIMARY KEY,
@@ -127,15 +44,6 @@ _SQL
_SQL
execute <<_SQL
- CREATE TABLE postgresql_network_addresses (
- id SERIAL PRIMARY KEY,
- cidr_address CIDR default '192.168.1.0/24',
- inet_address INET default '192.168.1.1',
- mac_address MACADDR default 'ff:ff:ff:ff:ff:ff'
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_oids (
id SERIAL PRIMARY KEY,
obj_id OID
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 720a127585..21b23d8e0c 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -479,6 +479,8 @@ ActiveRecord::Schema.define do
# Oracle/SQLServer supports precision up to 38
if current_adapter?(:OracleAdapter, :SQLServerAdapter)
t.decimal :atoms_in_universe, precision: 38, scale: 0
+ elsif current_adapter?(:FbAdapter)
+ t.decimal :atoms_in_universe, precision: 18, scale: 0
else
t.decimal :atoms_in_universe, precision: 55, scale: 0
end
@@ -595,6 +597,11 @@ ActiveRecord::Schema.define do
t.string :title, null: false
end
+ create_table :images, force: true do |t|
+ t.integer :imageable_identifier
+ t.string :imageable_class
+ end
+
create_table :price_estimates, force: true do |t|
t.string :estimate_of_type
t.integer :estimate_of_id
@@ -721,7 +728,7 @@ ActiveRecord::Schema.define do
t.string :author_name
t.string :author_email_address
if mysql_56?
- t.datetime :written_on, limit: 6
+ t.datetime :written_on, precision: 6
else
t.datetime :written_on
end
@@ -887,6 +894,11 @@ ActiveRecord::Schema.define do
t.string :overloaded_string_with_limit, limit: 255
t.string :string_with_default, default: 'the original default'
end
+
+ create_table :users, force: true do |t|
+ t.string :token
+ t.string :auth_token
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index ed30c7de70..aa2090b897 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,388 +1,111 @@
-* The decorated `load` and `require` methods are now kept private.
-
- Fixes #17553.
-
- *Xavier Noria*
-
-* `String#remove` and `String#remove!` accept multiple arguments.
-
- *Pavel Pravosud*
-
-* `TimeWithZone#strftime` now delegates every directive to `Time#strftime` except for '%Z',
- it also now correctly handles escaped '%' characters placed just before time zone related directives.
-
- *Pablo Herrero*
-
-* Corrected `Inflector#underscore` handling of multiple successive acroynms.
-
- *James Le Cuirot*
-
-* Delegation now works with ruby reserved words passed to `:to` option.
-
- Fixes #16956.
-
- *Agis Anastasopoulos*
-
-* Added method `#eql?` to `ActiveSupport::Duration`, in addition to `#==`.
-
- Currently, the following returns `false`, contrary to expectation:
-
- 1.minute.eql?(1.minute)
-
- Adding method `#eql?` will make this behave like expected. Method `#eql?` is
- just a bit stricter than `#==`, as it checks whether the argument is also a duration. Their
- parts may be different though.
-
- 1.minute.eql?(60.seconds) # => true
- 1.minute.eql?(60) # => false
-
- *Joost Lubach*
-
-* `Time#change` can now change nanoseconds (`:nsec`) as a higher-precision
- alternative to microseconds (`:usec`).
-
- *Agis Anastasooulos*
-
-* `MessageVerifier.new` raises an appropriate exception if the secret is `nil`.
- This prevents `MessageVerifier#generate` from raising a cryptic error later on.
-
- *Kostiantyn Kahanskyi*
-
-* Introduced new configuration option `active_support.test_order` for
- specifying the order in which test cases are executed. This option currently defaults
- to `:sorted` but will be changed to `:random` in Rails 5.0.
-
- *Akira Matsuda*, *Godfrey Chan*
-
-* Fixed a bug in `Inflector#underscore` where acroynms in nested constant names
- are incorrectly parsed as camelCase.
-
- Fixes #8015.
-
- *Fred Wu*, *Matthew Draper*
-
-* Make `Time#change` throw an exception if the `:usec` option is out of range and
- the time has an offset other than UTC or local.
-
- *Agis Anastasopoulos*
-
-* `Method` objects now report themselves as not `duplicable?`. This allows
- hashes and arrays containing `Method` objects to be `deep_dup`ed.
-
- *Peter Jaros*
-
-* `determine_constant_from_test_name` does no longer shadow `NameError`s
- which happens during constant autoloading.
-
- Fixes #9933.
-
- *Guo Xiang Tan*
-
-* Added instance_eval version to Object#try and Object#try!, so you can do this:
-
- person.try { name.first }
-
- instead of:
-
- person.try { |person| person.name.first }
-
- *DHH*, *Ari Pollak*
-
-* Fix the `ActiveSupport::Duration#instance_of?` method to return the right
- value with the class itself since it was previously delegated to the
- internal value.
-
- *Robin Dupret*
-
-* Fix rounding errors with `#travel_to` by resetting the usec on any passed time to zero, so we only travel
- with per-second precision, not anything deeper than that.
-
- *DHH*
-
-* Fix DateTime comparison with `DateTime::Infinity` object.
+* Change the default test order from `:sorted` to `:random`.
*Rafael Mendonça França*
-* Added Object#itself which returns the object itself. Useful when dealing with a chaining scenario, like Active Record scopes:
-
- Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
-
- *DHH*
-
-* `Object#with_options` executes block in merging option context when
- explicit receiver in not passed.
-
- *Pavel Pravosud*
-
-* Fixed a compatibility issue with the `Oj` gem when cherry-picking the file
- `active_support/core_ext/object/json` without requiring `active_support/json`.
-
- Fixes #16131.
-
- *Godfrey Chan*
-
-* Make `Hash#with_indifferent_access` copy the default proc too.
-
- *arthurnn*, *Xanders*
-
-* Add `String#truncate_words` to truncate a string by a number of words.
-
- *Mohamed Osama*
-
-* Deprecate `capture` and `quietly`.
-
- These methods are not thread safe and may cause issues when used in threaded environments.
- To avoid problems we are deprecating them.
-
- *Tom Meier*
-
-* `DateTime#to_f` now preserves the fractional seconds instead of always
- rounding to `.0`.
-
- Fixes #15994.
-
- *John Paul Ashenfelter*
-
-* Add `Hash#transform_values` to simplify a common pattern where the values of a
- hash must change, but the keys are left the same.
-
- *Sean Griffin*
-
-* Always instrument `ActiveSupport::Cache`.
-
- Since `ActiveSupport::Notifications` only instruments items when there
- are attached subscribers, we don't need to disable instrumentation.
-
- *Peter Wagenet*
-
-* Make the `apply_inflections` method case-insensitive when checking
- whether a word is uncountable or not.
-
- *Robin Dupret*
+* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`.
-* Make Dependencies pass a name to NameError error.
-
- *arthurnn*
-
-* Fixed `ActiveSupport::Cache::FileStore` exploding with long paths.
-
- *Adam Panzer*, *Michael Grosser*
-
-* Fixed `ActiveSupport::TimeWithZone#-` so precision is not unnecessarily lost
- when working with objects with a nanosecond component.
-
- `ActiveSupport::TimeWithZone#-` should return the same result as if we were
- using `Time#-`:
-
- Time.now.end_of_day - Time.now.beginning_of_day # => 86399.999999999
-
- Before:
-
- Time.zone.now.end_of_day.nsec # => 999999999
- Time.zone.now.end_of_day - Time.zone.now.beginning_of_day # => 86400.0
-
- After:
-
- Time.zone.now.end_of_day - Time.zone.now.beginning_of_day
- # => 86399.999999999
-
- *Gordon Chan*
-
-* Fixed precision error in NumberHelper when using Rationals.
-
- Before:
-
- ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
- # => "330.00"
-
- After:
-
- ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
- # => "333.33"
-
- See #15379.
-
- *Juanjo Bazán*
-
-* Removed deprecated `Numeric#ago` and friends
-
- Replacements:
-
- 5.ago => 5.seconds.ago
- 5.until => 5.seconds.until
- 5.since => 5.seconds.since
- 5.from_now => 5.seconds.from_now
-
- See #12389 for the history and rationale behind this.
-
- *Godfrey Chan*
-
-* DateTime `advance` now supports partial days.
-
- Before:
-
- DateTime.now.advance(days: 1, hours: 12)
-
- After:
-
- DateTime.now.advance(days: 1.5)
-
- Fixes #12005.
-
- *Shay Davidson*
-
-* `Hash#deep_transform_keys` and `Hash#deep_transform_keys!` now transform hashes
- in nested arrays. This change also applies to `Hash#deep_stringify_keys`,
- `Hash#deep_stringify_keys!`, `Hash#deep_symbolize_keys` and
- `Hash#deep_symbolize_keys!`.
-
- *OZAWA Sakuro*
-
-* Fixed confusing `DelegationError` in `Module#delegate`.
-
- See #15186.
-
- *Vladimir Yarotsky*
-
-* Fixed `ActiveSupport::Subscriber` so that no duplicate subscriber is created
- when a subscriber method is redefined.
-
- *Dennis Schön*
-
-* Remove deprecated string based terminators for `ActiveSupport::Callbacks`.
-
- *Eileen M. Uchitelle*
-
-* Fixed an issue when using
- `ActiveSupport::NumberHelper::NumberToDelimitedConverter` to
- convert a value that is an `ActiveSupport::SafeBuffer` introduced
- in 2da9d67.
-
- See #15064.
-
- *Mark J. Titorenko*
-
-* `TimeZone#parse` defaults the day of the month to '1' if any other date
- components are specified. This is more consistent with the behavior of
- `Time#parse`.
-
- *Ulysse Carion*
-
-* `humanize` strips leading underscores, if any.
-
- Before:
-
- '_id'.humanize # => ""
-
- After:
-
- '_id'.humanize # => "Id"
-
- *Xavier Noria*
-
-* Fixed backward compatibility issues introduced in 326e652.
-
- Empty Hash or Array should not be present in serialization result.
-
- {a: []}.to_query # => ""
- {a: {}}.to_query # => ""
-
- For more info see #14948.
+ *Rafael Mendonça França*
- *Bogdan Gusiev*
+* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=`
+ and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`.
-* Add `Digest::UUID::uuid_v3` and `Digest::UUID::uuid_v5` to support stable
- UUID fixtures on PostgreSQL.
+ *Rafael Mendonça França*
- *Roderick van Domburg*
+* Remove deprecated `ActiveSupport::SafeBuffer#prepend`.
-* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is
- true.
+ *Rafael Mendonça França*
- This fixes the current situation of:
+* Remove deprecated methods at `Kernel`.
- 1.second.eql?(1.second) # => false
+ `silence_stderr`, `silence_stream`, `capture` and `quietly`.
- `eql?` also requires that the other object is an `ActiveSupport::Duration`.
- This requirement makes `ActiveSupport::Duration`'s behavior consistent with
- the behavior of Ruby's numeric types:
+ *Rafael Mendonça França*
- 1.eql?(1.0) # => false
- 1.0.eql?(1) # => false
+* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions`
+ file.
- 1.second.eql?(1) # => false (was true)
- 1.eql?(1.second) # => false
+ *Rafael Mendonça França*
- { 1 => "foo", 1.0 => "bar" }
- # => { 1 => "foo", 1.0 => "bar" }
+* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and
+ `ActiveSupport::Cache::Store.instrument=`.
- { 1 => "foo", 1.second => "bar" }
- # now => { 1 => "foo", 1.second => "bar" }
- # was => { 1 => "bar" }
+ *Rafael Mendonça França*
- And though the behavior of these hasn't changed, for reference:
+* Change the way in which callback chains can be halted.
- 1 == 1.0 # => true
- 1.0 == 1 # => true
+ The preferred method to halt a callback chain from now on is to explicitly
+ `throw(:abort)`.
+ In the past, returning `false` in an ActiveSupport callback had the side
+ effect of halting the callback chain. This is not recommended anymore and,
+ depending on the value of
+ `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
+ either not work at all or display a deprecation warning.
- 1 == 1.second # => true
- 1.second == 1 # => true
+* Add Callbacks::CallbackChain.halt_and_display_warning_on_return_false
- *Emily Dobervich*
+ Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
+ to true will let an app support the deprecated way of halting callback
+ chains by returning `false`.
-* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies
- instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is
- deprecated.
+ Setting the value to false will tell the app to ignore any `false` value
+ returned by callbacks, and only halt the chain upon `throw(:abort)`.
- *Pavel Pravosud*
+ The value can also be set with the Rails configuration option
+ `config.active_support.halt_callback_chains_on_return_false`.
-* `HashWithIndifferentAccess` better respects `#to_hash` on objects it
- receives. In particular, `.new`, `#update`, `#merge`, and `#replace` accept
- objects which respond to `#to_hash`, even if those objects are not hashes
- directly.
+ When the configuration option is missing, its value is `true`, so older apps
+ ported to Rails 5.0 will not break (but display a deprecation warning).
+ For new Rails 5.0 apps, its value is set to `false` in an initializer, so
+ these apps will support the new behavior by default.
- *Peter Jaros*
+ *claudiob*
-* Deprecate `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead.
+* Changes arguments and default value of CallbackChain's :terminator option
- *Akshay Vishnoi*
+ Chains of callbacks defined without an explicit `:terminator` option will
+ now be halted as soon as a `before_` callback throws `:abort`.
-* Ensure classes which `include Enumerable` get `#to_json` in addition to
- `#as_json`.
+ Chains of callbacks defined with a `:terminator` option will maintain their
+ existing behavior of halting as soon as a `before_` callback matches the
+ terminator's expectation.
- *Sammy Larbi*
+ *claudiob*
-* Change the signature of `fetch_multi` to return a hash rather than an
- array. This makes it consistent with the output of `read_multi`.
+* Deprecate `MissingSourceFile` in favor of `LoadError`.
- *Parker Selbert*
+ `MissingSourceFile` was just an alias to `LoadError` and was not being
+ raised inside the framework.
-* Introduce `Concern#class_methods` as a sleek alternative to clunky
- `module ClassMethods`. Add `Kernel#concern` to define at the toplevel
- without chunky `module Foo; extend ActiveSupport::Concern` boilerplate.
+ *Rafael Mendonça França*
- # app/models/concerns/authentication.rb
- concern :Authentication do
- included do
- after_create :generate_private_key
- end
+* Add support for error dispatcher classes in `ActiveSupport::Rescuable`.
+ Now it acts closer to Ruby's rescue.
- class_methods do
- def authenticate(credentials)
- # ...
+ class BaseController < ApplicationController
+ module ErrorDispatcher
+ def self.===(other)
+ Exception === other && other.respond_to?(:status)
end
end
- def generate_private_key
- # ...
+ rescue_from ErrorDispatcher do |error|
+ render status: error.status, json: { error: error.to_s }
end
end
- # app/models/user.rb
- class User < ActiveRecord::Base
- include Authentication
- end
+ *Genadi Samokovarov*
+
+* Add `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier`
+
+ Previously, the only way to decode a message with `ActiveSupport::MessageVerifier`
+ was to use `#verify`, which would raise an exception on invalid messages. Now
+ `#verified` can also be used, which returns `nil` on messages that cannot be
+ decoded.
+
+ Previously, there was no way to check if a message's format was valid without
+ attempting to decode it. `#valid_message?` is a boolean convenience method that
+ checks whether the message is valid without actually decoding it.
- *Jeremy Kemper*
+ *Logan Leger*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index d06d4f3b2d..7bffebb076 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2014 David Heinemeier Hansson
+Copyright (c) 2005-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 80d1d35888..a5339e6475 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.'
s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
- s.add_dependency 'i18n', '>= 0.7.0.beta1', '< 0.8'
+ s.add_dependency 'i18n', '~> 0.7'
s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
- s.add_dependency 'thread_safe','~> 0.1'
+ s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index f39e89b7d0..71a6b78652 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -55,7 +55,7 @@ module ActiveSupport
codepoint.combining_class = Integer($4)
#codepoint.bidi_class = $5
codepoint.decomp_type = $7
- codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect { |element| element.hex }
+ codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect(&:hex)
#codepoint.bidi_mirrored = ($13=='Y') ? true : false
codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 94468240a4..290920dbf8 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2014 David Heinemeier Hansson
+# Copyright (c) 2005-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -71,15 +71,7 @@ module ActiveSupport
NumberHelper.eager_load!
end
- @@test_order = nil
-
- def self.test_order=(new_order)
- @@test_order = new_order
- end
-
- def self.test_order
- @@test_order
- end
+ cattr_accessor :test_order # :nodoc:
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index ff67a6828c..3a1e1ac3a1 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -8,7 +8,6 @@ require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/string/inflections'
-require 'active_support/deprecation'
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
@@ -179,18 +178,6 @@ module ActiveSupport
@silence = previous_silence
end
- # :deprecated:
- def self.instrument=(boolean)
- ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument= is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it."
- true
- end
-
- # :deprecated:
- def self.instrument
- ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it."
- true
- end
-
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned.
#
@@ -615,14 +602,12 @@ module ActiveSupport
end
def value
- convert_version_4beta1_entry! if defined?(@v)
compressed? ? uncompress(@value) : @value
end
# Check if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
- convert_version_4beta1_entry! if defined?(@value)
@expires_in && @created_at + @expires_in <= Time.now.to_f
end
@@ -658,8 +643,6 @@ module ActiveSupport
# Duplicate the value in a class. This is used by cache implementations that don't natively
# serialize entries to protect against accidental cache modifications.
def dup_value!
- convert_version_4beta1_entry! if defined?(@v)
-
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
if @value.is_a?(String)
@value = @value.dup
@@ -692,26 +675,6 @@ module ActiveSupport
def uncompress(value)
Marshal.load(Zlib::Inflate.inflate(value))
end
-
- # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
- # to ensure that cache entries created under the old version still work with the new class definition.
- def convert_version_4beta1_entry!
- if defined?(@v)
- @value = @v
- remove_instance_variable(:@v)
- end
-
- if defined?(@c)
- @compressed = @c
- remove_instance_variable(:@c)
- end
-
- if defined?(@x) && @x
- @created_at ||= Time.now.to_f
- @expires_in = @x - @created_at
- remove_instance_variable(:@x)
- end
- end
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 24c702b602..0f1de8b076 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/string/filters'
require 'thread'
module ActiveSupport
@@ -121,102 +122,106 @@ module ActiveSupport
ENDING = End.new
class Before
- def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
halted_lambda = chain_config[:terminator]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback, halted_lambda, filter)
+ halting(callback_sequence, user_callback, halted_lambda, filter)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
if !halted && user_conditions.all? { |c| c.call(target, value) }
- result = user_callback.call target, value
- env.halted = halted_lambda.call(target, result)
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
if env.halted
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback, halted_lambda, filter)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
unless halted
- result = user_callback.call target, value
- env.halted = halted_lambda.call(target, result)
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
+
if env.halted
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.before do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.before do |env|
user_callback.call env.target, env.value
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :simple
end
class After
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config[:skip_after_callbacks_if_terminated]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key?(:terminator)
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
else
if user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
halted = env.halted
@@ -224,122 +229,124 @@ module ActiveSupport
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.after do |env|
unless env.halted
user_callback.call env.target, env.value
end
+
env
- }
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.after do |env|
user_callback.call env.target, env.value
+
env
- }
+ end
end
private_class_method :simple
end
class Around
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple(next_callback, user_callback)
+ simple(callback_sequence, user_callback)
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
halted = env.halted
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
+
env
else
- next_callback.call env
+ run.call env
end
- }
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
if env.halted
- next_callback.call env
+ run.call env
else
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
end
- }
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
else
- next_callback.call env
+ run.call env
end
- }
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
user_callback.call(env.target, env.value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
- }
+ end
end
private_class_method :simple
end
@@ -392,17 +399,17 @@ module ActiveSupport
end
# Wraps code with filter
- def apply(next_callback)
+ def apply(callback_sequence)
user_conditions = conditions_lambdas
user_callback = make_lambda @filter
case kind
when :before
- Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
+ Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
when :after
- Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
when :around
- Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
end
end
@@ -467,16 +474,59 @@ module ActiveSupport
end
end
+ # Execute before and after filters in a sequence instead of
+ # chaining them with nested lambda calls, see:
+ # https://github.com/rails/rails/issues/18011
+ class CallbackSequence
+ def initialize(&call)
+ @call = call
+ @before = []
+ @after = []
+ end
+
+ def before(&before)
+ @before.unshift(before)
+ self
+ end
+
+ def after(&after)
+ @after.push(after)
+ self
+ end
+
+ def around(&around)
+ CallbackSequence.new do |*args|
+ around.call(*args) {
+ self.call(*args)
+ }
+ end
+ end
+
+ def call(*args)
+ @before.each { |b| b.call(*args) }
+ value = @call.call(*args)
+ @after.each { |a| a.call(*args) }
+ value
+ end
+ end
+
# An Array with a compile method.
class CallbackChain #:nodoc:#
include Enumerable
attr_reader :name, :config
+ # If true, any callback returning +false+ will halt the entire callback
+ # chain and display a deprecation message. If false, callback chains will
+ # only be halted by calling +throw :abort+. Defaults to +true+.
+ class_attribute :halt_and_display_warning_on_return_false
+ self.halt_and_display_warning_on_return_false = true
+
def initialize(name, config)
@name = name
@config = {
- :scope => [ :kind ]
+ scope: [:kind],
+ terminator: default_terminator
}.merge!(config)
@chain = []
@callbacks = nil
@@ -511,8 +561,9 @@ module ActiveSupport
def compile
@callbacks || @mutex.synchronize do
- @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
- callback.apply chain
+ final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
+ callback.apply callback_sequence
end
end
end
@@ -546,6 +597,28 @@ module ActiveSupport
@callbacks = nil
@chain.delete_if { |c| callback.duplicates?(c) }
end
+
+ def default_terminator
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
+ end
+ end
+
+ def display_deprecation_warning_for_false_terminator
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails.
+ To explicitly halt a callback chain, please use `throw :abort` instead.
+ MSG
+ end
end
module ClassMethods
@@ -659,16 +732,17 @@ module ActiveSupport
# ===== Options
#
# * <tt>:terminator</tt> - Determines when a before filter will halt the
- # callback chain, preventing following callbacks from being called and
- # the event from being triggered. This should be a lambda to be executed.
+ # callback chain, preventing following before and around callbacks from
+ # being called and the event from being triggered.
+ # This should be a lambda to be executed.
# The current object and the return result of the callback will be called
# with the lambda.
#
# define_callbacks :validate, terminator: ->(target, result) { result == false }
#
# In this example, if any before validate callbacks returns +false+,
- # other callbacks are not executed. Defaults to +false+, meaning no value
- # halts the chain.
+ # any successive before and around callback is not executed.
+ # Defaults to +false+, meaning no value halts the chain.
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index 199aa91020..52706c3d7a 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,3 +1,4 @@
-Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path|
+DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"]
+(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path|
require path
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 76ffd23ed1..080e3b5ef7 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -92,7 +92,7 @@ class Array
if empty?
'null'
else
- collect { |element| element.id }.join(',')
+ collect(&:id).join(',')
end
else
to_default_s
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 843c592669..234283e792 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -3,14 +3,13 @@ require 'bigdecimal/util'
class BigDecimal
DEFAULT_STRING_FORMAT = 'F'
- def to_formatted_s(*args)
- if args[0].is_a?(Symbol)
- super
+ alias_method :to_default_s, :to_s
+
+ def to_s(format = nil, options = nil)
+ if format.is_a?(Symbol)
+ to_formatted_s(format, options || {})
else
- format = args[0] || DEFAULT_STRING_FORMAT
- _original_to_s(format)
+ to_default_s(format || DEFAULT_STRING_FORMAT)
end
end
- alias_method :_original_to_s, :to_s
- alias_method :to_s, :to_formatted_s
end
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
deleted file mode 100644
index 46ba93ead4..0000000000
--- a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.'
-
-require 'bigdecimal'
-require 'yaml'
-require 'active_support/core_ext/big_decimal/conversions'
-
-class BigDecimal
- YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
-
- def encode_with(coder)
- string = to_s
- coder.represent_scalar(nil, YAML_MAPPING[string] || string)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index f2a221c396..f2b7bb3ef1 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -116,12 +116,4 @@ class Class
attr_writer name if instance_writer
end
end
-
- private
-
- unless respond_to?(:singleton_class?)
- def singleton_class?
- ancestors.first != self
- end
- end
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index dc4e767e9d..55ad384f4f 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -10,7 +10,11 @@ class DateTime
end
end
- # Seconds since midnight: DateTime.now.seconds_since_midnight.
+ # Returns the number of seconds since 00:00:00.
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0
+ # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296
+ # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399
def seconds_since_midnight
sec + (min * 60) + (hour * 3600)
end
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
index 763d563231..9c9faf67ea 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -4,7 +4,7 @@ class Hash
# h1 = { a: true, b: { c: [1, 2, 3] } }
# h2 = { a: false, b: { x: [3, 4, 5] } }
#
- # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
+ # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
#
# Like with Hash#merge in the standard library, a block can be provided
# to merge values:
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index f4105f66b0..9297a59c46 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -31,13 +31,13 @@ class Hash
# hash.stringify_keys
# # => {"name"=>"Rob", "age"=>"28"}
def stringify_keys
- transform_keys{ |key| key.to_s }
+ transform_keys(&:to_s)
end
# Destructively convert all keys to strings. Same as
# +stringify_keys+, but modifies +self+.
def stringify_keys!
- transform_keys!{ |key| key.to_s }
+ transform_keys!(&:to_s)
end
# Returns a new hash with all keys converted to symbols, as long as
@@ -105,14 +105,14 @@ class Hash
# hash.deep_stringify_keys
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
def deep_stringify_keys
- deep_transform_keys{ |key| key.to_s }
+ deep_transform_keys(&:to_s)
end
# Destructively convert all keys to strings.
# This includes the keys from the root hash and from all
# nested hashes and arrays.
def deep_stringify_keys!
- deep_transform_keys!{ |key| key.to_s }
+ deep_transform_keys!(&:to_s)
end
# Returns a new hash with all keys converted to symbols, as long as
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index 293a3b2619..364ed9d65f 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/kernel/agnostics'
require 'active_support/core_ext/kernel/concern'
-require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
index 2073cac98d..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
@@ -1,10 +1,3 @@
-module Kernel
- unless respond_to?(:debugger)
- # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it).
- def debugger
- message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
- defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
- end
- alias breakpoint debugger unless respond_to?(:breakpoint)
- end
-end
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index f5179552bb..eb44646848 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -29,34 +29,6 @@ module Kernel
$VERBOSE = old_verbose
end
- # For compatibility
- def silence_stderr #:nodoc:
- ActiveSupport::Deprecation.warn(
- "`#silence_stderr` is deprecated and will be removed in the next release."
- ) #not thread-safe
- silence_stream(STDERR) { yield }
- end
-
- # Deprecated : this method is not thread safe
- # Silences any stream for the duration of the block.
- #
- # silence_stream(STDOUT) do
- # puts 'This will never be seen'
- # end
- #
- # puts 'But this will'
- #
- # This method is not thread-safe.
- def silence_stream(stream)
- old_stream = stream.dup
- stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
- stream.sync = true
- yield
- ensure
- stream.reopen(old_stream)
- old_stream.close
- end
-
# Blocks and ignores any exception passed as argument if raised within the block.
#
# suppress(ZeroDivisionError) do
@@ -69,56 +41,4 @@ module Kernel
yield
rescue *exception_classes
end
-
- # Captures the given stream and returns it:
- #
- # stream = capture(:stdout) { puts 'notice' }
- # stream # => "notice\n"
- #
- # stream = capture(:stderr) { warn 'error' }
- # stream # => "error\n"
- #
- # even for subprocesses:
- #
- # stream = capture(:stdout) { system('echo notice') }
- # stream # => "notice\n"
- #
- # stream = capture(:stderr) { system('echo error 1>&2') }
- # stream # => "error\n"
- def capture(stream)
- ActiveSupport::Deprecation.warn(
- "`#capture(stream)` is deprecated and will be removed in the next release."
- ) #not thread-safe
- 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
- alias :silence :capture
-
- # Silences both STDOUT and STDERR, even for subprocesses.
- #
- # quietly { system 'bundle install' }
- #
- # This method is not thread-safe.
- def quietly
- ActiveSupport::Deprecation.warn(
- "`#quietly` is deprecated and will be removed in the next release."
- ) #not thread-safe
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
end
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 768b980f21..d9fb392752 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation/proxy_wrappers'
+
class LoadError
REGEXPS = [
/^no such file to load -- (.+)$/i,
@@ -25,4 +27,4 @@ class LoadError
end
end
-MissingSourceFile = LoadError
+MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('MissingSourceFile', 'LoadError')
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index 67f0e0335d..93fb598650 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -27,11 +27,8 @@ class Module
def attr_internal_define(attr_name, type)
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
- # class_eval is necessary on 1.9 or else the methods are made private
- class_eval do
- # use native attr_* methods as they are faster on some Ruby implementations
- send("attr_#{type}", internal_name)
- end
+ # use native attr_* methods as they are faster on some Ruby implementations
+ send("attr_#{type}", internal_name)
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
alias_method attr_name, internal_name
remove_method internal_name
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index d317df5079..d4e6b5a1ac 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -203,7 +203,7 @@ class Module
# include HairColors
# end
#
- # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_accessor(*syms, &blk)
mattr_reader(*syms, &blk)
mattr_writer(*syms, &blk)
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 570585b89a..24df83800b 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -17,7 +17,7 @@ class Module
# ==== Options
# * <tt>:to</tt> - Specifies the target object
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
- # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised
+ # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised
#
# The macro receives one or more method names (specified as symbols or
# strings) and the name of the target object via the <tt>:to</tt> option
diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
index b1097cc83b..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
+++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
@@ -1,11 +1,3 @@
-class Module
- ###
- # TODO: remove this after 1.9 support is dropped
- def methods_transplantable? # :nodoc:
- x = Module.new { def foo; end }
- Module.new { define_method :bar, x.instance_method(:foo) }
- true
- rescue TypeError
- false
- end
-end
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb
index e1ebd4f91c..b82148e4e5 100644
--- a/activesupport/lib/active_support/core_ext/name_error.rb
+++ b/activesupport/lib/active_support/core_ext/name_error.rb
@@ -1,5 +1,12 @@
class NameError
# Extract the name of the missing constant from the exception message.
+ #
+ # begin
+ # HelloWorld
+ # rescue NameError => e
+ # e.missing_name
+ # end
+ # # => "HelloWorld"
def missing_name
if /undefined local variable or method/ !~ message
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
@@ -7,6 +14,13 @@ class NameError
end
# Was this exception raised because the given name was missing?
+ #
+ # begin
+ # HelloWorld
+ # rescue NameError => e
+ # e.missing_name?("HelloWorld")
+ # end
+ # # => true
def missing_name?(name)
if name.is_a? Symbol
last_name = (missing_name || '').split('::').last
diff --git a/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
index deea8e9358..dfbca32474 100644
--- a/activesupport/lib/active_support/core_ext/numeric/bytes.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
@@ -7,36 +7,56 @@ class Numeric
EXABYTE = PETABYTE * 1024
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
+ #
+ # 2.bytes # => 2
def bytes
self
end
alias :byte :bytes
+ # Returns the number of bytes equivalent to the kilobytes provided.
+ #
+ # 2.kilobytes # => 2048
def kilobytes
self * KILOBYTE
end
alias :kilobyte :kilobytes
+ # Returns the number of bytes equivalent to the megabytes provided.
+ #
+ # 2.megabytes # => 2_097_152
def megabytes
self * MEGABYTE
end
alias :megabyte :megabytes
+ # Returns the number of bytes equivalent to the gigabytes provided.
+ #
+ # 2.gigabytes # => 2_147_483_648
def gigabytes
self * GIGABYTE
end
alias :gigabyte :gigabytes
+ # Returns the number of bytes equivalent to the terabytes provided.
+ #
+ # 2.terabytes # => 2_199_023_255_552
def terabytes
self * TERABYTE
end
alias :terabyte :terabytes
+ # Returns the number of bytes equivalent to the petabytes provided.
+ #
+ # 2.petabytes # => 2_251_799_813_685_248
def petabytes
self * PETABYTE
end
alias :petabyte :petabytes
+ # Returns the number of bytes equivalent to the exabytes provided.
+ #
+ # 2.exabytes # => 2_305_843_009_213_693_952
def exabytes
self * EXABYTE
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 6d3635c69a..0c8ff79237 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -118,18 +118,28 @@ class Numeric
end
end
- [Float, Fixnum, Bignum, BigDecimal].each do |klass|
- klass.send(:alias_method, :to_default_s, :to_s)
-
- klass.send(:define_method, :to_s) do |*args|
- if args[0].is_a?(Symbol)
- format = args[0]
- options = args[1] || {}
+ [Fixnum, Bignum].each do |klass|
+ klass.class_eval do
+ alias_method :to_default_s, :to_s
+ def to_s(base_or_format = 10, options = nil)
+ if base_or_format.is_a?(Symbol)
+ to_formatted_s(base_or_format, options || {})
+ else
+ to_default_s(base_or_format)
+ end
+ end
+ end
+ end
- self.to_formatted_s(format, options)
+ Float.class_eval do
+ alias_method :to_default_s, :to_s
+ def to_s(*args)
+ if args.empty?
+ to_default_s
else
- to_default_s(*args)
+ to_formatted_s(*args)
end
end
end
+
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 689fae4830..ef32817f55 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -36,33 +36,51 @@ class Numeric
end
alias :second :seconds
+ # Returns a Duration instance matching the number of minutes provided.
+ #
+ # 2.minutes # => 120 seconds
def minutes
ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]])
end
alias :minute :minutes
+ # Returns a Duration instance matching the number of hours provided.
+ #
+ # 2.hours # => 7_200 seconds
def hours
ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]])
end
alias :hour :hours
+ # Returns a Duration instance matching the number of days provided.
+ #
+ # 2.days # => 2 days
def days
ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
end
alias :day :days
+ # Returns a Duration instance matching the number of weeks provided.
+ #
+ # 2.weeks # => 14 days
def weeks
ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
end
alias :week :weeks
+ # Returns a Duration instance matching the number of fortnights provided.
+ #
+ # 2.fortnights # => 28 days
def fortnights
ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]])
end
alias :fortnight :fortnights
+ # Returns the number of milliseconds equivalent to the seconds provided.
# Used with the standard time durations, like 1.hour.in_milliseconds --
# so we can feed them to JavaScript functions like getTime().
+ #
+ # 2.in_milliseconds # => 2_000
def in_milliseconds
self * 1000
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index f1106cca9b..f4f9152d6a 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -2,7 +2,6 @@ require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/deep_dup'
-require 'active_support/core_ext/object/itself'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 2e99f4a1b8..0191d2e973 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -25,7 +25,7 @@ class Array
# array[1][2] # => nil
# dup[1][2] # => 4
def deep_dup
- map { |it| it.deep_dup }
+ map(&:deep_dup)
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 665cb0f96d..620f7b6561 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects;
+ # False for +nil+, +false+, +true+, symbol, number objects;
# true otherwise.
def duplicable?
true
@@ -78,17 +78,8 @@ end
require 'bigdecimal'
class BigDecimal
- # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
- # raises TypeError exception. Checking here on the runtime whether BigDecimal
- # will allow dup or not.
- begin
- BigDecimal.new('4.56').dup
-
- def duplicable?
- true
- end
- rescue TypeError
- # can't dup, so use superclass implementation
+ def duplicable?
+ true
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
index 755e1c6b16..593a7a4940 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -23,6 +23,6 @@ class Object
#
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
def instance_variable_names
- instance_variables.map { |var| var.to_s }
+ instance_variables.map(&:to_s)
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/itself.rb b/activesupport/lib/active_support/core_ext/object/itself.rb
deleted file mode 100644
index d71cea6674..0000000000
--- a/activesupport/lib/active_support/core_ext/object/itself.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class Object
- # TODO: Remove this file when we drop support for Ruby < 2.2
- unless respond_to?(:itself)
- # Returns the object itself.
- #
- # Useful for chaining methods, such as Active Record scopes:
- #
- # Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
- #
- # @return Object
- def itself
- self
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb
index ccd568bbf5..ec5ace4e16 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -38,7 +38,7 @@ class Array
# Calls <tt>to_param</tt> on all its elements and joins the result with
# slashes. This is used by <tt>url_for</tt> in Action Pack.
def to_param
- collect { |e| e.to_param }.join '/'
+ collect(&:to_param).join '/'
end
# Converts an array into a string suitable for use as a URL query string,
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 56da398978..e0f70b9caa 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -21,11 +21,11 @@ class Object
#
# +try+ will also return +nil+ if the receiver does not respond to the method:
#
- # @person.try(:non_existing_method) #=> nil
+ # @person.try(:non_existing_method) # => nil
#
# instead of
#
- # @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil
+ # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
#
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
# to the method:
@@ -63,9 +63,12 @@ class Object
try!(*a, &b) if a.empty? || respond_to?(a.first)
end
- # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and
- # does not implement the tried method.
-
+ # Same as #try, but raises a NoMethodError exception if the receiver is
+ # not +nil+ and does not implement the tried method.
+ #
+ # "a".try!(:upcase) # => "A"
+ # nil.try!(:upcase) # => nil
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
def try!(*a, &b)
if a.empty? && block_given?
if b.arity.zero?
@@ -94,6 +97,9 @@ class NilClass
nil
end
+ # Calling +try!+ on +nil+ always returns +nil+.
+ #
+ # nil.try!(:name) # => nil
def try!(*args)
nil
end
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 042283e4fc..ba8d4acd6d 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -1,6 +1,5 @@
require 'erb'
require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/deprecation'
class ERB
module Util
@@ -150,7 +149,11 @@ module ActiveSupport #:nodoc:
else
if html_safe?
new_safe_buffer = super
- new_safe_buffer.instance_variable_set :@html_safe, true
+
+ if new_safe_buffer
+ new_safe_buffer.instance_variable_set :@html_safe, true
+ end
+
new_safe_buffer
else
to_str[*args]
@@ -186,11 +189,6 @@ module ActiveSupport #:nodoc:
super(html_escape_interpolated_argument(value))
end
- def prepend!(value)
- ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend
- prepend value
- end
-
def +(other)
dup.concat(other)
end
@@ -247,6 +245,11 @@ module ActiveSupport #:nodoc:
end
class String
+ # Marks a string as trusted safe. It will be inserted into HTML with no
+ # additional escaping performed. It is your responsibilty to ensure that the
+ # string contains no malicious content. This method is equivalent to the
+ # `raw` helper in views. It is recommended that you use `sanitize` instead of
+ # this method. It should never be called on user input.
def html_safe
ActiveSupport::SafeBuffer.new(self)
end
diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb
index c2c30044f2..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/struct.rb
+++ b/activesupport/lib/active_support/core_ext/struct.rb
@@ -1,6 +1,3 @@
-# Backport of Struct#to_h from Ruby 2.0
-class Struct # :nodoc:
- def to_h
- Hash[members.zip(values)]
- end
-end unless Struct.instance_methods.include?(:to_h)
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
deleted file mode 100644
index 4cd6634558..0000000000
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-class Thread
- LOCK = Mutex.new # :nodoc:
-
- # Returns the value of a thread local variable that has been set. Note that
- # these are different than fiber local values.
- #
- # Thread local values are carried along with threads, and do not respect
- # fibers. For example:
- #
- # Thread.new {
- # Thread.current.thread_variable_set("foo", "bar") # set a thread local
- # Thread.current["foo"] = "bar" # set a fiber local
- #
- # Fiber.new {
- # Fiber.yield [
- # Thread.current.thread_variable_get("foo"), # get the thread local
- # Thread.current["foo"], # get the fiber local
- # ]
- # }.resume
- # }.join.value # => ['bar', nil]
- #
- # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned
- # for the fiber local. The fiber is executed in the same thread, so the
- # thread local values are available.
- def thread_variable_get(key)
- _locals[key.to_sym]
- end
-
- # Sets a thread local with +key+ to +value+. Note that these are local to
- # threads, and not to fibers. Please see Thread#thread_variable_get for
- # more information.
- def thread_variable_set(key, value)
- _locals[key.to_sym] = value
- end
-
- # Returns an array of the names of the thread-local variables (as Symbols).
- #
- # thr = Thread.new do
- # Thread.current.thread_variable_set(:cat, 'meow')
- # Thread.current.thread_variable_set("dog", 'woof')
- # end
- # thr.join # => #<Thread:0x401b3f10 dead>
- # thr.thread_variables # => [:dog, :cat]
- #
- # Note that these are not fiber local variables. Please see Thread#thread_variable_get
- # for more details.
- def thread_variables
- _locals.keys
- end
-
- # Returns <tt>true</tt> if the given string (or symbol) exists as a
- # thread-local variable.
- #
- # me = Thread.current
- # me.thread_variable_set(:oliver, "a")
- # me.thread_variable?(:oliver) # => true
- # me.thread_variable?(:stanley) # => false
- #
- # Note that these are not fiber local variables. Please see Thread#thread_variable_get
- # for more details.
- def thread_variable?(key)
- _locals.has_key?(key.to_sym)
- end
-
- # Freezes the thread so that thread local variables cannot be set via
- # Thread#thread_variable_set, nor can fiber local variables be set.
- #
- # me = Thread.current
- # me.freeze
- # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals
- # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals
- def freeze
- _locals.freeze
- super
- end
-
- private
-
- def _locals
- if defined?(@_locals)
- @_locals
- else
- LOCK.synchronize { @_locals ||= {} }
- end
- end
-end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 32cffe237d..72c3234630 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/time/marshal'
require 'active_support/core_ext/time/zones'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index ab8307429a..649dc52865 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -162,7 +162,7 @@ class Time
alias :at_noon :middle_of_day
alias :at_middle_of_day :middle_of_day
- # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the day, 23:59:59.999999
def end_of_day
change(
:hour => 23,
@@ -179,7 +179,7 @@ class Time
end
alias :at_beginning_of_hour :beginning_of_hour
- # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the hour, x:59:59.999999
def end_of_hour
change(
:min => 59,
@@ -195,7 +195,7 @@ class Time
end
alias :at_beginning_of_minute :beginning_of_minute
- # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the minute, x:xx:59.999999
def end_of_minute
change(
:sec => 59,
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 497c4c3fb8..467bad1726 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -1,30 +1,3 @@
-# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only
-# preserves utc_offset. Preserve zone also, even though it may not
-# work in some edge cases.
-if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
- class Time
- class << self
- alias_method :_load_without_zone, :_load
- def _load(marshaled_time)
- time = _load_without_zone(marshaled_time)
- time.instance_eval do
- if zone = defined?(@_zone) && remove_instance_variable('@_zone')
- ary = to_a
- ary[0] += subsec if ary[0] == sec
- ary[-1] = zone
- utc? ? Time.utc(*ary) : Time.local(*ary)
- else
- self
- end
- end
- end
- end
+require 'active_support/deprecation'
- alias_method :_dump_without_zone, :_dump
- def _dump(*args)
- obj = dup
- obj.instance_variable_set('@_zone', zone)
- obj.send :_dump_without_zone, *args
- end
- end
-end
+ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 64c3b7201b..0668eadb1e 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -51,7 +51,16 @@ class Time
end
end
- # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
+ # Returns a TimeZone instance matching the time zone provided.
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
+ # Raises an ArgumentError for invalid time zones.
+ #
+ # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
+ # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
+ # Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...>
+ # Time.find_zone! nil # => nil
+ # Time.find_zone! false # => false
+ # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE
def find_zone!(time_zone)
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
time_zone
@@ -72,6 +81,12 @@ class Time
raise ArgumentError, "Invalid Timezone: #{time_zone}"
end
+ # Returns a TimeZone instance matching the time zone provided.
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
+ # Returns +nil+ for invalid time zones.
+ #
+ # Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
+ # Time.find_zone "NOT-A-TIMEZONE" # => nil
def find_zone(time_zone)
find_zone!(time_zone) rescue nil
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 65a370dd30..e03e7c30d8 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -421,7 +421,7 @@ module ActiveSupport #:nodoc:
end
def load_once_path?(path)
- # to_s works around a ruby1.9 issue where String#starts_with?(Pathname)
+ # to_s works around a ruby issue where String#starts_with?(Pathname)
# will raise a TypeError: no implicit conversion of Pathname into String
autoload_once_paths.any? { |base| path.starts_with? base.to_s }
end
@@ -742,7 +742,7 @@ module ActiveSupport #:nodoc:
protected
def log_call(*args)
if log_activity?
- arg_str = args.collect { |arg| arg.inspect } * ', '
+ arg_str = args.collect(&:inspect) * ', '
/in `([a-z_\?\!]+)'/ =~ caller(1).first
selector = $1 || '<unknown>'
log "called #{selector}(#{arg_str})"
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 328b8c320a..9f9dca8453 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -20,7 +20,7 @@ module ActiveSupport
log: ->(message, callstack) {
logger =
- if defined?(Rails) && Rails.logger
+ if defined?(Rails.logger) && Rails.logger
Rails.logger
else
require 'active_support/logger'
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 0de1d2c7df..2818b8d68b 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -98,7 +98,7 @@ module ActiveSupport
to_i
end
- def respond_to_missing?(method, include_private=false) #:nodoc
+ def respond_to_missing?(method, include_private=false) #:nodoc:
@value.respond_to?(method, include_private)
end
@@ -122,13 +122,6 @@ module ActiveSupport
private
- # We define it as a workaround to Ruby 2.0.0-p353 bug.
- # For more information, check rails/rails#13055.
- # Remove it when we drop support for 2.0.0-p353.
- def ===(other) #:nodoc:
- value === other
- end
-
def method_missing(method, *args, &block) #:nodoc:
value.send(method, *args, &block)
end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index bc7933e38b..7068f09d87 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -5,10 +5,10 @@ module ActiveSupport
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index affcfb7398..9e742b1917 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -55,7 +55,13 @@ module I18n
reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
app.reloaders << reloader
- ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
+ ActionDispatch::Reloader.to_prepare do
+ reloader.execute_if_updated
+ # TODO: remove the following line as soon as the return value of
+ # callbacks is ignored, that is, returning `false` does not
+ # display a deprecation warning or halts the callback chain.
+ true
+ end
reloader.execute
@i18n_inited = true
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index c0ac5af153..48f4967892 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -6,7 +6,6 @@ module ActiveSupport
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:time_precision, :time_precision=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
- :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:json_encoder, :json_encoder=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -113,54 +112,6 @@ module ActiveSupport
# Sets the encoder used by Rails to encode Ruby objects into JSON strings
# in +Object#to_json+ and +ActiveSupport::JSON.encode+.
attr_accessor :json_encoder
-
- def encode_big_decimal_as_string=(as_string)
- message = \
- "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
- "the new encoder will always encode them as strings.\n\n" \
- "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \
- "your configuration file. If you have been setting this to true, you can safely remove it from " \
- "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \
- "Gemfile in order to restore this functionality."
-
- raise NotImplementedError, message
- end
-
- def encode_big_decimal_as_string
- message = \
- "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
- "the new encoder will always encode them as strings.\n\n" \
- "You are seeing this error because you are trying to check the value of the related configuration, " \
- "`active_support.encode_big_decimal_as_string`. If your application depends on this option, you should " \
- "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \
- "In the future, it will be removed from Rails, so you should stop checking its value."
-
- ActiveSupport::Deprecation.warn message
-
- true
- end
-
- # Deprecate CircularReferenceError
- def const_missing(name)
- if name == :CircularReferenceError
- message = "The JSON encoder in Rails 4.1 no longer offers protection from circular references. " \
- "You are seeing this warning because you are rescuing from (or otherwise referencing) " \
- "ActiveSupport::Encoding::CircularReferenceError. In the future, this error will be " \
- "removed from Rails. You should remove these rescue blocks from your code and ensure " \
- "that your data structures are free of circular references so they can be properly " \
- "serialized into JSON.\n\n" \
- "For example, the following Hash contains a circular reference to itself:\n" \
- " h = {}\n" \
- " h['circular'] = h\n" \
- "In this case, calling h.to_json would not work properly."
-
- ActiveSupport::Deprecation.warn message
-
- SystemStackError
- else
- super
- end
- end
end
self.use_standard_json_time_format = true
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index a8a8de5672..eee9bbaead 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -34,22 +34,78 @@ module ActiveSupport
@serializer = options[:serializer] || Marshal
end
- def verify(signed_message)
- raise InvalidSignature if signed_message.blank?
+ # Checks if a signed message could have been generated by signing an object
+ # with the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # signed_message = verifier.generate 'a private message'
+ # verifier.valid_message?(signed_message) # => true
+ #
+ # tampered_message = signed_message.chop # editing the message invalidates the signature
+ # verifier.valid_message?(tampered_message) # => false
+ def valid_message?(signed_message)
+ return if signed_message.blank?
data, digest = signed_message.split("--")
- if data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
+ end
+
+ # Decodes the signed message using the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ #
+ # signed_message = verifier.generate 'a private message'
+ # verifier.verified(signed_message) # => 'a private message'
+ #
+ # Returns +nil+ if the message was not signed with the same secret.
+ #
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
+ # other_verifier.verified(signed_message) # => nil
+ #
+ # Returns +nil+ if the message is not Base64-encoded.
+ #
+ # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
+ # verifier.verified(invalid_message) # => nil
+ #
+ # Raises any error raised while decoding the signed message.
+ #
+ # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
+ # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
+ def verified(signed_message)
+ if valid_message?(signed_message)
begin
+ data = signed_message.split("--")[0]
@serializer.load(decode(data))
rescue ArgumentError => argument_error
- raise InvalidSignature if argument_error.message =~ %r{invalid base64}
+ return if argument_error.message =~ %r{invalid base64}
raise
end
- else
- raise InvalidSignature
end
end
+ # Decodes the signed message using the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # signed_message = verifier.generate 'a private message'
+ #
+ # verifier.verify(signed_message) # => 'a private message'
+ #
+ # Raises +InvalidSignature+ if the message was not signed with the same
+ # secret or was not Base64-encoded.
+ #
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
+ # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
+ def verify(signed_message)
+ verified(signed_message) || raise(InvalidSignature)
+ end
+
+ # Generates a signed message for the provided value.
+ #
+ # The message is signed with the +MessageVerifier+'s secret. Without knowing
+ # the secret, the original value cannot be extracted from the message.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
def generate(value)
data = encode(@serializer.dump(value))
"#{data}--#{generate_digest(data)}"
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 7ab6293b60..35efebc65f 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -211,9 +211,8 @@ module ActiveSupport
codepoints
end
- # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1.
# Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars.
- if '<3'.respond_to?(:scrub) && !defined?(Rubinius)
+ if !defined?(Rubinius)
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
# resulting in a valid UTF-8 string.
#
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 3a244b34b5..075ddc2382 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -57,6 +57,18 @@ module ActiveSupport
@duration = nil
end
+ # Returns the difference in milliseconds between when the execution of the
+ # event started and when it ended.
+ #
+ # ActiveSupport::Notifications.subscribe('wait') do |*args|
+ # @event = ActiveSupport::Notifications::Event.new(*args)
+ # end
+ #
+ # ActiveSupport::Notifications.instrument('wait') do
+ # sleep 1
+ # end
+ #
+ # @event.duration # => 1000.138
def duration
@duration ||= 1000.0 * (self.end - time)
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
index 9a3dc526ae..6940beb318 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -59,7 +59,7 @@ module ActiveSupport
translate_in_locale("human.decimal_units.units", raise: true)
else
raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e }
+ end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@)
end
end
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 133aa6a054..6eba24b569 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -13,6 +13,13 @@ module ActiveSupport
end
end
+ initializer "active_support.halt_callback_chains_on_return_false", after: :load_config_initializers do |app|
+ if app.config.active_support.key? :halt_callback_chains_on_return_false
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = \
+ app.config.active_support.halt_callback_chains_on_return_false
+ end
+ end
+
# Sets the default value for Time.zone
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
initializer "active_support.initialize_time_zone" do |app|
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index a7eba91ac5..1a02acd5b1 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -60,7 +60,7 @@ module ActiveSupport
end
klasses.each do |klass|
- key = if klass.is_a?(Class) && klass <= Exception
+ key = if klass.is_a?(Module) && klass.respond_to?(:===)
klass.name
elsif klass.is_a?(String)
klass
@@ -101,7 +101,7 @@ module ActiveSupport
# itself when rescue_from CONSTANT is executed.
klass = self.class.const_get(klass_name) rescue nil
klass ||= klass_name.constantize rescue nil
- exception.is_a?(klass) if klass
+ klass === exception if klass
end
case rescuer
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index d5c2222d2e..9086a959aa 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -43,7 +43,9 @@ module ActiveSupport
end
def current_tags
- Thread.current[:activesupport_tagged_logging_tags] ||= []
+ # We use our object ID here to void conflicting with other instances
+ thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze
+ Thread.current[thread_key] ||= []
end
private
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index a4ba5989b1..c7d6c62129 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -9,36 +9,36 @@ require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
require 'active_support/core_ext/kernel/reporting'
-require 'active_support/deprecation'
module ActiveSupport
class TestCase < ::Minitest::Test
Assertion = Minitest::Assertion
class << self
+ # Sets the order in which test cases are run.
+ #
+ # ActiveSupport::TestCase.test_order = :random # => :random
+ #
+ # Valid values are:
+ # * +:random+ (to run tests in random order)
+ # * +:parallel+ (to run tests in parallel)
+ # * +:sorted+ (to run tests alphabetically by method name)
+ # * +:alpha+ (equivalent to +:sorted+)
def test_order=(new_order)
ActiveSupport.test_order = new_order
end
+ # Returns the order in which test cases are run.
+ #
+ # ActiveSupport::TestCase.test_order # => :random
+ #
+ # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
+ # Defaults to +:random+.
def test_order
test_order = ActiveSupport.test_order
if test_order.nil?
- ActiveSupport::Deprecation.warn "You did not specify a value for the " \
- "configuration option `active_support.test_order`. In Rails 5, " \
- "the default value of this option will change from `:sorted` to " \
- "`:random`.\n" \
- "To disable this warning and keep the current behavior, you can add " \
- "the following line to your `config/environments/test.rb`:\n" \
- "\n" \
- " Rails.application.configure do\n" \
- " config.active_support.test_order = :sorted\n" \
- " end\n" \
- "\n" \
- "Alternatively, you can opt into the future behavior by setting this " \
- "option to `:random`."
-
- test_order = :sorted
+ test_order = :random
self.test_order = test_order
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 11cca82995..8b649c193f 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -66,7 +66,7 @@ module ActiveSupport
exps = expressions.map { |e|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
}
- before = exps.map { |e| e.call }
+ before = exps.map(&:call)
yield
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index dbee145196..8ddf233b3e 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -121,16 +121,25 @@ module ActiveSupport
utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
- # Time uses +zone+ to display the time zone abbreviation, so we're
- # duck-typing it.
+ # Returns the time zone abbreviation.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)"
+ # Time.zone.now.zone # => "EST"
def zone
period.zone_identifier.to_s
end
+ # Returns a string of the object's date, time, zone and offset from UTC.
+ #
+ # Time.zone.now.httpdate # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
def inspect
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
end
+ # Returns a string of the object's date and time in the ISO 8601 standard
+ # format.
+ #
+ # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
def xmlschema(fraction_digits = 0)
fraction = if fraction_digits.to_i > 0
(".%06i" % time.usec)[0, fraction_digits.to_i + 1]
@@ -187,7 +196,7 @@ module ActiveSupport
# Returns a string of the object's date and time.
# Accepts an optional <tt>format</tt>:
- # * <tt>:default</tt> - default value, mimics Ruby 1.9 Time#to_s format.
+ # * <tt>:default</tt> - default value, mimics Ruby Time#to_s format.
# * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db).
# * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb.
def to_s(format = :default)
@@ -196,7 +205,7 @@ module ActiveSupport
elsif formatter = ::Time::DATE_FORMATS[format]
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
else
- "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
+ "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format
end
end
alias_method :to_formatted_s, :to_s
@@ -243,9 +252,23 @@ module ActiveSupport
utc.hash
end
+ # Adds an interval of time to the current object's time and return that
+ # value as a new TimeWithZone object.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now + 1000 # => Sun, 02 Nov 2014 01:43:08 EDT -04:00
+ #
+ # If we're adding a Duration of variable length (i.e., years, months, days),
+ # move forward from #time, otherwise move forward from #utc, for accuracy
+ # when moving across DST boundaries.
+ #
+ # For instance, a time + 24.hours will advance exactly 24 hours, while a
+ # time + 1.day will advance 23-25 hours, depending on the day.
+ #
+ # now + 24.hours # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now + 1.day # => Mon, 03 Nov 2014 01:26:28 EST -05:00
def +(other)
- # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
- # otherwise move forward from #utc, for accuracy when moving across DST boundaries
if duration_of_variable_length?(other)
method_missing(:+, other)
else
@@ -253,10 +276,25 @@ module ActiveSupport
result.in_time_zone(time_zone)
end
end
+ alias_method :since, :+
+ # Returns a new TimeWithZone object that represents the difference between
+ # the current object's time and the +other+ time.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00
+ # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00
+ #
+ # If subtracting a Duration of variable length (i.e., years, months, days),
+ # move backward from #time, otherwise move backward from #utc, for accuracy
+ # when moving across DST boundaries.
+ #
+ # For instance, a time - 24.hours will go subtract exactly 24 hours, while a
+ # time - 1.day will subtract 23-25 hours, depending on the day.
+ #
+ # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00
+ # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00
def -(other)
- # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
- # otherwise move backwards #utc, for accuracy when moving across DST boundaries
if other.acts_like?(:time)
to_time - other.to_time
elsif duration_of_variable_length?(other)
@@ -267,16 +305,6 @@ module ActiveSupport
end
end
- def since(other)
- # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
- # otherwise move forward from #utc, for accuracy when moving across DST boundaries
- if duration_of_variable_length?(other)
- method_missing(:since, other)
- else
- utc.since(other).in_time_zone(time_zone)
- end
- end
-
def ago(other)
since(-other)
end
@@ -303,15 +331,27 @@ module ActiveSupport
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
+ # Returns the object's date and time as a floating point number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_f # => 1417709320.285418
def to_f
utc.to_f
end
+ # Returns the object's date and time as an integer number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_i # => 1417709320
def to_i
utc.to_i
end
alias_method :tv_sec, :to_i
+ # Returns the object's date and time as a rational number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_r # => (708854548642709/500000)
def to_r
utc.to_r
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 55533a5d40..17629eabb3 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -202,7 +202,7 @@ module ActiveSupport
end
def find_tzinfo(name)
- TZInfo::TimezoneProxy.new(MAPPING[name] || name)
+ TZInfo::Timezone.new(MAPPING[name] || name)
end
alias_method :create, :new
@@ -237,7 +237,7 @@ module ActiveSupport
case arg
when String
begin
- @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset }
+ @lazy_zones_map[arg] ||= create(arg)
rescue TZInfo::InvalidTimezoneIdentifier
nil
end
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 27c64c4dca..f303daa1a7 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -141,7 +141,7 @@ module ActiveSupport
(0...attributes.length).each do |i|
attribute_hash[CONTENT_KEY] ||= ''
attribute_hash[attributes.item(i).name] = attributes.item(i).value
- end
+ end
attribute_hash
end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 5945605f7b..98f3ea7a14 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -1047,30 +1047,4 @@ class CacheEntryTest < ActiveSupport::TestCase
assert_equal value, entry.value
assert_equal value.bytesize, entry.size
end
-
- def test_restoring_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal false, entry.expired?
- end
-
- def test_restoring_compressed_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello")))
- version_4beta1_entry.instance_variable_set(:@c, true)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- end
-
- def test_restoring_expired_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal true, entry.expired?
- end
end
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 32c2dfdfc0..f6abef8cee 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -49,7 +49,7 @@ module CallbacksTest
def self.before(model)
model.history << [:before_save, :class]
end
-
+
def self.after(model)
model.history << [:after_save, :class]
end
@@ -501,21 +501,18 @@ module CallbacksTest
end
end
- class CallbackTerminator
+ class AbstractCallbackTerminator
include ActiveSupport::Callbacks
- define_callbacks :save, :terminator => ->(_,result) { result == :halt }
-
- set_callback :save, :before, :first
- set_callback :save, :before, :second
- set_callback :save, :around, :around_it
- set_callback :save, :before, :third
- set_callback :save, :after, :first
- set_callback :save, :around, :around_it
- set_callback :save, :after, :second
- set_callback :save, :around, :around_it
- set_callback :save, :after, :third
-
+ def self.set_save_callbacks
+ set_callback :save, :before, :first
+ set_callback :save, :before, :second
+ set_callback :save, :around, :around_it
+ set_callback :save, :before, :third
+ set_callback :save, :after, :first
+ set_callback :save, :around, :around_it
+ set_callback :save, :after, :third
+ end
attr_reader :history, :saved, :halted
def initialize
@@ -552,6 +549,39 @@ module CallbacksTest
end
end
+ class CallbackTerminator < AbstractCallbackTerminator
+ define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt }
+ set_save_callbacks
+ end
+
+ class CallbackTerminatorSkippingAfterCallbacks < AbstractCallbackTerminator
+ define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt },
+ skip_after_callbacks_if_terminated: true
+ set_save_callbacks
+ end
+
+ class CallbackDefaultTerminator < AbstractCallbackTerminator
+ define_callbacks :save
+
+ def second
+ @history << "second"
+ throw(:abort)
+ end
+
+ set_save_callbacks
+ end
+
+ class CallbackFalseTerminator < AbstractCallbackTerminator
+ define_callbacks :save
+
+ def second
+ @history << "second"
+ false
+ end
+
+ set_save_callbacks
+ end
+
class CallbackObject
def before(caller)
caller.record << "before"
@@ -688,10 +718,10 @@ module CallbacksTest
end
class CallbackTerminatorTest < ActiveSupport::TestCase
- def test_termination
+ def test_termination_skips_following_before_and_around_callbacks
terminator = CallbackTerminator.new
terminator.save
- assert_equal ["first", "second", "third", "second", "first"], terminator.history
+ assert_equal ["first", "second", "third", "first"], terminator.history
end
def test_termination_invokes_hook
@@ -707,6 +737,73 @@ module CallbacksTest
end
end
+ class CallbackTerminatorSkippingAfterCallbacksTest < ActiveSupport::TestCase
+ def test_termination_skips_after_callbacks
+ terminator = CallbackTerminatorSkippingAfterCallbacks.new
+ terminator.save
+ assert_equal ["first", "second"], terminator.history
+ end
+ end
+
+ class CallbackDefaultTerminatorTest < ActiveSupport::TestCase
+ def test_default_termination
+ terminator = CallbackDefaultTerminator.new
+ terminator.save
+ assert_equal ["first", "second", "third", "first"], terminator.history
+ end
+
+ def test_default_termination_invokes_hook
+ terminator = CallbackDefaultTerminator.new
+ terminator.save
+ assert_equal :second, terminator.halted
+ end
+
+ def test_block_never_called_if_abort_is_thrown
+ obj = CallbackDefaultTerminator.new
+ obj.save
+ assert !obj.saved
+ end
+ end
+
+ class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase
+ def test_returning_false_halts_callback_if_config_variable_is_not_set
+ obj = CallbackFalseTerminator.new
+ assert_deprecated do
+ obj.save
+ assert_equal :second, obj.halted
+ assert !obj.saved
+ end
+ end
+ end
+
+ class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase
+ def setup
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true
+ end
+
+ def test_returning_false_halts_callback_if_config_variable_is_true
+ obj = CallbackFalseTerminator.new
+ assert_deprecated do
+ obj.save
+ assert_equal :second, obj.halted
+ assert !obj.saved
+ end
+ end
+ end
+
+ class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase
+ def setup
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = false
+ end
+
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_false
+ obj = CallbackFalseTerminator.new
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
+ end
+ end
+
class HyphenatedKeyTest < ActiveSupport::TestCase
def test_save
obj = HyphenatedCallbacks.new
diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
deleted file mode 100644
index e634679d20..0000000000
--- a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'abstract_unit'
-
-class BigDecimalYamlConversionsTest < ActiveSupport::TestCase
- def test_to_yaml
- assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' }
- assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml)
- assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml)
- assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml)
- assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml)
- end
-end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 6fcf6e8743..346dc3d208 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -71,14 +71,14 @@ class EnumerableTests < ActiveSupport::TestCase
def test_index_by
payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
- payments.index_by { |p| p.price })
+ payments.index_by(&:price))
assert_equal Enumerator, payments.index_by.class
if Enumerator.method_defined? :size
assert_equal nil, payments.index_by.size
assert_equal 42, (1..42).index_by.size
end
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
- payments.index_by.each { |p| p.price })
+ payments.index_by.each(&:price))
end
def test_many
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 5e9fdfd872..3d2f50ce49 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -365,7 +365,7 @@ class HashExtTest < ActiveSupport::TestCase
:member? => true }
hashes.each do |name, hash|
- method_map.sort_by { |m| m.to_s }.each do |meth, expected|
+ method_map.sort_by(&:to_s).each do |meth, expected|
assert_equal(expected, hash.__send__(meth, 'a'),
"Calling #{name}.#{meth} 'a'")
assert_equal(expected, hash.__send__(meth, :a),
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index a87af0007c..503e6595cb 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -15,7 +15,6 @@ class KernelTest < ActiveSupport::TestCase
assert_equal old_verbose, $VERBOSE
end
-
def test_enable_warnings
enable_warnings { assert_equal true, $VERBOSE }
assert_equal 1234, enable_warnings { 1234 }
@@ -29,57 +28,11 @@ class KernelTest < ActiveSupport::TestCase
assert_equal old_verbose, $VERBOSE
end
-
- def test_silence_stream
- old_stream_position = STDOUT.tell
- silence_stream(STDOUT) { STDOUT.puts 'hello world' }
- assert_equal old_stream_position, STDOUT.tell
- rescue Errno::ESPIPE
- # Skip if we can't stream.tell
- end
-
- def test_silence_stream_closes_file_descriptors
- stream = StringIO.new
- dup_stream = StringIO.new
- stream.stubs(:dup).returns(dup_stream)
- dup_stream.expects(:close)
- silence_stream(stream) { stream.puts 'hello world' }
- end
-
- def test_quietly
- old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell
- assert_deprecated do
- quietly do
- puts 'see me, feel me'
- STDERR.puts 'touch me, heal me'
- end
- end
- assert_equal old_stdout_position, STDOUT.tell
- assert_equal old_stderr_position, STDERR.tell
- rescue Errno::ESPIPE
- # Skip if we can't STDERR.tell
- end
-
def test_class_eval
o = Object.new
class << o; @x = 1; end
assert_equal 1, o.class_eval { @x }
end
-
- def test_capture
- assert_deprecated do
- assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' }
- end
- assert_deprecated do
- assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' }
- end
- assert_deprecated do
- assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') }
- end
- assert_deprecated do
- assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') }
- end
- end
end
class KernelSuppressTest < ActiveSupport::TestCase
@@ -112,27 +65,3 @@ class MockStdErr
puts(message)
end
end
-
-class KernelDebuggerTest < ActiveSupport::TestCase
- def test_debugger_not_available_message_to_stderr
- old_stderr = $stderr
- $stderr = MockStdErr.new
- debugger
- assert_match(/Debugger requested/, $stderr.output.first)
- ensure
- $stderr = old_stderr
- end
-
- def test_debugger_not_available_message_to_rails_logger
- rails = Class.new do
- def self.logger
- @logger ||= MockStdErr.new
- end
- end
- Object.const_set(:Rails, rails)
- debugger
- assert_match(/Debugger requested/, rails.logger.output.first)
- ensure
- Object.send(:remove_const, :Rails)
- end
-end if RUBY_VERSION < '2.0.0'
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 5f804c749b..b2a75a2bcc 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -1,26 +1,11 @@
require 'abstract_unit'
require 'active_support/core_ext/load_error'
-class TestMissingSourceFile < ActiveSupport::TestCase
- def test_with_require
- assert_raise(MissingSourceFile) { require 'no_this_file_don\'t_exist' }
- end
- def test_with_load
- assert_raise(MissingSourceFile) { load 'nor_does_this_one' }
- end
- def test_path
- begin load 'nor/this/one.rb'
- rescue MissingSourceFile => e
- assert_equal 'nor/this/one.rb', e.path
- end
- end
- def test_is_missing
- begin load 'nor_does_this_one'
- rescue MissingSourceFile => e
- assert e.is_missing?('nor_does_this_one')
- assert e.is_missing?('nor_does_this_one.rb')
- assert_not e.is_missing?('some_other_file')
+class TestMissingSourceFile < ActiveSupport::TestCase
+ def test_it_is_deprecated
+ assert_deprecated do
+ MissingSourceFile.new
end
end
end
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index 8cc39ae7b9..d37f4bd0d8 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -6,16 +6,7 @@ require 'active_support/core_ext/numeric/time'
class DuplicableTest < ActiveSupport::TestCase
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)]
ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
-
- # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
- # raises TypeError exception. Checking here on the runtime whether BigDecimal
- # will allow dup or not.
- begin
- bd = BigDecimal.new('4.56')
- ALLOW_DUP << bd.dup
- rescue TypeError
- RAISE_DUP << bd
- end
+ ALLOW_DUP << BigDecimal.new('4.56')
def test_duplicable
RAISE_DUP.each do |v|
diff --git a/activesupport/test/core_ext/object/itself_test.rb b/activesupport/test/core_ext/object/itself_test.rb
deleted file mode 100644
index 65db0ddf40..0000000000
--- a/activesupport/test/core_ext/object/itself_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/object'
-
-class Object::ItselfTest < ActiveSupport::TestCase
- test 'itself returns self' do
- object = 'fun'
- assert_equal object, object.itself
- end
-end
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index efc6beaf02..89438675c1 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -52,11 +52,11 @@ class ObjectTryTest < ActiveSupport::TestCase
end
def test_try_only_block
- assert_equal @string.reverse, @string.try { |s| s.reverse }
+ assert_equal @string.reverse, @string.try(&:reverse)
end
def test_try_only_block_bang
- assert_equal @string.reverse, @string.try! { |s| s.reverse }
+ assert_equal @string.reverse, @string.try!(&:reverse)
end
def test_try_only_block_nil
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index e32c178951..cdc695f036 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -281,15 +281,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_constantize
- run_constantize_tests_on do |string|
- string.constantize
- end
+ run_constantize_tests_on(&:constantize)
end
def test_safe_constantize
- run_safe_constantize_tests_on do |string|
- string.safe_constantize
- end
+ run_safe_constantize_tests_on(&:safe_constantize)
end
end
@@ -671,16 +667,6 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal other, "&lt;foo&gt;other"
end
- test "Deprecated #prepend! method is still present" do
- other = "other".html_safe
-
- assert_deprecated do
- other.prepend! "<foo>"
- end
-
- assert_equal other, "&lt;foo&gt;other"
- end
-
test "Concatting safe onto unsafe yields unsafe" do
@other_string = "other"
diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb
deleted file mode 100644
index 0dff7b32d2..0000000000
--- a/activesupport/test/core_ext/struct_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/struct'
-
-class StructExt < ActiveSupport::TestCase
- def test_to_h
- x = Struct.new(:foo, :bar)
- z = x.new(1, 2)
- assert_equal({ foo: 1, bar: 2 }, z.to_h)
- end
-end
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
deleted file mode 100644
index 6a7c6e0604..0000000000
--- a/activesupport/test/core_ext/thread_test.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/thread'
-
-class ThreadExt < ActiveSupport::TestCase
- def test_main_thread_variable_in_enumerator
- assert_equal Thread.main, Thread.current
-
- Thread.current.thread_variable_set :foo, "bar"
-
- thread, value = Fiber.new {
- Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
- }.resume
-
- assert_equal Thread.current, thread
- assert_equal Thread.current.thread_variable_get(:foo), value
- end
-
- def test_thread_variable_in_enumerator
- Thread.new {
- Thread.current.thread_variable_set :foo, "bar"
-
- thread, value = Fiber.new {
- Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
- }.resume
-
- assert_equal Thread.current, thread
- assert_equal Thread.current.thread_variable_get(:foo), value
- }.join
- end
-
- def test_thread_variables
- assert_equal [], Thread.new { Thread.current.thread_variables }.join.value
-
- t = Thread.new {
- Thread.current.thread_variable_set(:foo, "bar")
- Thread.current.thread_variables
- }
- assert_equal [:foo], t.join.value
- end
-
- def test_thread_variable?
- assert_not Thread.new { Thread.current.thread_variable?("foo") }.join.value
- t = Thread.new {
- Thread.current.thread_variable_set("foo", "bar")
- }.join
-
- assert t.thread_variable?("foo")
- assert t.thread_variable?(:foo)
- assert_not t.thread_variable?(:bar)
- end
-
- def test_thread_variable_strings_and_symbols_are_the_same_key
- t = Thread.new {}.join
- t.thread_variable_set("foo", "bar")
- assert_equal "bar", t.thread_variable_get(:foo)
- end
-
- def test_thread_variable_frozen
- t = Thread.new { }.join
- t.freeze
- assert_raises(RuntimeError) do
- t.thread_variable_set(:foo, "bar")
- end
- end
-
- def test_thread_variable_frozen_after_set
- t = Thread.new { }.join
- t.thread_variable_set :foo, "bar"
- t.freeze
- assert_raises(RuntimeError) do
- t.thread_variable_set(:baz, "qux")
- end
- end
-
-end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index ad4062e5fe..92c233d567 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -812,6 +812,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_no_method_error_has_proper_context
+ rubinius_skip "Error message inconsistency"
+
e = assert_raises(NoMethodError) {
@twz.this_method_does_not_exist
}
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 96e9bd1e65..702e26859a 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -28,8 +28,6 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_depend_on_path
- skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0'
-
expected = assert_raises(LoadError) do
Kernel.require 'omgwtfbbq'
end
@@ -72,7 +70,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_missing_dependency_raises_missing_source_file
- assert_raise(MissingSourceFile) { require_dependency("missing_service") }
+ assert_raise(LoadError) { require_dependency("missing_service") }
end
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
@@ -613,7 +611,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_nested_load_error_isnt_rescued
with_loading 'dependencies' do
- assert_raise(MissingSourceFile) do
+ assert_raise(LoadError) do
RequiresNonexistent1
end
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 7e976aa772..5c5045da1e 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -176,46 +176,6 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal "𐒑", decoded_hash['string']
end
- def test_reading_encode_big_decimal_as_string_option
- assert_deprecated do
- assert ActiveSupport.encode_big_decimal_as_string
- end
- end
-
- def test_setting_deprecated_encode_big_decimal_as_string_option
- assert_raise(NotImplementedError) do
- ActiveSupport.encode_big_decimal_as_string = true
- end
-
- assert_raise(NotImplementedError) do
- ActiveSupport.encode_big_decimal_as_string = false
- end
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_array
- a = [1]
- a << a
- assert_deprecated do
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_hash
- a = { :name => 'foo' }
- a[:next] = a
- assert_deprecated do
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array
- a = { :name => 'foo', :sub => [] }
- a[:sub] << a
- assert_deprecated do
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
- end
-
def test_hash_key_identifiers_are_always_quoted
values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index b6c0a08b05..eb71369397 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -1,12 +1,5 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::Digest::SHA1
-rescue LoadError, NameError
- $stderr.puts "Skipping MessageEncryptor test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/time'
require 'active_support/json'
@@ -97,5 +90,3 @@ class MessageEncryptorTest < ActiveSupport::TestCase
::Base64.strict_encode64(bits)
end
end
-
-end
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index 28035bc428..6c3519df9a 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -1,12 +1,5 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::Digest::SHA1
-rescue LoadError, NameError
- $stderr.puts "Skipping MessageVerifier test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/time'
require 'active_support/json'
@@ -27,21 +20,29 @@ class MessageVerifierTest < ActiveSupport::TestCase
@data = { :some => "data", :now => Time.local(2010) }
end
+ def test_valid_message
+ data, hash = @verifier.generate(@data).split("--")
+ assert !@verifier.valid_message?(nil)
+ assert !@verifier.valid_message?("")
+ assert !@verifier.valid_message?("#{data.reverse}--#{hash}")
+ assert !@verifier.valid_message?("#{data}--#{hash.reverse}")
+ assert !@verifier.valid_message?("purejunk")
+ end
+
def test_simple_round_tripping
message = @verifier.generate(@data)
+ assert_equal @data, @verifier.verified(message)
assert_equal @data, @verifier.verify(message)
end
- def test_missing_signature_raises
- assert_not_verified(nil)
- assert_not_verified("")
+ def test_verified_returns_false_on_invalid_message
+ assert !@verifier.verified("purejunk")
end
- def test_tampered_data_raises
- data, hash = @verifier.generate(@data).split("--")
- assert_not_verified("#{data.reverse}--#{hash}")
- assert_not_verified("#{data}--#{hash.reverse}")
- assert_not_verified("purejunk")
+ def test_verify_exception_on_invalid_message
+ assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
+ @verifier.verify("purejunk")
+ end
end
def test_alternative_serialization_method
@@ -50,6 +51,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new)
message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) })
exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" }
+ assert_equal exp, verifier.verified(message)
assert_equal exp, verifier.verify(message)
ensure
ActiveSupport.use_standard_json_time_format = prev
@@ -63,6 +65,11 @@ class MessageVerifierTest < ActiveSupport::TestCase
#
valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
exception = assert_raise(ArgumentError, NameError) do
+ @verifier.verified(valid_message)
+ end
+ assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
+ "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
+ exception = assert_raise(ArgumentError, NameError) do
@verifier.verify(valid_message)
end
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
@@ -75,12 +82,4 @@ class MessageVerifierTest < ActiveSupport::TestCase
end
assert_equal exception.message, 'Secret should not be nil.'
end
-
- def assert_not_verified(message)
- assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
- @verifier.verify(message)
- end
- end
-end
-
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index feca013675..94748dd991 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -182,7 +182,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_sortability
- words = %w(builder armor zebra).sort_by { |s| s.mb_chars }
+ words = %w(builder armor zebra).sort_by(&:mb_chars)
assert_equal %w(armor builder zebra), words
end
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index aba81b8248..f7bd21c8ab 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -115,7 +115,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
next if (line.empty? || line =~ /^\#/)
cols, comment = line.split("#")
- cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? }
+ cols = cols.split(";").map(&:strip).reject(&:empty?)
next unless cols.length == 5
# codepoints are in hex in the test suite, pack wants them as integers
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index ec9d231125..bd43ad0797 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -12,6 +12,12 @@ end
class CoolError < StandardError
end
+module WeirdError
+ def self.===(other)
+ Exception === other && other.respond_to?(:weird?)
+ end
+end
+
class Stargate
attr_accessor :result
@@ -29,6 +35,10 @@ class Stargate
@result = e.message
end
+ rescue_from WeirdError do
+ @result = 'weird'
+ end
+
def dispatch(method)
send(method)
rescue Exception => e
@@ -47,6 +57,16 @@ class Stargate
raise MadRonon.new("dex")
end
+ def weird
+ StandardError.new.tap do |exc|
+ def exc.weird?
+ true
+ end
+
+ raise exc
+ end
+ end
+
def sos
@result = 'killed'
end
@@ -91,15 +111,20 @@ class RescuableTest < ActiveSupport::TestCase
assert_equal 'dex', @stargate.result
end
+ def test_rescue_from_error_dispatchers_with_case_operator
+ @stargate.dispatch :weird
+ assert_equal 'weird', @stargate.result
+ end
+
def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array
- expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon"]
- result = @stargate.send(:rescue_handlers).collect {|e| e.first}
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError"]
+ result = @stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
def test_children_should_inherit_rescue_definitions_from_parents_and_child_rescue_should_be_appended
- expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"]
- result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first}
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError", "CoolError"]
+ result = @cool_stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index efa9d5e61f..4532152996 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -165,4 +165,9 @@ class SafeBufferTest < ActiveSupport::TestCase
x = 'foo %{x} bar'.html_safe % { x: 'qux' }
assert x.html_safe?, 'should be safe'
end
+
+ test 'Should not affect frozen objects when accessing characters' do
+ x = 'Hello'.html_safe
+ assert_equal x[/a/, 1], nil
+ end
end
diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb
index 27f629474e..03a63e94e8 100644
--- a/activesupport/test/tagged_logging_test.rb
+++ b/activesupport/test/tagged_logging_test.rb
@@ -79,6 +79,19 @@ class TaggedLoggingTest < ActiveSupport::TestCase
assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string
end
+ test "keeps each tag in their own instance" do
+ @other_output = StringIO.new
+ @other_logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@other_output))
+ @logger.tagged("OMG") do
+ @other_logger.tagged("BCX") do
+ @logger.info "Cool story bro"
+ @other_logger.info "Funky time"
+ end
+ end
+ assert_equal "[OMG] Cool story bro\n", @output.string
+ assert_equal "[BCX] Funky time\n", @other_output.string
+ end
+
test "cleans up the taggings on flush" do
@logger.tagged("BCX") do
Thread.new do
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 5e852c8050..151b623171 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -182,30 +182,28 @@ class TestOrderTest < ActiveSupport::TestCase
ActiveSupport::TestCase.test_order = @original_test_order
end
- def test_defaults_to_sorted_with_warning
+ def test_defaults_to_random
ActiveSupport::TestCase.test_order = nil
- assert_equal :sorted, assert_deprecated { ActiveSupport::TestCase.test_order }
+ assert_equal :random, ActiveSupport::TestCase.test_order
- # It should only produce a deprecation warning the first time this is accessed
- assert_equal :sorted, assert_not_deprecated { ActiveSupport::TestCase.test_order }
- assert_equal :sorted, assert_not_deprecated { ActiveSupport.test_order }
+ assert_equal :random, ActiveSupport.test_order
end
def test_test_order_is_global
- ActiveSupport::TestCase.test_order = :random
-
- assert_equal :random, ActiveSupport.test_order
- assert_equal :random, ActiveSupport::TestCase.test_order
- assert_equal :random, self.class.test_order
- assert_equal :random, Class.new(ActiveSupport::TestCase).test_order
-
- ActiveSupport.test_order = :sorted
+ ActiveSupport::TestCase.test_order = :sorted
assert_equal :sorted, ActiveSupport.test_order
assert_equal :sorted, ActiveSupport::TestCase.test_order
assert_equal :sorted, self.class.test_order
assert_equal :sorted, Class.new(ActiveSupport::TestCase).test_order
+
+ ActiveSupport.test_order = :random
+
+ assert_equal :random, ActiveSupport.test_order
+ assert_equal :random, ActiveSupport::TestCase.test_order
+ assert_equal :random, self.class.test_order
+ assert_equal :random, Class.new(ActiveSupport::TestCase).test_order
end
def test_i_suck_and_my_tests_are_order_dependent!
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 3e6d9652bb..cd7e184cda 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -395,15 +395,10 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] }
end
- def test_unknown_zone_should_have_tzinfo_but_exception_on_utc_offset
- zone = ActiveSupport::TimeZone.create("bogus")
- assert_instance_of TZInfo::TimezoneProxy, zone.tzinfo
- assert_raise(TZInfo::InvalidTimezoneIdentifier) { zone.utc_offset }
- end
-
- def test_unknown_zone_with_utc_offset
- zone = ActiveSupport::TimeZone.create("bogus", -21_600)
- assert_equal(-21_600, zone.utc_offset)
+ def test_unknown_zone_raises_exception
+ assert_raise TZInfo::InvalidTimezoneIdentifier do
+ ActiveSupport::TimeZone.create("bogus")
+ end
end
def test_unknown_zones_dont_store_mapping_keys
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 2e962576b5..1314c9065a 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -8,15 +8,13 @@ require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
class NokogiriEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = 'Nokogiri'
+ @default_backend = ActiveSupport::XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'Nokogiri'
end
def teardown
- XmlMini.backend = @default_backend
+ ActiveSupport::XmlMini.backend = @default_backend
end
def test_file_from_xml
@@ -56,13 +54,13 @@ class NokogiriEngineTest < ActiveSupport::TestCase
end
def test_setting_nokogiri_as_backend
- XmlMini.backend = 'Nokogiri'
- assert_equal XmlMini_Nokogiri, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'Nokogiri'
+ assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend
end
def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(''))
+ assert_equal({}, ActiveSupport::XmlMini.parse(nil))
+ assert_equal({}, ActiveSupport::XmlMini.parse(''))
end
def test_array_type_makes_an_array
@@ -207,9 +205,9 @@ class NokogiriEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 4f078f31e0..7978a50921 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -8,15 +8,13 @@ require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
class NokogiriSAXEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = 'NokogiriSAX'
+ @default_backend = ActiveSupport::XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'NokogiriSAX'
end
def teardown
- XmlMini.backend = @default_backend
+ ActiveSupport::XmlMini.backend = @default_backend
end
def test_file_from_xml
@@ -57,13 +55,13 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
end
def test_setting_nokogirisax_as_backend
- XmlMini.backend = 'NokogiriSAX'
- assert_equal XmlMini_NokogiriSAX, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'NokogiriSAX'
+ assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend
end
def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(''))
+ assert_equal({}, ActiveSupport::XmlMini.parse(nil))
+ assert_equal({}, ActiveSupport::XmlMini.parse(''))
end
def test_array_type_makes_an_array
@@ -208,9 +206,9 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index 0c1f11803c..f0067ca656 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -2,19 +2,17 @@ require 'abstract_unit'
require 'active_support/xml_mini'
class REXMLEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def test_default_is_rexml
- assert_equal XmlMini_REXML, XmlMini.backend
+ assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
end
def test_set_rexml_as_backend
- XmlMini.backend = 'REXML'
- assert_equal XmlMini_REXML, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'REXML'
+ assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
end
def test_parse_from_io
- XmlMini.backend = 'REXML'
+ ActiveSupport::XmlMini.backend = 'REXML'
io = StringIO.new(<<-eoxml)
<root>
good
@@ -29,9 +27,9 @@ class REXMLEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/ci/travis.rb b/ci/travis.rb
index a1920a7a1f..b62f90a59e 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -140,6 +140,6 @@ if failures.empty?
else
puts
puts "Rails build FAILED"
- puts "Failed components: #{failures.map { |component| component.first }.join(', ')}"
+ puts "Failed components: #{failures.map(&:first).join(', ')}"
exit(false)
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 2770fc73e7..cd3fa9f65b 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,27 +1,5 @@
-* Change Posts to Articles in Getting Started sample application in order to
-better align with the actual guides.
+* New section in Active Record Querying: Understanding The Method Chaining
- *John Kelly Ferguson*
+ *Andrey Nering*
-* Update all Rails 4.1.0 references to 4.1.1 within the guides and code.
-
- *John Kelly Ferguson*
-
-* Split up rows in the Explain Queries table of the ActiveRecord Querying section
-in order to improve readability.
-
- *John Kelly Ferguson*
-
-* Change all non-HTTP method 'post' references to 'article'.
-
- *John Kelly Ferguson*
-
-* Updates the maintenance policy to match the latest versions of Rails
-
- *Matias Korhonen*
-
-* Switched the order of `Applying a default scope` and `Merging of scopes` subsections so default scopes are introduced first.
-
- *Alex Riabov*
-
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/guides/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/Rakefile b/guides/Rakefile
index 94d4be8c0a..fd093b94c1 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -34,11 +34,13 @@ namespace :guides do
task :help do
puts <<-help
-Guides are taken from the source directory, and the resulting HTML goes into the
+Guides are taken from the source directory, and the result goes into the
output directory. Assets are stored under files, and copied to output/files as
part of the generation process.
-All this process is handled via rake tasks, here's a full list of them:
+You can generate HTML, Kindle or both formats using the `guides:generate` task.
+
+All of these processes are handled via rake tasks, here's a full list of them:
#{%x[rake -T]}
Some arguments may be passed via environment variables:
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 318a1ef1c7..ed558e4793 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -34,7 +34,7 @@ pre, code {
overflow: auto;
color: #222;
}
-pre,tt,code,.note>p {
+pre, tt, code {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 1f0cec1e22..e04d79c818 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -1,5 +1,5 @@
# Activate the gem you are reporting the issue against.
-gem 'rails', '4.0.0'
+gem 'rails', '4.2.0'
require 'rails'
require 'action_controller/railtie'
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 0e51eaa0db..9be8130884 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -3,8 +3,6 @@ unless File.exist?('Gemfile')
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
- gem 'rack', github: 'rack/rack'
- gem 'i18n', github: 'svenfuchs/i18n'
GEMFILE
system 'bundle'
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index d72633d0b2..66bbb15afb 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -1,5 +1,5 @@
# Activate the gem you are reporting the issue against.
-gem 'activerecord', '4.0.0'
+gem 'activerecord', '4.2.0'
require 'active_record'
require 'minitest/autorun'
require 'logger'
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index e7f5d0d5ff..d95354e12d 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -3,8 +3,6 @@ unless File.exist?('Gemfile')
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
- gem 'rack', github: 'rack/rack'
- gem 'i18n', github: 'svenfuchs/i18n'
gem 'sqlite3'
GEMFILE
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index 9d1d5567f6..762ab1c0e2 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -24,11 +24,11 @@ begin
require 'redcarpet'
rescue LoadError
# This can happen if doc:guides is executed in an application.
- $stderr.puts('Generating guides requires Redcarpet 3.1.2+.')
+ $stderr.puts('Generating guides requires Redcarpet 3.2.2+.')
$stderr.puts(<<ERROR) if bundler?
Please add
- gem 'redcarpet', '~> 3.1.2'
+ gem 'redcarpet', '~> 3.2.2'
to the Gemfile, run
diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb
index 09eecd5634..32926622e3 100644
--- a/guides/rails_guides/kindle.rb
+++ b/guides/rails_guides/kindle.rb
@@ -70,7 +70,7 @@ module Kindle
File.open("sections/%03d/_section.txt" % section_idx, 'w') {|f| f.puts title}
doc.xpath("//h3[@id]").each_with_index do |h3,item_idx|
subsection = h3.inner_text
- content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map {|x| x.to_html}
+ content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map(&:to_html)
item = Nokogiri::HTML(h3.to_html + content.join("\n"))
item_path = "sections/%03d/%03d.html" % [section_idx, item_idx]
add_head_section(item, subsection)
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 688f177578..50a791cda5 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -23,8 +23,9 @@ HTML
end
def paragraph(text)
- if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)/
+ if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/
convert_notes(text)
+ elsif text.include?('DO NOT READ THIS FILE IN GITHUB')
elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/
linkback = %(<a href="#footnote-#{$1}-ref"><sup>#{$1}</sup></a>)
%(<p class="footnote" id="footnote-#{$1}">#{linkback} #{$2}</p>)
@@ -47,7 +48,7 @@ HTML
case code_type
when 'ruby', 'sql', 'plain'
code_type
- when 'erb'
+ when 'erb', 'html+erb'
'ruby; html-script: true'
when 'html'
'xml' # HTML is understood, but there are .xml rules in the CSS
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index b11aaa15a8..019da08687 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 2.2 Release Notes
===============================
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 20566c9155..4ac1529e76 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 2.3 Release Notes
===============================
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 2630207c0f..3d7966e50b 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 3.0 Release Notes
===============================
@@ -138,7 +140,7 @@ More Information: - [Rails Edge Architecture](http://yehudakatz.com/2009/06/11/r
[Arel](http://github.com/brynary/arel) (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record.
-More information: - [Why I wrote Arel](http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/.)
+More information: - [Why I wrote Arel](https://web.archive.org/web/20120718093140/http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/)
### Mail Extraction
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index b7ed285b96..8728750966 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 3.1 Release Notes
===============================
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index c5db0262e9..0b28aac9ce 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 3.2 Release Notes
===============================
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 84a65df2bc..5f52c33746 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 4.0 Release Notes
===============================
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index c7877a9cb5..dbc151c0ca 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 4.1 Release Notes
===============================
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index 8553cffa9d..faff1add9f 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -1,23 +1,23 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails 4.2 Release Notes
===============================
Highlights in Rails 4.2:
-* Active Job, Action Mailer #deliver_later
+* Active Job
+* Asynchronous mails
* Adequate Record
* Web Console
* Foreign key support
-These release notes cover only the major changes. To learn about various bug
-fixes and changes, please refer to the change logs or check out the [list of
-commits](https://github.com/rails/rails/commits/master) in the main Rails
-repository on GitHub.
+These release notes cover only the major changes. To learn about other
+features, bug fixes, and changes, please refer to the changelogs or check out
+the [list of commits](https://github.com/rails/rails/commits/4-2-stable) in
+the main Rails repository on GitHub.
--------------------------------------------------------------------------------
-NOTE: This document is a work in progress, please help to improve this by sending
-a [pull request](https://github.com/rails/rails/edit/master/guides/source/4_2_release_notes.md).
-
Upgrading to Rails 4.2
----------------------
@@ -25,96 +25,115 @@ If you're upgrading an existing application, it's a great idea to have good test
coverage before going in. You should also first upgrade to Rails 4.1 in case you
haven't and make sure your application still runs as expected before attempting
to upgrade to Rails 4.2. A list of things to watch out for when upgrading is
-available in the guide: [Upgrading Ruby on
-Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2)
+available in the guide [Upgrading Ruby on
+Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2).
Major Features
--------------
-### Active Job, Action Mailer #deliver_later
+### Active Job
-Active Job is a new framework in Rails 4.2. It is an adapter layer on top of
+Active Job is a new framework in Rails 4.2. It is a common interface on top of
queuing systems like [Resque](https://github.com/resque/resque), [Delayed
Job](https://github.com/collectiveidea/delayed_job),
[Sidekiq](https://github.com/mperham/sidekiq), and more.
-You can write your jobs with the Active Job API, and it'll run on all these
-queues with no changes (it comes pre-configured with an inline runner).
+Jobs written with the Active Job API run on any of the supported queues thanks
+to their respective adapters. Active Job comes pre-configured with an inline
+runner that executes jobs right away.
+
+Jobs often need to take Active Record objects as arguments. Active Job passes
+object references as URIs (uniform resource identifiers) instead of marshaling
+the object itself. The new [Global ID](https://github.com/rails/globalid)
+library builds URIs and looks up the objects they reference. Passing Active
+Record objects as job arguments just works by using Global ID internally.
+
+For example, if `trashable` is an Active Record object, then this job runs
+just fine with no serialization involved:
+
+```ruby
+class TrashableCleanupJob < ActiveJob::Base
+ def perform(trashable, depth)
+ trashable.cleanup(depth)
+ end
+end
+```
+
+See the [Active Job Basics](active_job_basics.html) guide for more
+information.
+
+### Asynchronous Mails
-Building on top of Active Job, Action Mailer now comes with a `#deliver_later`
-method, which adds your email to be sent as a job to a queue, so it doesn't
-bog down the controller or model.
+Building on top of Active Job, Action Mailer now comes with a `deliver_later`
+method that sends emails via the queue, so it doesn't block the controller or
+model if the queue is asynchronous (the default inline queue blocks).
-The new GlobalID library makes it easy to pass Active Record objects to jobs by
-serializing them in a generic form. This means you no longer have to manually
-pack and unpack your Active Records by passing ids. Just give the job the
-Active Record object, and it'll serialize it using GlobalID, and deserialize
-it at run time.
+Sending emails right away is still possible with `deliver_now`.
### Adequate Record
-Adequate Record is a set of refactorings that make Active Record `find` and
-`find_by` methods and some association queries up to 2x faster.
+Adequate Record is a set of performance improvements in Active Record that makes
+common `find` and `find_by` calls and some association queries up to 2x faster.
-It works by caching SQL query patterns while executing the Active Record calls.
-The cache helps skip parts of the computation involved in the transformation of
-the calls into SQL queries. More details in [Aaron Patterson's
+It works by caching common SQL queries as prepared statements and reusing them
+on similar calls, skipping most of the query-generation work on subsequent
+calls. For more details, please refer to [Aaron Patterson's blog
post](http://tenderlovemaking.com/2014/02/19/adequaterecord-pro-like-activerecord.html).
-Nothing special has to be done to activate this feature. Most `find` and
-`find_by` calls and association queries will use it automatically. Examples:
+Active Record will automatically take advantage of this feature on
+supported operations without any user involvement or code changes. Here are
+some examples of supported operations:
```ruby
-Post.find 1 # caches query pattern
-Post.find 2 # uses the cached pattern
+Post.find(1) # First call generates and cache the prepared statement
+Post.find(2) # Subsequent calls reuse the cached prepared statement
-Post.find_by_title 'first post' # caches query pattern
-Post.find_by_title 'second post' # uses the cached pattern
+Post.find_by_title('first post')
+Post.find_by_title('second post')
-post.comments # caches query pattern
-post.comments(true) # uses cached pattern
+Post.find_by(title: 'first post')
+Post.find_by(title: 'second post')
+
+post.comments
+post.comments(true)
```
-The caching is not used in the following scenarios:
+It's important to highlight that, as the examples above suggest, the prepared
+statements do not cache the values passed in the method calls; rather, they
+have placeholders for them.
+
+Caching is not used in the following scenarios:
- The model has a default scope
-- The model uses single table inheritance to inherit from another model
-- `find` with a list of ids. eg:
+- The model uses single table inheritance
+- `find` with a list of ids, e.g.:
```ruby
- Post.find(1,2,3)
- OR
- Post.find [1,2]
+ # not cached
+ Post.find(1, 2, 3)
+ Post.find([1,2])
```
- `find_by` with SQL fragments:
```ruby
- Post.find_by "published_at < ?", 2.weeks.ago
+ Post.find_by('published_at < ?', 2.weeks.ago)
```
### Web Console
-New applications generated from Rails 4.2 now come with the Web Console gem by
-default.
-
-Web Console is a set of debugging tools for your Rails application. It will add
-an interactive console on every error page, a `console` view helper and a VT100
-compatible terminal.
-
-The interactive console on the error pages lets you execute code where the
-exception originated. It's quite handy to introspect the state that led to the
-error.
-
-The `console` view helper launches an interactive console within the context of
-the view where it is invoked.
+New applications generated with Rails 4.2 now come with the [Web
+Console](https://github.com/rails/web-console) gem by default. Web Console adds
+an interactive Ruby console on every error page and provides a `console` view
+and controller helpers.
-Finally, you can launch a VT100 terminal that runs `rails console`. If you need
-to create or modify existing test data, you can do that straight from the
-browser.
+The interactive console on error pages lets you execute code in the context of
+the place where the exception originated. The `console` helper, if called
+anywhere in a view or controller, launches an interactive console with the final
+context, once rendering has completed.
-### Foreign key support
+### Foreign Key Support
The migration DSL now supports adding and removing foreign keys. They are dumped
to `schema.rb` as well. At this time, only the `mysql`, `mysql2` and `postgresql`
@@ -149,18 +168,18 @@ individual components for new deprecations in this release.
The following changes may require immediate action upon upgrade.
-### `render` with a String argument
+### `render` with a String Argument
-Previously, calling `render "foo/bar"` in a controller action is equivalent to
-`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean `render template: "foo/bar"`
-instead. If you need to render a file, please change your code to use the
-explicit form (`render file: "foo/bar"`) instead.
+Previously, calling `render "foo/bar"` in a controller action was equivalent to
+`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean
+`render template: "foo/bar"` instead. If you need to render a file, please
+change your code to use the explicit form (`render file: "foo/bar"`) instead.
-### `respond_with` / class-level `respond_to`
+### `respond_with` / Class-Level `respond_to`
-`respond_with` and the corresponding class-level `respond_to` have been moved to
-the `responders` gem. To use the following, add `gem 'responders', '~> 2.0'` to
-your Gemfile:
+`respond_with` and the corresponding class-level `respond_to` have been moved
+to the [responders](https://github.com/plataformatec/responders) gem. Add
+`gem 'responders', '~> 2.0'` to your Gemfile to use it:
```ruby
# app/controllers/users_controller.rb
@@ -191,61 +210,47 @@ class UsersController < ApplicationController
end
```
-### Default host for `rails server`
+### Default Host for `rails server`
Due to a [change in Rack](https://github.com/rack/rack/commit/28b014484a8ac0bbb388e7eaeeef159598ec64fc),
`rails server` now listens on `localhost` instead of `0.0.0.0` by default. This
-should have minimal impact on the standard development workflow as both http://127.0.0.1:3000
-and http://localhost:3000 will continue to work as before on your own machine.
+should have minimal impact on the standard development workflow as both
+http://127.0.0.1:3000 and http://localhost:3000 will continue to work as before
+on your own machine.
-However, with this change you will no longer be able to access the Rails server
-from a different machine (e.g. your development environment is in a virtual
-machine and you would like to access it from the host machine), you would need
-to start the server with `rails server -b 0.0.0.0` to restore the old behavior.
+However, with this change you will no longer be able to access the Rails
+server from a different machine, for example if your development environment
+is in a virtual machine and you would like to access it from the host machine.
+In such cases, please start the server with `rails server -b 0.0.0.0` to
+restore the old behavior.
If you do this, be sure to configure your firewall properly such that only
trusted machines on your network can access your development server.
-### Production logging
-
-The default log level in the `production` environment is now `:debug`. This
-makes it consistent with the other environments, and ensures plenty of
-information is available to diagnose problems.
-
-It can be returned to the previous level, `:info`, in the environment
-configuration:
-
-```ruby
-# config/environments/production.rb
-
-# Decrease the log volume.
-config.log_level = :info
-```
-
### HTML Sanitizer
The HTML sanitizer has been replaced with a new, more robust, implementation
-built upon Loofah and Nokogiri. The new sanitizer is more secure and its
-sanitization is more powerful and flexible.
+built upon [Loofah](https://github.com/flavorjones/loofah) and
+[Nokogiri](https://github.com/sparklemotion/nokogiri). The new sanitizer is
+more secure and its sanitization is more powerful and flexible.
-With a new sanitization algorithm, the sanitized output will change for certain
+Due to the new algorithm, the sanitized output may be different for certain
pathological inputs.
-If you have particular need for the exact output of the old sanitizer, you can
-add `rails-deprecated_sanitizer` to your Gemfile, and it will automatically
-replace the new implementation. Because it is opt-in, the legacy gem will not
-give deprecation warnings.
+If you have a particular need for the exact output of the old sanitizer, you
+can add the [rails-deprecated_sanitizer](https://github.com/kaspth/rails-deprecated_sanitizer)
+gem to the `Gemfile`, to have the old behavior. The gem does not issue
+deprecation warnings because it is opt-in.
`rails-deprecated_sanitizer` will be supported for Rails 4.2 only; it will not
be maintained for Rails 5.0.
-See [the blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/)
-for more detail on the changes in the new sanitizer.
+See [this blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/)
+for more details on the changes in the new sanitizer.
### `assert_select`
-`assert_select` is now based on Nokogiri, making it (TODO: betterer).
-
+`assert_select` is now based on [Nokogiri](https://github.com/sparklemotion/nokogiri).
As a result, some previously-valid selectors are now unsupported. If your
application is using any of these spellings, you will need to update them:
@@ -253,8 +258,13 @@ application is using any of these spellings, you will need to update them:
non-alphanumeric characters.
```
- a[href=/] => a[href="/"]
- a[href$=/] => a[href$="/"]
+ # before
+ a[href=/]
+ a[href$=/]
+
+ # now
+ a[href="/"]
+ a[href$="/"]
```
* DOMs built from HTML source containing invalid HTML with improperly
@@ -308,6 +318,16 @@ Please refer to the [Changelog][railties] for detailed changes.
### Deprecations
+* Deprecated missing `config.log_level` for production environments.
+ ([Pull Request](https://github.com/rails/rails/pull/16622))
+
+* Deprecated `rake test:all` in favor of `rake test` as it now run all tests
+ in the `test` folder.
+ ([Pull Request](https://github.com/rails/rails/pull/17348))
+
+* Deprecated `rake test:all:db` in favor of `rake test:db`.
+ ([Pull Request](https://github.com/rails/rails/pull/17348))
+
* Deprecated `Rails::Rack::LogTailer` without replacement.
([Commit](https://github.com/rails/rails/commit/84a13e019e93efaa8994b3f8303d635a7702dbce))
@@ -319,9 +339,6 @@ Please refer to the [Changelog][railties] for detailed changes.
* Added a `required` option to the model generator for associations.
([Pull Request](https://github.com/rails/rails/pull/16062))
-* Introduced an `after_bundle` callback for use in Rails templates.
- ([Pull Request](https://github.com/rails/rails/pull/16359))
-
* Introduced the `x` namespace for defining custom configuration options:
```ruby
@@ -361,20 +378,23 @@ Please refer to the [Changelog][railties] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/16129))
-* Introduce a `--skip-turbolinks` option in the app generator to not generate
- any turbolinks integration.
+* Introduced a `--skip-turbolinks` option in the app generator to not generate
+ turbolinks integration.
([Commit](https://github.com/rails/rails/commit/bf17c8a531bc8059d50ad731398002a3e7162a7d))
-* Introduced a `bin/setup` script to enable automated setup code when
+* Introduced a `bin/setup` script as a convention for automated setup code when
bootstrapping an application.
([Pull Request](https://github.com/rails/rails/pull/15189))
-* Changed default value for `config.assets.digest` to `true` in development.
+* Changed the default value for `config.assets.digest` to `true` in development.
([Pull Request](https://github.com/rails/rails/pull/15155))
* Introduced an API to register new extensions for `rake notes`.
([Pull Request](https://github.com/rails/rails/pull/14379))
+* Introduced an `after_bundle` callback for use in Rails templates.
+ ([Pull Request](https://github.com/rails/rails/pull/16359))
+
* Introduced `Rails.gem_version` as a convenience method to return
`Gem::Version.new(Rails.version)`.
([Pull Request](https://github.com/rails/rails/pull/14101))
@@ -387,10 +407,11 @@ Please refer to the [Changelog][action-pack] for detailed changes.
### Removals
-* `respond_with` and the class-level `respond_to` were removed from Rails and
+* `respond_with` and the class-level `respond_to` have been removed from Rails and
moved to the `responders` gem (version 2.0). Add `gem 'responders', '~> 2.0'`
to your `Gemfile` to continue using these features.
- ([Pull Request](https://github.com/rails/rails/pull/16526))
+ ([Pull Request](https://github.com/rails/rails/pull/16526),
+ [More Details](http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#responders))
* Removed deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError`
in favor of `AbstractController::Helpers::MissingHelperError`.
@@ -406,7 +427,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
([Commit](https://github.com/rails/rails-dom-testing/commit/b12850bc5ff23ba4b599bf2770874dd4f11bf750))
* Deprecated support for setting the `:to` option of a router to a symbol or a
- string that does not contain a `#` character:
+ string that does not contain a "#" character:
```ruby
get '/posts', to: MyRackApp => (No change necessary)
@@ -417,22 +438,22 @@ Please refer to the [Changelog][action-pack] for detailed changes.
([Commit](https://github.com/rails/rails/commit/cc26b6b7bccf0eea2e2c1a9ebdcc9d30ca7390d9))
-### Notable changes
+* Deprecated support for string keys in URL helpers:
-* Rails will now automatically include the template's digest in ETags.
- ([Pull Request](https://github.com/rails/rails/pull/16527))
+ ```ruby
+ # bad
+ root_path('controller' => 'posts', 'action' => 'index')
-* `render nothing: true` or rendering a `nil` body no longer add a single
- space padding to the response body.
- ([Pull Request](https://github.com/rails/rails/pull/14883))
+ # good
+ root_path(controller: 'posts', action: 'index')
+ ```
-* Introduced the `always_permitted_parameters` option to configure which
- parameters are permitted globally. The default value of this configuration
- is `['controller', 'action']`.
- ([Pull Request](https://github.com/rails/rails/pull/15933))
+ ([Pull Request](https://github.com/rails/rails/pull/17743))
-* The `*_filter` family methods have been removed from the documentation. Their
- usage is discouraged in favor of the `*_action` family methods:
+### Notable changes
+
+* The `*_filter` family of methods have been removed from the documentation. Their
+ usage is discouraged in favor of the `*_action` family of methods:
```
after_filter => after_action
@@ -457,16 +478,28 @@ Please refer to the [Changelog][action-pack] for detailed changes.
(Commit [1](https://github.com/rails/rails/commit/6c5f43bab8206747a8591435b2aa0ff7051ad3de),
[2](https://github.com/rails/rails/commit/489a8f2a44dc9cea09154ee1ee2557d1f037c7d4))
-* Added HTTP method `MKCALENDAR` from RFC-4791
+* `render nothing: true` or rendering a `nil` body no longer add a single
+ space padding to the response body.
+ ([Pull Request](https://github.com/rails/rails/pull/14883))
+
+* Rails now automatically includes the template's digest in ETags.
+ ([Pull Request](https://github.com/rails/rails/pull/16527))
+
+* Segments that are passed into URL helpers are now automatically escaped.
+ ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f))
+
+* Introduced the `always_permitted_parameters` option to configure which
+ parameters are permitted globally. The default value of this configuration
+ is `['controller', 'action']`.
+ ([Pull Request](https://github.com/rails/rails/pull/15933))
+
+* Added the HTTP method `MKCALENDAR` from [RFC 4791](https://tools.ietf.org/html/rfc4791).
([Pull Request](https://github.com/rails/rails/pull/15121))
* `*_fragment.action_controller` notifications now include the controller
and action name in the payload.
([Pull Request](https://github.com/rails/rails/pull/14137))
-* Segments that are passed into URL helpers are now automatically escaped.
- ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f))
-
* Improved the Routing Error page with fuzzy matching for route search.
([Pull Request](https://github.com/rails/rails/pull/14619))
@@ -481,21 +514,19 @@ Please refer to the [Changelog][action-pack] for detailed changes.
serving assets from your Rails server in production.
([Pull Request](https://github.com/rails/rails/pull/16466))
-* The way `assert_select` works has changed; specifically a different library
- is used to interpret CSS selectors, build the transient DOM that the
- selectors are applied against, and to extract the data from that DOM. These
- changes should only affect edge cases. Examples:
- * Values in attribute selectors may need to be quoted if they contain
- non-alphanumeric characters.
- * DOMs built from HTML source containing invalid HTML with improperly
- nested elements may differ.
- * If the data selected contains entities, the value selected for comparison
- used to be raw (e.g. `AT&amp;T`), and now is evaluated
- (e.g. `AT&T`).
+* When calling the `process` helpers in an integration test the path needs to have
+ a leading slash. Previously you could omit it but that was a byproduct of the
+ implementation and not an intentional feature, e.g.:
+ ```ruby
+ test "list all posts" do
+ get "/posts"
+ assert_response :success
+ end
+ ```
Action View
--------------
+-----------
Please refer to the [Changelog][action-view] for detailed changes.
@@ -516,16 +547,16 @@ Please refer to the [Changelog][action-view] for detailed changes.
`render file: "foo/bar"`.
([Pull Request](https://github.com/rails/rails/pull/16888))
-* Introduced a `#{partial_name}_iteration` special local variable for use with
- partials that are rendered with a collection. It provides access to the
- current state of the iteration via the `#index`, `#size`, `#first?` and
- `#last?` methods.
- ([Pull Request](https://github.com/rails/rails/pull/7698))
-
* The form helpers no longer generate a `<div>` element with inline CSS around
the hidden fields.
([Pull Request](https://github.com/rails/rails/pull/14738))
+* Introduced a `#{partial_name}_iteration` special local variable for use with
+ partials that are rendered with a collection. It provides access to the
+ current state of the iteration via the `index`, `size`, `first?` and
+ `last?` methods.
+ ([Pull Request](https://github.com/rails/rails/pull/7698))
+
* Placeholder I18n follows the same convention as `label` I18n.
([Pull Request](https://github.com/rails/rails/pull/16438))
@@ -545,6 +576,10 @@ Please refer to the [Changelog][action-mailer] for detailed changes.
### Notable changes
+* `link_to` and `url_for` generate absolute URLs by default in templates,
+ it is no longer needed to pass `only_path: false`.
+ ([Commit](https://github.com/rails/rails/commit/9685080a7677abfa5d288a81c3e078368c6bb67c))
+
* Introduced `deliver_later` which enqueues a job on the application's queue
to deliver emails asynchronously.
([Pull Request](https://github.com/rails/rails/pull/16485))
@@ -573,32 +608,51 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Removed unused `:timestamp` type. Transparently alias it to `:datetime`
in all cases. Fixes inconsistencies when column types are sent outside of
- `ActiveRecord`, such as for XML serialization.
+ Active Record, such as for XML serialization.
([Pull Request](https://github.com/rails/rails/pull/15184))
### Deprecations
-* Deprecated `sanitize_sql_hash_for_conditions` without replacement. Using a
- `Relation` for performing queries and updates is the prefered API.
- ([Commit](https://github.com/rails/rails/commit/d5902c9e))
-
* Deprecated swallowing of errors inside `after_commit` and `after_rollback`.
([Pull Request](https://github.com/rails/rails/pull/16537))
+* Deprecated broken support for automatic detection of counter caches on
+ `has_many :through` associations. You should instead manually specify the
+ counter cache on the `has_many` and `belongs_to` associations for the
+ through records.
+ ([Pull Request](https://github.com/rails/rails/pull/15754))
+
+* Deprecated passing Active Record objects to `.find` or `.exists?`. Call
+ `id` on the objects first.
+ (Commit [1](https://github.com/rails/rails/commit/d92ae6ccca3bcfd73546d612efaea011270bd270),
+ [2](https://github.com/rails/rails/commit/d35f0033c7dec2b8d8b52058fb8db495d49596f7))
+
+* Deprecated half-baked support for PostgreSQL range values with excluding
+ beginnings. We currently map PostgreSQL ranges to Ruby ranges. This conversion
+ is not fully possible because Ruby ranges do not support excluded beginnings.
+
+ The current solution of incrementing the beginning is not correct
+ and is now deprecated. For subtypes where we don't know how to increment
+ (e.g. `succ` is not defined) it will raise an `ArgumentError` for ranges
+ with excluding beginnings.
+ ([Commit](https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3))
+
* Deprecated calling `DatabaseTasks.load_schema` without a connection. Use
`DatabaseTasks.load_schema_current` instead.
([Commit](https://github.com/rails/rails/commit/f15cef67f75e4b52fd45655d7c6ab6b35623c608))
+* Deprecated `sanitize_sql_hash_for_conditions` without replacement. Using a
+ `Relation` for performing queries and updates is the preferred API.
+ ([Commit](https://github.com/rails/rails/commit/d5902c9e))
+
+* Deprecated `add_timestamps` and `t.timestamps` without passing the `:null`
+ option. The default of `null: true` will change in Rails 5 to `null: false`.
+ ([Pull Request](https://github.com/rails/rails/pull/16481))
+
* Deprecated `Reflection#source_macro` without replacement as it is no longer
needed in Active Record.
([Pull Request](https://github.com/rails/rails/pull/16373))
-* Deprecated broken support for automatic detection of counter caches on
- `has_many :through` associations. You should instead manually specify the
- counter cache on the `has_many` and `belongs_to` associations for the
- through records.
- ([Pull Request](https://github.com/rails/rails/pull/15754))
-
* Deprecated `serialized_attributes` without replacement.
([Pull Request](https://github.com/rails/rails/pull/15704))
@@ -607,90 +661,78 @@ Please refer to the [Changelog][active-record] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/15878))
* Deprecated using `.joins`, `.preload` and `.eager_load` with associations
- that depends on the instance state (i.e. those defined with a scope that
+ that depend on the instance state (i.e. those defined with a scope that
takes an argument) without replacement.
([Commit](https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1))
-* Deprecated passing Active Record objects to `.find` or `.exists?`. Call
- `#id` on the objects first.
- (Commit [1](https://github.com/rails/rails/commit/d92ae6ccca3bcfd73546d612efaea011270bd270),
- [2](https://github.com/rails/rails/commit/d35f0033c7dec2b8d8b52058fb8db495d49596f7))
-
-* Deprecated half-baked support for PostgreSQL range values with excluding
- beginnings. We currently map PostgreSQL ranges to Ruby ranges. This conversion
- is not fully possible because the Ruby range does not support excluded
- beginnings.
-
- The current solution of incrementing the beginning is not correct
- and is now deprecated. For subtypes where we don't know how to increment
- (e.g. `#succ` is not defined) it will raise an `ArgumentError` for ranges
- with excluding beginnings.
-
- ([Commit](https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3))
-
### Notable changes
-* The PostgreSQL adapter now supports the `JSONB` datatype in PostgreSQL 9.4+.
- ([Pull Request](https://github.com/rails/rails/pull/16220))
-
-* The `#references` method in migrations now supports a `type` option for
- specifying the type of the foreign key (e.g. `:uuid`).
- ([Pull Request](https://github.com/rails/rails/pull/16231))
+* `SchemaDumper` uses `force: :cascade` on `create_table`. This makes it
+ possible to reload a schema when foreign keys are in place.
* Added a `:required` option to singular associations, which defines a
presence validation on the association.
([Pull Request](https://github.com/rails/rails/pull/16056))
-* Introduced `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the
- record is invalid.
- ([Pull Request](https://github.com/rails/rails/pull/8639))
-
-* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`,
- meaning that it no longer retains the extra attributes from custom
- `select`s.
- ([Pull Request](https://github.com/rails/rails/pull/15866))
-
-* Introduced the `bin/rake db:purge` task to empty the database for the
- current environment.
- ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d))
-
* `ActiveRecord::Dirty` now detects in-place changes to mutable values.
- Serialized attributes on Active Record models will no longer save when
+ Serialized attributes on Active Record models are no longer saved when
unchanged. This also works with other types such as string columns and json
columns on PostgreSQL.
(Pull Requests [1](https://github.com/rails/rails/pull/15674),
[2](https://github.com/rails/rails/pull/15786),
[3](https://github.com/rails/rails/pull/15788))
-* Added support for `#pretty_print` in `ActiveRecord::Base` objects.
- ([Pull Request](https://github.com/rails/rails/pull/15172))
+* Introduced the `db:purge` Rake task to empty the database for the
+ current environment.
+ ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d))
+
+* Introduced `ActiveRecord::Base#validate!` that raises
+ `ActiveRecord::RecordInvalid` if the record is invalid.
+ ([Pull Request](https://github.com/rails/rails/pull/8639))
+
+* Introduced `validate` as an alias for `valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
-* PostgreSQL and SQLite adapters no longer add a default limit of 255
+* `touch` now accepts multiple attributes to be touched at once.
+ ([Pull Request](https://github.com/rails/rails/pull/14423))
+
+* The PostgreSQL adapter now supports the `jsonb` datatype in PostgreSQL 9.4+.
+ ([Pull Request](https://github.com/rails/rails/pull/16220))
+
+* The PostgreSQL and SQLite adapters no longer add a default limit of 255
characters on string columns.
([Pull Request](https://github.com/rails/rails/pull/14579))
+* Added support for the `citext` column type in the PostgreSQL adapter.
+ ([Pull Request](https://github.com/rails/rails/pull/12523))
+
+* Added support for user-created range types in the PostgreSQL adapter.
+ ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032))
+
* `sqlite3:///some/path` now resolves to the absolute system path
`/some/path`. For relative paths, use `sqlite3:some/path` instead.
(Previously, `sqlite3:///some/path` resolved to the relative path
- `some/path`. This behaviour was deprecated on Rails 4.1).
+ `some/path`. This behavior was deprecated on Rails 4.1).
([Pull Request](https://github.com/rails/rails/pull/14569))
-* Introduced `#validate` as an alias for `#valid?`.
- ([Pull Request](https://github.com/rails/rails/pull/14456))
-
-* `#touch` now accepts multiple attributes to be touched at once.
- ([Pull Request](https://github.com/rails/rails/pull/14423))
-
* Added support for fractional seconds for MySQL 5.6 and above.
(Pull Request [1](https://github.com/rails/rails/pull/8240),
[2](https://github.com/rails/rails/pull/14359))
-* Added support for the `citext` column type in PostgreSQL adapter.
- ([Pull Request](https://github.com/rails/rails/pull/12523))
+* Added `ActiveRecord::Base#pretty_print` to pretty print models.
+ ([Pull Request](https://github.com/rails/rails/pull/15172))
-* Added support for user-created range types in PostgreSQL adapter.
- ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032))
+* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`,
+ meaning that it no longer retains the extra attributes from custom
+ `SELECT`s.
+ ([Pull Request](https://github.com/rails/rails/pull/15866))
+
+* `ActiveRecord::Base#reflections` now returns a hash with string keys instead
+ of symbol keys. ([Pull Request](https://github.com/rails/rails/pull/17718))
+* The `references` method in migrations now supports a `type` option for
+ specifying the type of the foreign key (e.g. `:uuid`).
+ ([Pull Request](https://github.com/rails/rails/pull/16231))
Active Model
------------
@@ -708,11 +750,14 @@ Please refer to the [Changelog][active-model] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/16180))
* Deprecated `ActiveModel::Dirty#reset_changes` in favor of
- `#clear_changes_information`.
+ `clear_changes_information`.
([Pull Request](https://github.com/rails/rails/pull/16180))
### Notable changes
+* Introduced `validate` as an alias for `valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
+
* Introduced the `restore_attributes` method in `ActiveModel::Dirty` to restore
the changed (dirty) attributes to their previous values.
(Pull Request [1](https://github.com/rails/rails/pull/14861),
@@ -726,10 +771,6 @@ Please refer to the [Changelog][active-model] for detailed changes.
characters if validations are enabled.
([Pull Request](https://github.com/rails/rails/pull/15708))
-* Introduced `#validate` as an alias for `#valid?`.
- ([Pull Request](https://github.com/rails/rails/pull/14456))
-
-
Active Support
--------------
@@ -760,12 +801,12 @@ Please refer to the [Changelog][active-support] for detailed changes.
### Notable changes
-* Introduced new configuration option `active_support.test_order` for
+* Introduced a new configuration option `active_support.test_order` for
specifying the order test cases are executed. This option currently defaults
to `:sorted` but will be changed to `:random` in Rails 5.0.
([Commit](https://github.com/rails/rails/commit/53e877f7d9291b2bf0b8c425f9e32ef35829f35b))
-* `Object#try` and `Object#try!` can now be used without an explicit receiver.
+* `Object#try` and `Object#try!` can now be used without an explicit receiver in the block.
([Commit](https://github.com/rails/rails/commit/5e51bdda59c9ba8e5faf86294e3e431bd45f1830),
[Pull Request](https://github.com/rails/rails/pull/17361))
@@ -776,7 +817,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
(Commit [1](https://github.com/rails/rails/commit/702ad710b57bef45b081ebf42e6fa70820fdd810),
[2](https://github.com/rails/rails/commit/64d91122222c11ad3918cc8e2e3ebc4b0a03448a))
-* `Object#with_options` can now be used without an explicit receiver.
+* `Object#with_options` can now be used without an explicit receiver in the block.
([Pull Request](https://github.com/rails/rails/pull/16339))
* Introduced `String#truncate_words` to truncate a string by a number of words.
@@ -795,6 +836,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
`module Foo; extend ActiveSupport::Concern; end` boilerplate.
([Commit](https://github.com/rails/rails/commit/b16c36e688970df2f96f793a759365b248b582ad))
+* New [guide](constant_autoloading_and_reloading.html) about constant autoloading and reloading.
Credits
-------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index f2315bfe22..67f5f1cdd5 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -10,10 +10,15 @@
</p>
<% else %>
<p>
- These are the new guides for Rails 4.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These are the new guides for Rails 5.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
</p>
<% end %>
<p>
- The guides for earlier releases: <a href="http://guides.rubyonrails.org/v4.1.6/">Rails 4.1.6</a>, <a href="http://guides.rubyonrails.org/v4.0.10/">Rails 4.0.10</a>, <a href="http://guides.rubyonrails.org/v3.2.19/">Rails 3.2.19</a> and <a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
+The guides for earlier releases:
+<a href="http://guides.rubyonrails.org/v4.2.0/">Rails 4.2.0</a>,
+<a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>,
+<a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>,
+<a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and
+<a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
</p>
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 1ca0d9ed55..36d1b6de83 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Action Controller Overview
==========================
@@ -112,8 +114,8 @@ NOTE: The actual URL in this example will be encoded as "/clients?ids%5b%5d=1&id
The value of `params[:ids]` will now be `["1", "2", "3"]`. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type.
-NOTE: Values such as `[]`, `[nil]` or `[nil, nil, ...]` in `params` are replaced
-with `nil` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation)
+NOTE: Values such as `[nil]` or `[nil, nil, ...]` in `params` are replaced
+with `[]` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation)
for more information.
To send a hash you include the key name inside the brackets:
@@ -992,6 +994,11 @@ you would like in a response object. The `ActionController::Live` module allows
you to create a persistent connection with a browser. Using this module, you will
be able to send arbitrary data to the browser at specific points in time.
+NOTE: The default Rails server (WEBrick) is a buffering web server and does not
+support streaming. In order to use this feature, you'll need to use a non buffering
+server like [Puma](http://puma.io), [Rainbows](http://rainbows.bogomips.org)
+or [Passenger](https://www.phusionpassenger.com).
+
#### Incorporating Live Streaming
Including `ActionController::Live` inside of your controller class will provide
@@ -1164,67 +1171,10 @@ class ClientsController < ApplicationController
end
```
-WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development). If you would like to dynamically generate error pages, see [Custom errors page](#custom-errors-page).
+WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development).
NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's [article](http://m.onkey.org/2008/7/20/rescue-from-dispatching) on the subject for more information.
-
-### Custom errors page
-
-You can customize the layout of your error handling using controllers and views.
-First define your app own routes to display the errors page.
-
-* `config/application.rb`
-
- ```ruby
- config.exceptions_app = self.routes
- ```
-
-* `config/routes.rb`
-
- ```ruby
- match '/404', via: :all, to: 'errors#not_found'
- match '/422', via: :all, to: 'errors#unprocessable_entity'
- match '/500', via: :all, to: 'errors#server_error'
- ```
-
-Create the controller and views.
-
-* `app/controllers/errors_controller.rb`
-
- ```ruby
- class ErrorsController < ActionController::Base
- layout 'error'
-
- def not_found
- render status: :not_found
- end
-
- def unprocessable_entity
- render status: :unprocessable_entity
- end
-
- def server_error
- render status: :server_error
- end
- end
- ```
-
-* `app/views`
-
- ```
- errors/
- not_found.html.erb
- unprocessable_entity.html.erb
- server_error.html.erb
- layouts/
- error.html.erb
- ```
-
-Do not forget to set the correct status code on the controller as shown before.
-
-WARNING: You should avoid using the database or any complex operations because the user is already on the error page. Generating another error while on an error page could cause issues like presenting an empty page for the users.
-
Force HTTPS protocol
--------------------
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index f6c974c87a..c586675ee5 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Action Mailer Basics
====================
@@ -35,10 +37,26 @@ views.
```bash
$ bin/rails generate mailer UserMailer
create app/mailers/user_mailer.rb
+create app/mailers/application_mailer.rb
invoke erb
create app/views/user_mailer
+create app/views/layouts/mailer.text.erb
+create app/views/layouts/mailer.html.erb
invoke test_unit
create test/mailers/user_mailer_test.rb
+create test/mailers/previews/user_mailer_preview.rb
+```
+
+```ruby
+# app/mailers/application_mailer.rb
+class ApplicationMailer < ActionMailer::Base
+ default from: "from@example.com"
+ layout 'mailer'
+end
+
+# app/mailers/user_mailer.rb
+class UserMailer < ApplicationMailer
+end
```
As you can see, you can generate mailers just like you use other generators with
@@ -63,8 +81,7 @@ delivered via email.
`app/mailers/user_mailer.rb` contains an empty mailer:
```ruby
-class UserMailer < ActionMailer::Base
- default from: 'from@example.com'
+class UserMailer < ApplicationMailer
end
```
@@ -72,7 +89,7 @@ Let's add a method called `welcome_email`, that will send an email to the user's
registered email address:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
def welcome_email(user)
@@ -348,7 +365,7 @@ for the HTML version and `welcome_email.text.erb` for the plain text version.
To change the default mailer view for your action you do something like:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
def welcome_email(user)
@@ -370,7 +387,7 @@ If you want more flexibility you can also pass a block and render specific
templates or even render inline or text without using a template file:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
def welcome_email(user)
@@ -400,7 +417,7 @@ layout.
In order to use a different file, call `layout` in your mailer:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
layout 'awesome' # use awesome.(html|text).erb as the layout
end
```
@@ -412,7 +429,7 @@ You can also pass in a `layout: 'layout_name'` option to the render call inside
the format block to specify different layouts for different formats:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def welcome_email(user)
mail(to: user.email) do |format|
format.html { render layout: 'my_layout' }
@@ -455,16 +472,7 @@ By using the full URL, your links will now work in your emails.
#### generating URLs with `url_for`
-You need to pass the `only_path: false` option when using `url_for`. This will
-ensure that absolute URLs are generated because the `url_for` view helper will,
-by default, generate relative URLs when a `:host` option isn't explicitly
-provided.
-
-```erb
-<%= url_for(controller: 'welcome',
- action: 'greeting',
- only_path: false) %>
-```
+`url_for` generate full URL by default in templates.
If you did not configure the `:host` option globally make sure to pass it to
`url_for`.
@@ -476,9 +484,6 @@ If you did not configure the `:host` option globally make sure to pass it to
action: 'greeting') %>
```
-NOTE: When you explicitly pass the `:host` Rails will always generate absolute
-URLs, so there is no need to pass `only_path: false`.
-
#### generating URLs with named routes
Email clients have no web context and so paths have no base URL to form complete
@@ -510,7 +515,7 @@ while delivering emails, you can do this using `delivery_method_options` in the
mailer action.
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def welcome_email(user, company)
@user = user
@url = user_url(@user)
@@ -532,7 +537,7 @@ option. In such cases don't forget to add the `:content_type` option. Rails
will default to `text/plain` otherwise.
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def welcome_email(user, email_body)
mail(to: user.email,
body: email_body,
@@ -562,7 +567,7 @@ mailer, and pass the email object to the mailer `receive` instance
method. Here's an example:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def receive(email)
page = Page.find_by(address: email.to.first)
page.emails.create(
@@ -598,7 +603,7 @@ Action Mailer allows for you to specify a `before_action`, `after_action` and
using instance variables set in your mailer action.
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
after_action :set_delivery_options,
:prevent_delivery_to_guests,
:set_business_headers
@@ -728,7 +733,9 @@ Mailer framework. You can do this in an initializer file
`config/initializers/sandbox_email_interceptor.rb`
```ruby
-ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging?
+if Rails.env.staging?
+ ActionMailer::Base.register_interceptor(SandboxEmailInterceptor)
+end
```
NOTE: The example above uses a custom environment called "staging" for a
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 683e633668..a6bde4f517 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Action View Overview
====================
@@ -7,7 +9,6 @@ After reading this guide, you will know:
* How best to use templates, partials, and layouts.
* What helpers are provided by Action View and how to make your own.
* How to use localized views.
-* How to use Action View outside of Rails.
--------------------------------------------------------------------------------
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 0e9e4eff1d..31c9406d5c 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Job Basics
=================
@@ -23,7 +25,7 @@ clean-ups, to billing charges, to mailings. Anything that can be chopped up
into small units of work and run in parallel, really.
-The Purpose of the Active Job
+The Purpose of Active Job
-----------------------------
The main point is to ensure that all Rails apps will have a job infrastructure
in place, even if it's in the form of an "immediate runner". We can then have
@@ -56,9 +58,6 @@ You can also create a job that will run on a specific queue:
$ bin/rails generate job guests_cleanup --queue urgent
```
-As you can see, you can generate jobs just like you use other generators with
-Rails.
-
If you don't want to use a generator, you could create your own file inside of
`app/jobs`, just make sure that it inherits from `ActiveJob::Base`.
@@ -107,14 +106,19 @@ Active Job has built-in adapters for multiple queueing backends (Sidekiq,
Resque, Delayed Job and others). To get an up-to-date list of the adapters
see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
-### Changing the Backend
+### Setting the Backend
-You can easily change your queueing backend:
+You can easily set your queueing backend:
```ruby
-# be sure to have the adapter gem in your Gemfile and follow
-# the adapter specific installation and deployment instructions
-config.active_job.queue_adapter = :sidekiq
+# config/application.rb
+module YourApp
+ class Application < Rails::Application
+ # Be sure to have the adapter's gem in your Gemfile and follow
+ # the adapter's specific installation and deployment instructions.
+ config.active_job.queue_adapter = :sidekiq
+ end
+end
```
@@ -149,11 +153,11 @@ class GuestsCleanupJob < ActiveJob::Base
end
# Now your job will run on queue production_low_priority on your
-# production environment and on beta_low_priority on your beta
+# production environment and on staging_low_priority on your staging
# environment
```
-The default queue name prefix delimiter is '_'. This can be changed by setting
+The default queue name prefix delimiter is '\_'. This can be changed by setting
`config.active_job.queue_name_delimiter` in `application.rb`:
```ruby
@@ -213,8 +217,8 @@ backends you need to specify the queues to listen to.
Callbacks
---------
-Active Job provides hooks during the lifecycle of a job. Callbacks allow you to
-trigger logic during the lifecycle of a job.
+Active Job provides hooks during the life cycle of a job. Callbacks allow you to
+trigger logic during the life cycle of a job.
### Available callbacks
@@ -301,7 +305,6 @@ Active Job provides a way to catch exceptions raised during the execution of the
job:
```ruby
-
class GuestsCleanupJob < ActiveJob::Base
queue_as :default
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index a520b91a4d..8dee1cc5ec 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Model Basics
===================
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index bd074d0055..9d2ba328ea 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Record Basics
====================
@@ -358,7 +360,7 @@ class CreatePublications < ActiveRecord::Migration
t.string :publisher_type
t.boolean :single_issue
- t.timestamps
+ t.timestamps null: false
end
add_index :publications, :publication_type_id
end
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 9c7e60cbb0..9d3a8c3af6 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Record Callbacks
=======================
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index c8a31fe7b8..8ae282bad2 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Record Migrations
========================
@@ -39,7 +41,7 @@ class CreateProducts < ActiveRecord::Migration
t.string :name
t.text :description
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -285,7 +287,7 @@ class CreateProducts < ActiveRecord::Migration
t.string :name
t.text :description
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -452,6 +454,8 @@ number of digits after the decimal point.
are using a dynamic value (such as a date), the default will only be calculated
the first time (i.e. on the date the migration is applied).
* `index` Adds an index for the column.
+* `required` Adds `required: true` for `belongs_to` associations and
+`null: false` to the column in the migration.
Some adapters may support additional options; see the adapter specific API docs
for further information.
@@ -824,7 +828,7 @@ class CreateProducts < ActiveRecord::Migration
create_table :products do |t|
t.string :name
t.text :description
- t.timestamps
+ t.timestamps null: false
end
end
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 6c94218ef6..fa0f31cbbd 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Record and PostgreSQL
============================
@@ -83,9 +85,12 @@ Book.where("array_length(ratings, 1) >= 3")
* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html)
+NOTE: you need to enable the `hstore` extension to use hstore.
+
```ruby
# db/migrate/20131009135255_create_profiles.rb
ActiveRecord::Schema.define do
+ enable_extension 'hstore' unless extension_enabled?('hstore')
create_table :profiles do |t|
t.hstore 'settings'
end
@@ -103,11 +108,6 @@ profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
-
-## you need to call _will_change! if you are editing the store in place
-profile.settings["color"] = "green"
-profile.settings_will_change!
-profile.save!
```
### JSON
@@ -219,7 +219,7 @@ Currently there is no special support for enumerated types. They are mapped as
normal text columns:
```ruby
-# db/migrate/20131220144913_create_events.rb
+# db/migrate/20131220144913_create_articles.rb
execute <<-SQL
CREATE TYPE article_status AS ENUM ('draft', 'published');
SQL
@@ -281,7 +281,7 @@ end
# Usage
User.create settings: "01010011"
user = User.first
-user.settings # => "(Paris,Champs-Élysées)"
+user.settings # => "01010011"
user.settings = "0xAF"
user.settings # => 10101111
user.save!
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index e1a465c64f..f8c64cbd0c 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Record Query Interface
=============================
@@ -9,6 +11,7 @@ After reading this guide, you will know:
* How to specify the order, retrieved attributes, grouping, and other properties of the found records.
* How to use eager loading to reduce the number of database queries needed for data retrieval.
* How to use dynamic finders methods.
+* How to use method chaining to use multiple ActiveRecord methods together.
* How to check for the existence of particular records.
* How to perform various calculations on Active Record models.
* How to run EXPLAIN on relations.
@@ -87,7 +90,7 @@ The primary operation of `Model.find(options)` can be summarized as:
* Convert the supplied options to an equivalent SQL query.
* Fire the SQL query and retrieve the corresponding results from the database.
* Instantiate the equivalent Ruby object of the appropriate model for every resulting row.
-* Run `after_find` callbacks, if any.
+* Run `after_find` and then `after_initialize` callbacks, if any.
### Retrieving a Single Object
@@ -1327,14 +1330,66 @@ You can specify an exclamation point (`!`) on the end of the dynamic finders to
If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`.
+Understanding The Method Chaining
+---------------------------------
+
+The Active Record pattern implements [Method Chaining](http://en.wikipedia.org/wiki/Method_chaining),
+which allow us to use multiple Active Record methods together in a simple and straightforward way.
+
+You can chain methods in a statement when the previous method called returns an
+`ActiveRecord::Relation`, like `all`, `where`, and `joins`. Methods that return
+a single object (see [Retrieving a Single Object Section](#retrieving-a-single-object))
+have to be at the end of the statement.
+
+There are some examples below. This guide won't cover all the possibilities, just a few as examples.
+When an Active Record method is called, the query is not immediately generated and sent to the database,
+this just happens when the data is actually needed. So each example below generates a single query.
+
+### Retrieving filtered data from multiple tables
+
+```ruby
+Person
+ .select('people.id, people.name, comments.text')
+ .joins(:comments)
+ .where('comments.created_at > ?', 1.week.ago)
+```
+
+The result should be something like this:
+
+```sql
+SELECT people.id, people.name, comments.text
+FROM people
+INNER JOIN comments
+ ON comments.person_id = people.id
+WHERE comments.created_at = '2015-01-01'
+```
+
+### Retrieving specific data from multiple tables
+
+```ruby
+Person
+ .select('people.id, people.name, companies.name')
+ .joins(:company)
+ .find_by('people.name' => 'John') # this should be the last
+```
+
+The above should generate:
+
+```sql
+SELECT people.id, people.name, companies.name
+FROM people
+INNER JOIN companies
+ ON companies.person_id = people.id
+WHERE people.name = 'John'
+LIMIT 1
+```
+
+NOTE: Remember that, if `find_by` returns more than one registry, it will take
+just the first and ignore the others. Note the `LIMIT 1` statement above.
+
Find or Build a New Object
--------------------------
-NOTE: Some dynamic finders have been deprecated in Rails 4.0 and will be
-removed in Rails 4.1. The best practice is to use Active Record scopes
-instead. You can find the deprecation gem at
-https://github.com/rails/activerecord-deprecated_finders
-
It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods.
### `find_or_create_by`
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 546c0608ee..8c832bafff 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Record Validations
=========================
@@ -487,6 +489,8 @@ constraints to acceptable values:
* `:even` - Specifies the value must be an even number if set to true. The
default error message for this option is _"must be even"_.
+NOTE: By default, `numericality` doesn't allow `nil` values. You can use `allow_nil: true` option to permit it.
+
The default error message is _"is not a number"_.
### `presence`
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 616b813817..ba839e1723 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Support Core Extensions
==============================
@@ -1447,7 +1449,7 @@ Returns the substring of the string starting at position `position`:
"hello".from(0) # => "hello"
"hello".from(2) # => "llo"
"hello".from(-2) # => "lo"
-"hello".from(10) # => "" if < 1.9, nil in 1.9
+"hello".from(10) # => nil
```
NOTE: Defined in `active_support/core_ext/string/access.rb`.
@@ -1950,24 +1952,6 @@ as well as adding or subtracting their results from a Time object. For example:
(4.months + 5.years).from_now
```
-While these methods provide precise calculation when used as in the examples above, care
-should be taken to note that this is not true if the result of `months', `years', etc is
-converted before use:
-
-```ruby
-# equivalent to 30.days.to_i.from_now
-1.month.to_i.from_now
-
-# equivalent to 365.25.days.to_f.from_now
-1.year.to_f.from_now
-```
-
-In such cases, Ruby's core [Date](http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html) and
-[Time](http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html) should be used for precision
-date and time arithmetic.
-
-NOTE: Defined in `active_support/core_ext/numeric/time.rb`.
-
### Formatting
Enables the formatting of numbers in a variety of ways.
@@ -3889,7 +3873,7 @@ def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
-rescue MissingSourceFile => e
+rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
@@ -3901,7 +3885,7 @@ NOTE: Defined in `active_support/core_ext/name_error.rb`.
Extensions to `LoadError`
-------------------------
-Active Support adds `is_missing?` to `LoadError`, and also assigns that class to the constant `MissingSourceFile` for backwards compatibility.
+Active Support adds `is_missing?` to `LoadError`.
Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension).
@@ -3912,7 +3896,7 @@ def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
-rescue MissingSourceFile => e
+rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 9dfacce560..0aa74e387d 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Support Instrumentation
==============================
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index a2ebf55335..d481700709 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
API Documentation Guidelines
============================
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index ae0f19c02a..64d1c31083 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
The Asset Pipeline
==================
@@ -167,9 +169,8 @@ directory. Files in this directory are served by the Sprockets middleware.
Assets can still be placed in the `public` hierarchy. Any assets under `public`
will be served as static files by the application or web server when
-`config.serve_static_assets` is set to true. You should use
-`app/assets` for files that must undergo some pre-processing before they are
-served.
+`config.serve_static_files` is set to true. You should use `app/assets` for
+files that must undergo some pre-processing before they are served.
In production, Rails precompiles these files to `public/assets` by default. The
precompiled copies are then served as static assets by the web server. The files
@@ -181,12 +182,12 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript
file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a
Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`)
for that controller. Additionally, when generating a scaffold, Rails generates
-the file scaffolds.css (or scaffolds.css.scss if `sass-rails` is in the
+the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the
`Gemfile`.)
For example, if you generate a `ProjectsController`, Rails will also add a new
-file at `app/assets/javascripts/projects.js.coffee` and another at
-`app/assets/stylesheets/projects.css.scss`. By default these files will be ready
+file at `app/assets/javascripts/projects.coffee` and another at
+`app/assets/stylesheets/projects.scss`. By default these files will be ready
to use by your application immediately using the `require_tree` directive. See
[Manifest Files and Directives](#manifest-files-and-directives) for more details
on require_tree.
@@ -231,7 +232,9 @@ images, JavaScript files or stylesheets.
scope of the application or those libraries which are shared across applications.
* `vendor/assets` is for assets that are owned by outside entities, such as
-code for JavaScript plugins and CSS frameworks.
+code for JavaScript plugins and CSS frameworks. Keep in mind that third party
+code with references to other files also processed by the asset Pipeline (images,
+stylesheets, etc.), will need to be rewritten to use helpers like `asset_path`.
WARNING: If you are upgrading from Rails 3, please take into account that assets
under `lib/assets` or `vendor/assets` are available for inclusion via the
@@ -421,7 +424,7 @@ $('#logo').attr({ src: "<%= asset_path('logo.png') %>" });
This writes the path to the particular asset being referenced.
Similarly, you can use the `asset_path` helper in CoffeeScript files with `erb`
-extension (e.g., `application.js.coffee.erb`):
+extension (e.g., `application.coffee.erb`):
```js
$('#logo').attr src: "<%= asset_path('logo.png') %>"
@@ -522,8 +525,8 @@ The file extensions used on an asset determine what preprocessing is applied.
When a controller or a scaffold is generated with the default Rails gemset, a
CoffeeScript file and a SCSS file are generated in place of a regular JavaScript
and CSS file. The example used before was a controller called "projects", which
-generated an `app/assets/javascripts/projects.js.coffee` and an
-`app/assets/stylesheets/projects.css.scss` file.
+generated an `app/assets/javascripts/projects.coffee` and an
+`app/assets/stylesheets/projects.scss` file.
In development mode, or if the asset pipeline is disabled, when these files are
requested they are processed by the processors provided by the `coffee-script`
@@ -535,13 +538,13 @@ web server.
Additional layers of preprocessing can be requested by adding other extensions,
where each extension is processed in a right-to-left manner. These should be
used in the order the processing should be applied. For example, a stylesheet
-called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB,
+called `app/assets/stylesheets/projects.scss.erb` is first processed as ERB,
then SCSS, and finally served as CSS. The same applies to a JavaScript file -
-`app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then
+`app/assets/javascripts/projects.coffee.erb` is processed as ERB, then
CoffeeScript, and served as JavaScript.
Keep in mind the order of these preprocessors is important. For example, if
-you called your JavaScript file `app/assets/javascripts/projects.js.erb.coffee`
+you called your JavaScript file `app/assets/javascripts/projects.erb.coffee`
then it would be processed with the CoffeeScript interpreter first, which
wouldn't understand ERB and therefore you would run into problems.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 61490ceb54..95c7e747ef 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Active Record Associations
==========================
@@ -101,13 +103,13 @@ class CreateOrders < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :orders do |t|
t.belongs_to :customer, index: true
t.datetime :order_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -132,13 +134,13 @@ class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -165,13 +167,13 @@ class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :orders do |t|
t.belongs_to :customer, index:true
t.datetime :order_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -207,19 +209,19 @@ class CreateAppointments < ActiveRecord::Migration
def change
create_table :physicians do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :patients do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :appointments do |t|
t.belongs_to :physician, index: true
t.belongs_to :patient, index: true
t.datetime :appointment_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -291,19 +293,19 @@ class CreateAccountHistories < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
create_table :account_histories do |t|
t.belongs_to :account, index: true
t.integer :credit_rating
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -332,12 +334,12 @@ class CreateAssembliesAndParts < ActiveRecord::Migration
def change
create_table :assemblies do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :parts do |t|
t.string :part_number
- t.timestamps
+ t.timestamps null: false
end
create_table :assemblies_parts, id: false do |t|
@@ -371,13 +373,13 @@ class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
add_index :accounts, :supplier_id
@@ -455,7 +457,7 @@ class CreatePictures < ActiveRecord::Migration
t.string :name
t.integer :imageable_id
t.string :imageable_type
- t.timestamps
+ t.timestamps null: false
end
add_index :pictures, :imageable_id
@@ -471,7 +473,7 @@ class CreatePictures < ActiveRecord::Migration
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true, index: true
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -501,7 +503,7 @@ class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.references :manager, index: true
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -879,10 +881,12 @@ class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
- has_many :orders
+ has_many :orders, counter_cache: :count_of_orders
end
```
+NOTE: You only need to specify the :counter_cache option on the "has_many side" of the association when using a custom name for the counter cache.
+
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
##### `:dependent`
@@ -1495,6 +1499,7 @@ The `has_many` association supports these options:
* `:as`
* `:autosave`
* `:class_name`
+* `:counter_cache`
* `:dependent`
* `:foreign_key`
* `:inverse_of`
@@ -1522,6 +1527,10 @@ class Customer < ActiveRecord::Base
end
```
+##### `:counter_cache`
+
+This option can be used to configure a custom named `:counter_cache`. You only need this option when you customized the name of your `:counter_cache` on the [belongs_to association](#options-for-belongs-to).
+
##### `:dependent`
Controls what happens to the associated objects when their owner is destroyed:
@@ -1532,8 +1541,6 @@ Controls what happens to the associated objects when their owner is destroyed:
* `:restrict_with_exception` causes an exception to be raised if there are any associated records
* `:restrict_with_error` causes an error to be added to the owner if there are any associated objects
-NOTE: This option is ignored when you use the `:through` option on the association.
-
##### `:foreign_key`
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
new file mode 100644
index 0000000000..489ea681e2
--- /dev/null
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -0,0 +1,1300 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
+Autoloading and Reloading Constants
+===================================
+
+This guide documents how constant autoloading and reloading works.
+
+After reading this guide, you will know:
+
+* Key aspects of Ruby constants
+* What is `autoload_paths`
+* How constant autoloading works
+* What is `require_dependency`
+* How constant reloading works
+* Solutions to common autoloading gotchas
+
+--------------------------------------------------------------------------------
+
+
+Introduction
+------------
+
+Ruby on Rails allows applications to be written as if their code was preloaded.
+
+In a normal Ruby program classes need to load their dependencies:
+
+```ruby
+require 'application_controller'
+require 'post'
+
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+Our Rubyist instinct quickly sees some redundancy in there: If classes were
+defined in files matching their name, couldn't their loading be automated
+somehow? We could save scanning the file for dependencies, which is brittle.
+
+Moreover, `Kernel#require` loads files once, but development is much more smooth
+if code gets refreshed when it changes without restarting the server. It would
+be nice to be able to use `Kernel#load` in development, and `Kernel#require` in
+production.
+
+Indeed, those features are provided by Ruby on Rails, where we just write
+
+```ruby
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+This guide documents how that works.
+
+
+Constants Refresher
+-------------------
+
+While constants are trivial in most programming languages, they are a rich
+topic in Ruby.
+
+It is beyond the scope of this guide to document Ruby constants, but we are
+nevertheless going to highlight a few key topics. Truly grasping the following
+sections is instrumental to understanding constant autoloading and reloading.
+
+### Nesting
+
+Class and module definitions can be nested to create namespaces:
+
+```ruby
+module XML
+ class SAXParser
+ # (1)
+ end
+end
+```
+
+The *nesting* at any given place is the collection of enclosing nested class and
+module objects outwards. For example, in the previous example, the nesting at
+(1) is
+
+```ruby
+[XML::SAXParser, XML]
+```
+
+It is important to understand that the nesting is composed of class and module
+*objects*, it has nothing to do with the constants used to access them, and is
+also unrelated to their names.
+
+For instance, while this definition is similar to the previous one:
+
+```ruby
+class XML::SAXParser
+ # (2)
+end
+```
+
+the nesting in (2) is different:
+
+```ruby
+[XML::SAXParser]
+```
+
+`XML` does not belong to it.
+
+We can see in this example that the name of a class or module that belongs to a
+certain nesting does not necessarily correlate with the namespaces at the spot.
+
+Even more, they are totally independent, take for instance
+
+```ruby
+module X::Y
+ module A::B
+ # (3)
+ end
+end
+```
+
+The nesting in (3) consists of two module objects:
+
+```ruby
+[A::B, X::Y]
+```
+
+So, it not only doesn't end in `A`, which does not even belong to the nesting,
+but it also contains `X::Y`, which is independent from `A::B`.
+
+The nesting is an internal stack maintained by the interpreter, and it gets
+modified according to these rules:
+
+* The class object following a `class` keyword gets pushed when its body is
+executed, and popped after it.
+
+* The module object following a `module` keyword gets pushed when its body is
+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,
+the singleton class of 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
+a newly created anonymous module is pushed by Ruby.
+
+It is interesting to observe that blocks do not modify the stack. In particular
+the blocks that may be passed to `Class.new` and `Module.new` do not get the
+class or module being defined pushed to their nesting. That's one of the
+differences between defining classes and modules in one way or another.
+
+The nesting at any given place can be inspected with `Module.nesting`.
+
+### Class and Module Definitions are Constant Assignments
+
+Let's suppose the following snippet creates a class (rather than reopening it):
+
+```ruby
+class C
+end
+```
+
+Ruby creates a constant `C` in `Object` and stores in that constant a class
+object. The name of the class instance is "C", a string, named after the
+constant.
+
+That is,
+
+```ruby
+class Project < ActiveRecord::Base
+end
+```
+
+performs a constant assignment equivalent to
+
+```ruby
+Project = Class.new(ActiveRecord::Base)
+```
+
+including setting the name of the class as a side-effect:
+
+```ruby
+Project.name # => "Project"
+```
+
+Constant assignment has a special rule to make that happen: if the object
+being assigned is an anonymous class or module, Ruby sets the object's name to
+the name of the constant.
+
+INFO. From then on, what happens to the constant and the instance does not
+matter. For example, the constant could be deleted, the class object could be
+assigned to a different constant, be stored in no constant anymore, etc. Once
+the name is set, it doesn't change.
+
+Similarly, module creation using the `module` keyword as in
+
+```ruby
+module Admin
+end
+```
+
+performs a constant assignment equivalent to
+
+```ruby
+Admin = Module.new
+```
+
+including setting the name as a side-effect:
+
+```ruby
+Admin.name # => "Admin"
+```
+
+WARNING. The execution context of a block passed to `Class.new` or `Module.new`
+is not entirely equivalent to the one of the body of the definitions using the
+`class` and `module` keywords. But both idioms result in the same constant
+assignment.
+
+Thus, when one informally says "the `String` class", that really means: the
+class object stored in the constant called "String" in the class object stored
+in the `Object` constant. `String` is otherwise an ordinary Ruby constant and
+everything related to constants such as resolution algorithms applies to it.
+
+Likewise, in the controller
+
+```ruby
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If
+all is good, the constant evaluates to an object that responds to `all`.
+
+That is why we talk about *constant* autoloading, Rails has the ability to
+load constants on the fly.
+
+### Constants are Stored in Modules
+
+Constants belong to modules in a very literal sense. Classes and modules have
+a constant table; think of it as a hash table.
+
+Let's analyze an example to really understand what that means. While common
+abuses of language like "the `String` class" are convenient, the exposition is
+going to be precise here for didactic purposes.
+
+Let's consider the following module definition:
+
+```ruby
+module Colors
+ RED = '0xff0000'
+end
+```
+
+First, when the `module` keyword is processed, the interpreter creates a new
+entry in the constant table of the class object stored in the `Object` constant.
+Said entry associates the name "Colors" to a newly created module object.
+Furthermore, the interpreter sets the name of the new module object to be the
+string "Colors".
+
+Later, when the body of the module definition is interpreted, a new entry is
+created in the constant table of the module object stored in the `Colors`
+constant. That entry maps the name "RED" to the string "0xff0000".
+
+In particular, `Colors::RED` is totally unrelated to any other `RED` constant
+that may live in any other class or module object. If there were any, they
+would have separate entries in their respective constant tables.
+
+Pay special attention in the previous paragraphs to the distinction between
+class and module objects, constant names, and value objects associated to them
+in constant tables.
+
+### Resolution Algorithms
+
+#### Resolution Algorithm for Relative Constants
+
+At any given place in the code, let's define *cref* to be the first element of
+the nesting if it is not empty, or `Object` otherwise.
+
+Without getting too much into the details, the resolution algorithm for relative
+constant references goes like this:
+
+1. If the nesting is not empty the constant is looked up in its elements and in
+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
+implementation of `const_missing` raises `NameError`, but it can be overridden.
+
+Rails autoloading **does not emulate this algorithm**, but its starting point is
+the name of the constant to be autoloaded, and the cref. See more in [Relative
+References](#autoloading-algorithms-relative-references).
+
+#### Resolution Algorithm for Qualified Constants
+
+Qualified constants look like this:
+
+```ruby
+Billing::Invoice
+```
+
+`Billing::Invoice` is composed of two constants: `Billing` is relative and is
+resolved using the algorithm of the previous section.
+
+INFO. Leading colons would make the first segment absolute rather than
+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
+object, that is, `Billing` in the example above. The algorithm for qualified
+constants goes like this:
+
+1. The constant is looked up in the parent and its ancestors.
+
+2. If the lookup fails, `const_missing` is invoked in the parent. The default
+implementation of `const_missing` raises `NameError`, but it can be overridden.
+
+As you see, this algorithm is simpler than the one for relative constants. In
+particular, the nesting plays no role here, and modules are not special-cased,
+if neither they nor their ancestors have the constants, `Object` is **not**
+checked.
+
+Rails autoloading **does not emulate this algorithm**, but its starting point is
+the name of the constant to be autoloaded, and the parent. See more in
+[Qualified References](#qualified-references).
+
+
+Vocabulary
+----------
+
+### Parent Namespaces
+
+Given a string with a constant path we define its *parent namespace* to be the
+string that results from removing its rightmost segment.
+
+For example, the parent namespace of the string "A::B::C" is the string "A::B",
+the parent namespace of "A::B" is "A", and the parent namespace of "A" is "".
+
+The interpretation of a parent namespace when thinking about classes and modules
+is tricky though. Let's consider a module M named "A::B":
+
+* The parent namespace, "A", may not reflect nesting at a given spot.
+
+* The constant `A` may no longer exist, some code could have removed it from
+`Object`.
+
+* If `A` exists, the class or module that was originally in `A` may not be there
+anymore. For example, if after a constant removal there was another constant
+assignment there would generally be a different object in there.
+
+* In such case, it could even happen that the reassigned `A` held a new class or
+module called also "A"!
+
+* In the previous scenarios M would no longer be reachable through `A::B` but
+the module object itself could still be alive somewhere and its name would
+still be "A::B".
+
+The idea of a parent namespace is at the core of the autoloading algorithms
+and helps explain and understand their motivation intuitively, but as you see
+that metaphor leaks easily. Given an edge case to reason about, take always into
+account that by "parent namespace" the guide means exactly that specific string
+derivation.
+
+### Loading Mechanism
+
+Rails autoloads files with `Kernel#load` when `config.cache_classes` is false,
+the default in development mode, and with `Kernel#require` otherwise, the
+default in production mode.
+
+`Kernel#load` allows Rails to execute files more than once if [constant
+reloading](#constant-reloading) is enabled.
+
+This guide uses the word "load" freely to mean a given file is interpreted, but
+the actual mechanism can be `Kernel#load` or `Kernel#require` depending on that
+flag.
+
+
+Autoloading Availability
+------------------------
+
+Rails is always able to autoload provided its environment is in place. For
+example the `runner` command autoloads:
+
+```
+$ bin/rails runner 'p User.column_names'
+["id", "email", "created_at", "updated_at"]
+```
+
+The console autoloads, the test suite autoloads, and of course the application
+autoloads.
+
+By default, Rails eager loads the application files when it boots in production
+mode, so most of the autoloading going on in development does not happen. But
+autoloading may still be triggered during eager loading.
+
+For example, given
+
+```ruby
+class BeachHouse < House
+end
+```
+
+if `House` is still unknown when `app/models/beach_house.rb` is being eager
+loaded, Rails autoloads it.
+
+
+autoload_paths
+--------------
+
+As you probably know, when `require` gets a relative file name:
+
+```ruby
+require 'erb'
+```
+
+Ruby looks for the file in the directories listed in `$LOAD_PATH`. That is, Ruby
+iterates over all its directories and for each one of them checks whether they
+have a file called "erb.rb", or "erb.so", or "erb.o", or "erb.dll". If it finds
+any of them, the interpreter loads it and ends the search. Otherwise, it tries
+again in the next directory of the list. If the list gets exhausted, `LoadError`
+is raised.
+
+We are going to cover how constant autoloading works in more detail later, but
+the idea is that when a constant like `Post` is hit and missing, if there's a
+`post.rb` file for example in `app/models` Rails is going to find it, evaluate
+it, and have `Post` defined as a side-effect.
+
+Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which
+to look up `post.rb`. That collection is called `autoload_paths` and by
+default it contains:
+
+* All subdirectories of `app` in the application and engines. For example,
+ `app/controllers`. They do not need to be the default ones, any custom
+ directories like `app/workers` belong automatically to `autoload_paths`.
+
+* Any existing second level directories called `app/*/concerns` in the
+ application and engines.
+
+* The directory `test/mailers/previews`.
+
+Also, this collection is configurable via `config.autoload_paths`. For example,
+`lib` was in the list years ago, but no longer is. An application can opt-in
+by adding this to `config/application.rb`:
+
+```ruby
+config.autoload_paths += "#{Rails.root}/lib"
+```
+
+The value of `autoload_paths` can be inspected. In a just generated application
+it is (edited):
+
+```
+$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths'
+.../app/assets
+.../app/controllers
+.../app/helpers
+.../app/mailers
+.../app/models
+.../app/controllers/concerns
+.../app/models/concerns
+.../test/mailers/previews
+```
+
+INFO. `autoload_paths` is computed and cached during the initialization process.
+The application needs to be restarted to reflect any changes in the directory
+structure.
+
+
+Autoloading Algorithms
+----------------------
+
+### Relative References
+
+A relative constant reference may appear in several places, for example, in
+
+```ruby
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+all three constant references are relative.
+
+#### Constants after the `class` and `module` Keywords
+
+Ruby performs a lookup for the constant that follows a `class` or `module`
+keyword because it needs to know if the class or module is going to be created
+or reopened.
+
+If the constant is not defined at that point it is not considered to be a
+missing constant, autoloading is **not** triggered.
+
+So, in the previous example, if `PostsController` is not defined when the file
+is interpreted Rails autoloading is not going to be triggered, Ruby will just
+define the controller.
+
+#### Top-Level Constants
+
+On the contrary, if `ApplicationController` is unknown, the constant is
+considered missing and an autoload is going to be attempted by Rails.
+
+In order to load `ApplicationController`, Rails iterates over `autoload_paths`.
+First checks if `app/assets/application_controller.rb` exists. If it does not,
+which is normally the case, it continues and finds
+`app/controllers/application_controller.rb`.
+
+If the file defines the constant `ApplicationController` all is fine, otherwise
+`LoadError` is raised:
+
+```
+unable to autoload constant ApplicationController, expected
+<full path to application_controller.rb> to define it (LoadError)
+```
+
+INFO. Rails does not require the value of autoloaded constants to be a class or
+module object. For example, if the file `app/models/max_clients.rb` defines
+`MAX_CLIENTS = 100` autoloading `MAX_CLIENTS` works just fine.
+
+#### Namespaces
+
+Autoloading `ApplicationController` looks directly under the directories of
+`autoload_paths` because the nesting in that spot is empty. The situation of
+`Post` is different, the nesting in that line is `[PostsController]` and support
+for namespaces comes into play.
+
+The basic idea is that given
+
+```ruby
+module Admin
+ class BaseController < ApplicationController
+ @@all_roles = Role.all
+ end
+end
+```
+
+to autoload `Role` we are going to check if it is defined in the current or
+parent namespaces, one at a time. So, conceptually we want to try to autoload
+any of
+
+```
+Admin::BaseController::Role
+Admin::Role
+Role
+```
+
+in that order. That's the idea. To do so, Rails looks in `autoload_paths`
+respectively for file names like these:
+
+```
+admin/base_controller/role.rb
+admin/role.rb
+role.rb
+```
+
+modulus some additional directory lookups we are going to cover soon.
+
+INFO. `'Constant::Name'.underscore` gives the relative path without extension of
+the file name where `Constant::Name` is expected to be defined.
+
+Let's see how Rails autoloads the `Post` constant in the `PostsController`
+above assuming the application has a `Post` model defined in
+`app/models/post.rb`.
+
+First it checks for `posts_controller/post.rb` in `autoload_paths`:
+
+```
+app/assets/posts_controller/post.rb
+app/controllers/posts_controller/post.rb
+app/helpers/posts_controller/post.rb
+...
+test/mailers/previews/posts_controller/post.rb
+```
+
+Since the lookup is exhausted without success, a similar search for a directory
+is performed, we are going to see why in the [next section](#automatic-modules):
+
+```
+app/assets/posts_controller/post
+app/controllers/posts_controller/post
+app/helpers/posts_controller/post
+...
+test/mailers/previews/posts_controller/post
+```
+
+If all those attempts fail, then Rails starts the lookup again in the parent
+namespace. In this case only the top-level remains:
+
+```
+app/assets/post.rb
+app/controllers/post.rb
+app/helpers/post.rb
+app/mailers/post.rb
+app/models/post.rb
+```
+
+A matching file is found in `app/models/post.rb`. The lookup stops there and the
+file is loaded. If the file actually defines `Post` all is fine, otherwise
+`LoadError` is raised.
+
+### Qualified References
+
+When a qualified constant is missing Rails does not look for it in the parent
+namespaces. But there is a caveat: When a constant is missing, Rails is
+unable to tell if the trigger was a relative reference or a qualified one.
+
+For example, consider
+
+```ruby
+module Admin
+ User
+end
+```
+
+and
+
+```ruby
+Admin::User
+```
+
+If `User` is missing, in either case all Rails knows is that a constant called
+"User" was missing in a module called "Admin".
+
+If there is a top-level `User` Ruby would resolve it in the former example, but
+wouldn't in the latter. In general, Rails does not emulate the Ruby constant
+resolution algorithms, but in this case it tries using the following heuristic:
+
+> If none of the parent namespaces of the class or module has the missing
+> constant then Rails assumes the reference is relative. Otherwise qualified.
+
+For example, if this code triggers autoloading
+
+```ruby
+Admin::User
+```
+
+and the `User` constant is already present in `Object`, it is not possible that
+the situation is
+
+```ruby
+module Admin
+ User
+end
+```
+
+because otherwise Ruby would have resolved `User` and no autoloading would have
+been triggered in the first place. Thus, Rails assumes a qualified reference and
+considers the file `admin/user.rb` and directory `admin/user` to be the only
+valid options.
+
+In practice, this works quite well as long as the nesting matches all parent
+namespaces respectively and the constants that make the rule apply are known at
+that time.
+
+However, autoloading happens on demand. If by chance the top-level `User` was
+not yet loaded, then Rails assumes a relative reference by contract.
+
+Naming conflicts of this kind are rare in practice, but if one occurs,
+`require_dependency` provides a solution by ensuring that the constant needed
+to trigger the heuristic is defined in the conflicting place.
+
+### Automatic Modules
+
+When a module acts as a namespace, Rails does not require the application to
+defines a file for it, a directory matching the namespace is enough.
+
+Suppose an application has a back office whose controllers are stored in
+`app/controllers/admin`. If the `Admin` module is not yet loaded when
+`Admin::UsersController` is hit, Rails needs first to autoload the constant
+`Admin`.
+
+If `autoload_paths` has a file called `admin.rb` Rails is going to load that
+one, but if there's no such file and a directory called `admin` is found, Rails
+creates an empty module and assigns it to the `Admin` constant on the fly.
+
+### Generic Procedure
+
+Relative references are reported to be missing in the cref where they were hit,
+and qualified references are reported to be missing in their parent. (See
+[Resolution Algorithm for Relative
+Constants](#resolution-algorithm-for-relative-constants) at the beginning of
+this guide for the definition of *cref*, and [Resolution Algorithm for Qualified
+Constants](#resolution-algorithm-for-qualified-constants) for the definition of
+*parent*.)
+
+The procedure to autoload constant `C` in an arbitrary situation is as follows:
+
+```
+if the class or module in which C is missing is Object
+ let ns = ''
+else
+ let M = the class or module in which C is missing
+
+ if M is anonymous
+ let ns = ''
+ else
+ let ns = M.name
+ end
+end
+
+loop do
+ # Look for a regular file.
+ for dir in autoload_paths
+ if the file "#{dir}/#{ns.underscore}/c.rb" exists
+ load/require "#{dir}/#{ns.underscore}/c.rb"
+
+ if C is now defined
+ return
+ else
+ raise LoadError
+ end
+ end
+ end
+
+ # Look for an automatic module.
+ for dir in autoload_paths
+ if the directory "#{dir}/#{ns.underscore}/c" exists
+ if ns is an empty string
+ let C = Module.new in Object and return
+ else
+ let C = Module.new in ns.constantize and return
+ end
+ end
+ end
+
+ if ns is empty
+ # We reached the top-level without finding the constant.
+ raise NameError
+ else
+ if C exists in any of the parent namespaces
+ # Qualified constants heuristic.
+ raise NameError
+ else
+ # Try again in the parent namespace.
+ let ns = the parent namespace of ns and retry
+ end
+ end
+end
+```
+
+
+require_dependency
+------------------
+
+Constant autoloading is triggered on demand and therefore code that uses a
+certain constant may have it already defined or may trigger an autoload. That
+depends on the execution path and it may vary between runs.
+
+There are times, however, in which you want to make sure a certain constant is
+known when the execution reaches some code. `require_dependency` provides a way
+to load a file using the current [loading mechanism](#loading-mechanism), and
+keeping track of constants defined in that file as if they were autoloaded to
+have them reloaded as needed.
+
+`require_dependency` is rarely needed, but see a couple of use-cases in
+[Autoloading and STI](#autoloading-and-sti) and [When Constants aren't
+Triggered](#when-constants-aren-t-missed).
+
+WARNING. Unlike autoloading, `require_dependency` does not expect the file to
+define any particular constant. Exploiting this behavior would be a bad practice
+though, file and constant paths should match.
+
+
+Constant Reloading
+------------------
+
+When `config.cache_classes` is false Rails is able to reload autoloaded
+constants.
+
+For example, in you're in a console session and edit some file behind the
+scenes, the code can be reloaded with the `reload!` command:
+
+```
+> reload!
+```
+
+When the application runs, code is reloaded when something relevant to this
+logic changes. In order to do that, Rails monitors a number of things:
+
+* `config/routes.rb`.
+
+* Locales.
+
+* Ruby files under `autoload_paths`.
+
+* `db/schema.rb` and `db/structure.sql`.
+
+If anything in there changes, there is a middleware that detects it and reloads
+the code.
+
+Autoloading keeps track of autoloaded constants. Reloading is implemented by
+removing them all from their respective classes and modules using
+`Module#remove_const`. That way, when the code goes on, those constants are
+going to be unknown again, and files reloaded on demand.
+
+INFO. This is an all-or-nothing operation, Rails does not attempt to reload only
+what changed since dependencies between classes makes that really tricky.
+Instead, everything is wiped.
+
+
+Module#autoload isn't Involved
+------------------------------
+
+`Module#autoload` provides a lazy way to load constants that is fully integrated
+with the Ruby constant lookup algorithms, dynamic constant API, etc. It is quite
+transparent.
+
+Rails internals make extensive use of it to defer as much work as possible from
+the boot process. But constant autoloading in Rails is **not** implemented with
+`Module#autoload`.
+
+One possible implementation based on `Module#autoload` would be to walk the
+application tree and issue `autoload` calls that map existing file names to
+their conventional constant name.
+
+There are a number of reasons that prevent Rails from using that implementation.
+
+For example, `Module#autoload` is only capable of loading files using `require`,
+so reloading would not be possible. Not only that, it uses an internal `require`
+which is not `Kernel#require`.
+
+Then, it provides no way to remove declarations in case a file is deleted. If a
+constant gets removed with `Module#remove_const` its `autoload` is not triggered
+again. Also, it doesn't support qualified names, so files with namespaces should
+be interpreted during the walk tree to install their own `autoload` calls, but
+those files could have constant references not yet configured.
+
+An implementation based on `Module#autoload` would be awesome but, as you see,
+at least as of today it is not possible. Constant autoloading in Rails is
+implemented with `Module#const_missing`, and that's why it has its own contract,
+documented in this guide.
+
+
+Common Gotchas
+--------------
+
+### Nesting and Qualified Constants
+
+Let's consider
+
+```ruby
+module Admin
+ class UsersController < ApplicationController
+ def index
+ @users = User.all
+ end
+ end
+end
+```
+
+and
+
+```ruby
+class Admin::UsersController < ApplicationController
+ def index
+ @users = User.all
+ end
+end
+```
+
+To resolve `User` Ruby checks `Admin` in the former case, but it does not in
+the latter because it does not belong to the nesting. (See [Nesting](#nesting)
+and [Resolution Algorithms](#resolution-algorithms).)
+
+Unfortunately Rails autoloading does not know the nesting in the spot where the
+constant was missing and so it is not able to act as Ruby would. In particular,
+`Admin::User` will get autoloaded in either case.
+
+Albeit qualified constants with `class` and `module` keywords may technically
+work with autoloading in some cases, it is preferable to use relative constants
+instead:
+
+```ruby
+module Admin
+ class UsersController < ApplicationController
+ def index
+ @users = User.all
+ end
+ end
+end
+```
+
+### Autoloading and STI
+
+Single Table Inheritance (STI) is a feature of Active Record that enables
+storing a hierarchy of models in one single table. The API of such models is
+aware of the hierarchy and encapsulates some common needs. For example, given
+these classes:
+
+```ruby
+# app/models/polygon.rb
+class Polygon < ActiveRecord::Base
+end
+
+# app/models/triangle.rb
+class Triangle < Polygon
+end
+
+# app/models/rectangle.rb
+class Rectangle < Polygon
+end
+```
+
+`Triangle.create` creates a row that represents a triangle, and
+`Rectangle.create` creates a row that represents a rectangle. If `id` is the
+ID of an existing record, `Polygon.find(id)` returns an object of the correct
+type.
+
+Methods that operate on collections are also aware of the hierarchy. For
+example, `Polygon.all` returns all the records of the table, because all
+rectangles and triangles are polygons. Active Record takes care of returning
+instances of their corresponding class in the result set.
+
+Types are autoloaded as needed. For example, if `Polygon.first` is a rectangle
+and `Rectangle` has not yet been loaded, Active Record autoloads it and the
+record is correctly instantiated.
+
+All good, but if instead of performing queries based on the root class we need
+to work on some subclass, things get interesting.
+
+While working with `Polygon` you do not need to be aware of all its descendants,
+because anything in the table is by definition a polygon, but when working with
+subclasses Active Record needs to be able to enumerate the types it is looking
+for. Let’s see an example.
+
+`Rectangle.all` only loads rectangles by adding a type constraint to the query:
+
+```sql
+SELECT "polygons".* FROM "polygons"
+WHERE "polygons"."type" IN ("Rectangle")
+```
+
+Let’s introduce now a subclass of `Rectangle`:
+
+```ruby
+# app/models/square.rb
+class Square < Rectangle
+end
+```
+
+`Rectangle.all` should now return rectangles **and** squares:
+
+```sql
+SELECT "polygons".* FROM "polygons"
+WHERE "polygons"."type" IN ("Rectangle", "Square")
+```
+
+But there’s a caveat here: How does Active Record know that the class `Square`
+exists at all?
+
+Even if the file `app/models/square.rb` exists and defines the `Square` class,
+if no code yet used that class, `Rectangle.all` issues the query
+
+```sql
+SELECT "polygons".* FROM "polygons"
+WHERE "polygons"."type" IN ("Rectangle")
+```
+
+That is not a bug, the query includes all *known* descendants of `Rectangle`.
+
+A way to ensure this works correctly regardless of the order of execution is to
+load the leaves of the tree by hand at the bottom of the file that defines the
+root class:
+
+```ruby
+# app/models/polygon.rb
+class Polygon < ActiveRecord::Base
+end
+require_dependency ‘square’
+```
+
+Only the leaves that are **at least grandchildren** need to be loaded this
+way. Direct subclasses do not need to be preloaded. If the hierarchy is
+deeper, intermediate classes will be autoloaded recursively from the bottom
+because their constant will appear in the class definitions as superclass.
+
+### Autoloading and `require`
+
+Files defining constants to be autoloaded should never be `require`d:
+
+```ruby
+require 'user' # DO NOT DO THIS
+
+class UsersController < ApplicationController
+ ...
+end
+```
+
+There are two possible gotchas here in development mode:
+
+1. If `User` is autoloaded before reaching the `require`, `app/models/user.rb`
+runs again because `load` does not update `$LOADED_FEATURES`.
+
+2. If the `require` runs first Rails does not mark `User` as an autoloaded
+constant and changes to `app/models/user.rb` aren't reloaded.
+
+Just follow the flow and use constant autoloading always, never mix
+autoloading and `require`. As a last resort, if some file absolutely needs to
+load a certain file use `require_dependency` to play nice with constant
+autoloading. This option is rarely needed in practice, though.
+
+Of course, using `require` in autoloaded files to load ordinary 3rd party
+libraries is fine, and Rails is able to distinguish their constants, they are
+not marked as autoloaded.
+
+### Autoloading and Initializers
+
+Consider this assignment in `config/initializers/set_auth_service.rb`:
+
+```ruby
+AUTH_SERVICE = if Rails.env.production?
+ RealAuthService
+else
+ MockedAuthService
+end
+```
+
+The purpose of this setup would be that the application uses the class that
+corresponds to the environment via `AUTH_SERVICE`. In development mode
+`MockedAuthService` gets autoloaded when the initializer runs. Let’s suppose
+we do some requests, change its implementation, and hit the application again.
+To our surprise the changes are not reflected. Why?
+
+As [we saw earlier](#constant-reloading), Rails removes autoloaded constants,
+but `AUTH_SERVICE` stores the original class object. Stale, non-reachable
+using the original constant, but perfectly functional.
+
+The following code summarizes the situation:
+
+```ruby
+class C
+ def quack
+ 'quack!'
+ end
+end
+
+X = C
+Object.instance_eval { remove_const(:C) }
+X.new.quack # => quack!
+X.name # => C
+C # => uninitialized constant C (NameError)
+```
+
+Because of that, it is not a good idea to autoload constants on application
+initialization.
+
+In the case above we could implement a dynamic access point:
+
+```ruby
+# app/models/auth_service.rb
+class AuthService
+ if Rails.env.production?
+ def self.instance
+ RealAuthService
+ end
+ else
+ def self.instance
+ MockedAuthService
+ end
+ end
+end
+```
+
+and have the application use `AuthService.instance` instead. `AuthService`
+would be loaded on demand and be autoload-friendly.
+
+### `require_dependency` and Initializers
+
+As we saw before, `require_dependency` loads files in an autoloading-friendly
+way. Normally, though, such a call does not make sense in an initializer.
+
+One could think about doing some [`require_dependency`](#require-dependency)
+calls in an initializer to make sure certain constants are loaded upfront, for
+example as an attempt to address the [gotcha with STIs](#autoloading-and-sti).
+
+Problem is, in development mode [autoloaded constants are wiped](#constant-reloading)
+if there is any relevant change in the file system. If that happens then
+we are in the very same situation the initializer wanted to avoid!
+
+Calls to `require_dependency` have to be strategically written in autoloaded
+spots.
+
+### When Constants aren't Missed
+
+#### Relative References
+
+Let's consider a flight simulator. The application has a default flight model
+
+```ruby
+# app/models/flight_model.rb
+class FlightModel
+end
+```
+
+that can be overridden by each airplane, for instance
+
+```ruby
+# app/models/bell_x1/flight_model.rb
+module BellX1
+ class FlightModel < FlightModel
+ end
+end
+
+# app/models/bell_x1/aircraft.rb
+module BellX1
+ class Aircraft
+ def initialize
+ @flight_model = FlightModel.new
+ end
+ end
+end
+```
+
+The initializer wants to create a `BellX1::FlightModel` and nesting has
+`BellX1`, that looks good. But if the default flight model is loaded and the
+one for the Bell-X1 is not, the interpreter is able to resolve the top-level
+`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`.
+
+That code depends on the execution path.
+
+These kind of ambiguities can often be resolved using qualified constants:
+
+```ruby
+module BellX1
+ class Plane
+ def flight_model
+ @flight_model ||= BellX1::FlightModel.new
+ end
+ end
+end
+```
+
+Also, `require_dependency` is a solution:
+
+```ruby
+require_dependency 'bell_x1/flight_model'
+
+module BellX1
+ class Plane
+ def flight_model
+ @flight_model ||= FlightModel.new
+ end
+ end
+end
+```
+
+#### Qualified References
+
+Given
+
+```ruby
+# app/models/hotel.rb
+class Hotel
+end
+
+# app/models/image.rb
+class Image
+end
+
+# app/models/hotel/image.rb
+class Hotel
+ class Image < Image
+ end
+end
+```
+
+the expression `Hotel::Image` is ambiguous because it depends on the execution
+path.
+
+As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks
+up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has
+been loaded but `app/models/hotel/image.rb` hasn't, Ruby does not find `Image`
+in `Hotel`, but it does in `Object`:
+
+```
+$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null
+Image # NOT Hotel::Image!
+```
+
+The code evaluating `Hotel::Image` needs to make sure
+`app/models/hotel/image.rb` has been loaded, possibly with
+`require_dependency`.
+
+In these cases the interpreter issues a warning though:
+
+```
+warning: toplevel constant Image referenced by Hotel::Image
+```
+
+This surprising constant resolution can be observed with any qualifying class:
+
+```
+2.1.5 :001 > String::Array
+(irb):1: warning: toplevel constant Array referenced by String::Array
+ => Array
+```
+
+WARNING. To find this gotcha the qualifying namespace has to be a class,
+`Object` is not an ancestor of modules.
+
+### Autoloading within Singleton Classes
+
+Let's suppose we have these class definitions:
+
+```ruby
+# app/models/hotel/services.rb
+module Hotel
+ class Services
+ end
+end
+
+# app/models/hotel/geo_location.rb
+module Hotel
+ class GeoLocation
+ class << self
+ Services
+ end
+ end
+end
+```
+
+If `Hotel::Services` is known by the time `app/models/hotel/geo_location.rb`
+is being loaded, `Services` is resolved by Ruby because `Hotel` belongs to the
+nesting when the singleton class of `Hotel::GeoLocation` is opened.
+
+But if `Hotel::Services` is not known, Rails is not able to autoload it, the
+application raises `NameError`.
+
+The reason is that autoloading is triggered for the singleton class, which is
+anonymous, and as [we saw before](#generic-procedure), Rails only checks the
+top-level namespace in that edge case.
+
+An easy solution to this caveat is to qualify the constant:
+
+```ruby
+module Hotel
+ class GeoLocation
+ class << self
+ Hotel::Services
+ end
+ end
+end
+```
+
+### Autoloading in `BasicObject`
+
+Direct descendants of `BasicObject` do not have `Object` among their ancestors
+and cannot resolve top-level constants:
+
+```ruby
+class C < BasicObject
+ String # NameError: uninitialized constant C::String
+end
+```
+
+When autoloading is involved that plot has a twist. Let's consider:
+
+```ruby
+class C < BasicObject
+ def user
+ User # WRONG
+ end
+end
+```
+
+Since Rails checks the top-level namespace `User` gets autoloaded just fine the
+first time the `user` method is invoked. You only get the exception if the
+`User` constant is known at that point, in particular in a *second* call to
+`user`:
+
+```ruby
+c = C.new
+c.user # surprisingly fine, User
+c.user # NameError: uninitialized constant C::User
+```
+
+because it detects that a parent namespace already has the constant (see [Qualified
+References](#autoloading-algorithms-qualified-references).)
+
+As with pure Ruby, within the body of a direct descendant of `BasicObject` use
+always absolute constant paths:
+
+```ruby
+class C < BasicObject
+ ::String # RIGHT
+
+ def user
+ ::User # RIGHT
+ end
+end
+```
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index cbcd053950..61b991df61 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Caching with Rails: An overview
===============================
@@ -105,7 +107,7 @@ This method generates a cache key that depends on all products and can be used i
<% end %>
```
-If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless`
+If you want to cache a fragment under certain conditions, you can use `cache_if` or `cache_unless`
```erb
<% cache_if (condition, cache_key_for_products) do %>
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index e72bc81766..7567a38aef 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
The Rails Command Line
======================
@@ -24,7 +26,7 @@ There are a few commands that are absolutely critical to your everyday usage of
* `rails dbconsole`
* `rails new app_name`
-All commands can run with ```-h or --help``` to list more information.
+All commands can run with `-h` or `--help` to list more information.
Let's create a simple Rails application to step through each of these commands in context.
@@ -61,7 +63,7 @@ With no further work, `rails server` will run our new shiny Rails app:
$ cd commandsapp
$ bin/rails server
=> Booting WEBrick
-=> Rails 4.2.0 application starting in development on http://localhost:3000
+=> Rails 5.0.0 application starting in development on http://localhost:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2013-08-07 02:00:01] INFO WEBrick 1.3.1
@@ -151,9 +153,9 @@ $ bin/rails generate controller Greetings hello
create app/helpers/greetings_helper.rb
invoke assets
invoke coffee
- create app/assets/javascripts/greetings.js.coffee
+ create app/assets/javascripts/greetings.coffee
invoke scss
- create app/assets/stylesheets/greetings.css.scss
+ create app/assets/stylesheets/greetings.scss
```
What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a JavaScript file and a stylesheet file.
@@ -239,11 +241,11 @@ $ bin/rails generate scaffold HighScore game:string score:integer
create app/views/high_scores/show.json.jbuilder
invoke assets
invoke coffee
- create app/assets/javascripts/high_scores.js.coffee
+ create app/assets/javascripts/high_scores.coffee
invoke scss
- create app/assets/stylesheets/high_scores.css.scss
+ create app/assets/stylesheets/high_scores.scss
invoke scss
- identical app/assets/stylesheets/scaffolds.css.scss
+ identical app/assets/stylesheets/scaffolds.scss
```
The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything.
@@ -284,7 +286,7 @@ If you wish to test out some code without changing any data, you can do that by
```bash
$ bin/rails console --sandbox
-Loading development environment in sandbox (Rails 4.2.0)
+Loading development environment in sandbox (Rails 5.0.0)
Any modifications you make will be rolled back on exit
irb(main):001:0>
```
@@ -368,7 +370,7 @@ Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'mak
You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing `rake --tasks`. Each task has a description, and should help you find the thing you need.
-To get the full backtrace for running rake task you can pass the option ```--trace``` to command line, for example ```rake db:create --trace```.
+To get the full backtrace for running rake task you can pass the option `--trace` to command line, for example `rake db:create --trace`.
```bash
$ bin/rake --tasks
@@ -381,10 +383,10 @@ rake db:create # Create the database from config/database.yml for the c
rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
rake middleware # Prints out your Rack middleware stack
...
-rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)
-rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids
+rake tmp:clear # Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)
+rake tmp:create # Creates tmp directories for cache, sockets, and pids
```
-INFO: You can also use ```rake -T``` to get the list of tasks.
+INFO: You can also use `rake -T` to get the list of tasks.
### `about`
@@ -393,10 +395,10 @@ INFO: You can also use ```rake -T``` to get the list of tasks.
```bash
$ bin/rake about
About your application's environment
-Rails version 4.2.0
-Ruby version 1.9.3 (x86_64-linux)
-RubyGems version 1.3.6
-Rack version 1.3
+Rails version 5.0.0
+Ruby version 2.2.0 (x86_64-linux)
+RubyGems version 2.4.5
+Rack version 1.6
JavaScript Runtime Node.js (V8)
Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
Application root /home/foobar/commandsapp
@@ -494,15 +496,14 @@ Rails comes with a test suite called Minitest. Rails owes its stability to the u
### `tmp`
-The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for sessions), process id files, and cached actions.
+The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like process id files and cached actions.
The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory:
* `rake tmp:cache:clear` clears `tmp/cache`.
-* `rake tmp:sessions:clear` clears `tmp/sessions`.
* `rake tmp:sockets:clear` clears `tmp/sockets`.
-* `rake tmp:clear` clears all the three: cache, sessions and sockets.
-* `rake tmp:create` creates tmp directories for sessions, cache, sockets, and pids.
+* `rake tmp:clear` clears all cache and sockets files.
+* `rake tmp:create` creates tmp directories for cache, sockets and pids.
### Miscellaneous
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 2957232186..5baab8a4b5 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Configuring Rails Applications
==============================
@@ -120,7 +122,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`.
-* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. NGINX or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app.
+* `config.serve_static_files` configures Rails to serve static files. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
* `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified:
@@ -197,7 +199,7 @@ The full set of methods that can be used in this block are as follows:
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
* `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`.
-* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_assets` is `false`.
+* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_files` is `false`.
* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`.
* `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
* `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request.
@@ -214,7 +216,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value.
* `ActionDispatch::ParamsParser` parses out parameters from the request into `params`.
* `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types.
-* `ActionDispatch::Head` converts HEAD requests to GET requests and serves them as so.
+* `Rack::Head` converts HEAD requests to GET requests and serves them as so.
Besides these usual middleware, you can add your own by using the `config.middleware.use` method:
@@ -225,13 +227,13 @@ config.middleware.use Magical::Unicorns
This will put the `Magical::Unicorns` middleware on the end of the stack. You can use `insert_before` if you wish to add a middleware before another.
```ruby
-config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns
+config.middleware.insert_before Rack::Head, Magical::Unicorns
```
There's also `insert_after` which will insert a middleware after another:
```ruby
-config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns
+config.middleware.insert_after Rack::Head, Magical::Unicorns
```
Middlewares can also be completely swapped out and replaced with others:
@@ -318,6 +320,8 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8".
+* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`.
+
* `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging.
* `config.action_controller.request_forgery_protection_token` sets the token parameter name for RequestForgery. Calling `protect_from_forgery` sets it to `:authenticity_token` by default.
@@ -503,6 +507,8 @@ There are a few configuration options available in Active Support:
* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
+* `config.active_support.halt_callback_chains_on_return_false` specifies whether ActiveRecord, ActiveModel and ActiveModel::Validations callback chains can be halted by returning `false` in a 'before' callback. Defaults to `true`.
+
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
* `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 4eb360cc7a..db3f19f8ac 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Contributing to Ruby on Rails
=============================
@@ -24,7 +26,7 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge
### Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you do not find any issue addressing it you may proceed to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
+If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
@@ -205,7 +207,7 @@ Rails follows a simple set of coding style conventions:
* Use Ruby >= 1.9 syntax for hashes. Prefer `{ a: :b }` over `{ :a => :b }`.
* Prefer `&&`/`||` over `and`/`or`.
* Prefer class << self over self.method for class methods.
-* Use `MyClass.my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`.
+* Use `my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`.
* Use `a = b` and not `a=b`.
* Use assert_not methods instead of refute.
* Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks.
@@ -287,7 +289,12 @@ $ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
The `-n` option allows you to run a single method instead of the whole
file.
-##### Testing Active Record
+#### Testing Active Record
+
+First, create the databases you'll need. For MySQL and PostgreSQL,
+running the SQL statements `create database activerecord_unittest` and
+`create database activerecord_unittest2` is sufficient. This is not
+necessary for SQLite3.
This is how you run the Active Record test suite only for SQLite3:
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 1a647f8375..cef9ac083b 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Debugging Rails Applications
============================
@@ -309,7 +311,7 @@ For example:
```bash
=> Booting WEBrick
-=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000
+=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
@@ -422,11 +424,11 @@ then `backtrace` will supply the answer.
--> #0 ArticlesController.index
at /PathTo/project/test_app/app/controllers/articles_controller.rb:8
#1 ActionController::ImplicitRender.send_action(method#String, *args#Array)
- at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/implicit_render.rb:4
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/implicit_render.rb:4
#2 AbstractController::Base.process_action(action#NilClass, *args#Array)
- at /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb:189
+ at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189
#3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass)
- at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/rendering.rb:10
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10
...
```
@@ -438,7 +440,7 @@ context.
```
(byebug) frame 2
-[184, 193] in /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb
+[184, 193] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb
184: # is the intended way to override action dispatching.
185: #
186: # Notice that the first argument is the method to be dispatched
@@ -655,7 +657,7 @@ instruction to be executed. In this case, the activesupport's `week` method.
```
(byebug) step
-[50, 59] in /PathToGems/activesupport-4.2.0/lib/active_support/core_ext/numeric/time.rb
+[50, 59] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb
50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
51: end
52: alias :day :days
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 3d9ec578ae..9eacc3a2fe 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Development Dependencies Install
================================
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 4c98d3e1d5..67032a31f5 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -85,9 +85,9 @@
description: This guide provides you with all you need to get started in creating, enqueueing and executing background jobs.
-
name: Testing Rails Applications
- url: testing.html
work_in_progress: true
- description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy.
+ url: testing.html
+ description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy.
-
name: Securing Rails Applications
url: security.html
@@ -113,15 +113,25 @@
url: working_with_javascript_in_rails.html
description: This guide covers the built-in Ajax/JavaScript functionality of Rails.
-
- name: Getting Started with Engines
- url: engines.html
- description: This guide explains how to write a mountable engine.
- work_in_progress: true
- -
name: The Rails Initialization Process
work_in_progress: true
url: initialization.html
description: This guide explains the internals of the Rails initialization process as of Rails 4
+ -
+ name: Autoloading and Reloading Constants
+ url: autoloading_and_reloading_constants.html
+ description: This guide documents how autoloading and reloading constants work.
+ -
+ name: Active Support Instrumentation
+ work_in_progress: true
+ url: active_support_instrumentation.html
+ description: This guide explains how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code.
+ -
+ name: Profiling Rails Applications
+ work_in_progress: true
+ url: profiling.html
+ description: This guide explains how to profile your Rails applications to improve performance.
+
-
name: Extending Rails
documents:
@@ -138,6 +148,11 @@
name: Creating and Customizing Rails Generators
url: generators.html
description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator).
+ -
+ name: Getting Started with Engines
+ url: engines.html
+ description: This guide explains how to write a mountable engine.
+ work_in_progress: true
-
name: Contributing to Ruby on Rails
documents:
@@ -171,7 +186,6 @@
name: Ruby on Rails 4.2 Release Notes
url: 4_2_release_notes.html
description: Release notes for Rails 4.2.
- work_in_progress: true
-
name: Ruby on Rails 4.1 Release Notes
url: 4_1_release_notes.html
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 21ac941ac0..731178787f 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Getting Started with Engines
============================
@@ -32,7 +34,7 @@ directory structure, and are both generated using the `rails plugin new`
generator. The difference is that an engine is considered a "full plugin" by
Rails (as indicated by the `--full` option that's passed to the generator
command). We'll actually be using the `--mountable` option here, which includes
-all the features of `--full`, and then some. This guide will refer to these
+all the features of `--full`, and then some. This guide will refer to these
"full plugins" simply as "engines" throughout. An engine **can** be a plugin,
and a plugin **can** be an engine.
@@ -1036,31 +1038,42 @@ functionality, especially controllers. This means that if you were to make a
typical `GET` to a controller in a controller's functional test like this:
```ruby
-get :index
+module Blorgh
+ class FooControllerTest < ActionController::TestCase
+ def test_index
+ get :index
+ ...
+ end
+ end
+end
```
It may not function correctly. This is because the application doesn't know how
to route these requests to the engine unless you explicitly tell it **how**. To
-do this, you must also pass the `:use_route` option as a parameter on these
-requests:
+do this, you must set the `@routes` instance variable to the engine's route set
+in your setup code:
```ruby
-get :index, use_route: :blorgh
+module Blorgh
+ class FooControllerTest < ActionController::TestCase
+ setup do
+ @routes = Engine.routes
+ end
+
+ def test_index
+ get :index
+ ...
+ end
+ end
+end
```
This tells the application that you still want to perform a `GET` request to the
`index` action of this controller, but you want to use the engine's route to get
there, rather than the application's one.
-Another way to do this is to assign the `@routes` instance variable to `Engine.routes` in your test setup:
-
-```ruby
-setup do
- @routes = Engine.routes
-end
-```
-
-This will also ensure url helpers for the engine will work as expected in your tests.
+This also ensures that the engine's URL helpers will work as expected in your
+tests.
Improving engine functionality
------------------------------
@@ -1155,7 +1168,7 @@ end
Using `Class#class_eval` is great for simple adjustments, but for more complex
class modifications, you might want to consider using [`ActiveSupport::Concern`]
-(http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html).
+(http://api.rubyonrails.org/classes/ActiveSupport/Concern.html).
ActiveSupport::Concern manages load order of interlinked dependent modules and
classes at run time allowing you to significantly modularize your code.
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index cb45e38614..a8dcd3ee4f 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Form Helpers
============
@@ -96,7 +98,15 @@ form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_
### Helpers for Generating Form Elements
-Rails provides a series of helpers for generating form elements such as checkboxes, text fields, and radio buttons. These basic helpers, with names ending in "_tag" (such as `text_field_tag` and `check_box_tag`), generate just a single `<input>` element. The first parameter to these is always the name of the input. When the form is submitted, the name will be passed along with the form data, and will make its way to the `params` hash in the controller with the value entered by the user for that field. For example, if the form contains `<%= text_field_tag(:query) %>`, then you would be able to get the value of this field in the controller with `params[:query]`.
+Rails provides a series of helpers for generating form elements such as
+checkboxes, text fields, and radio buttons. These basic helpers, with names
+ending in `_tag` (such as `text_field_tag` and `check_box_tag`), generate just a
+single `<input>` element. The first parameter to these is always the name of the
+input. When the form is submitted, the name will be passed along with the form
+data, and will make its way to the `params` hash in the controller with the
+value entered by the user for that field. For example, if the form contains `<%=
+text_field_tag(:query) %>`, then you would be able to get the value of this
+field in the controller with `params[:query]`.
When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in [chapter 7 of this guide](#understanding-parameter-naming-conventions). For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html).
diff --git a/guides/source/generators.md b/guides/source/generators.md
index f5d2c67cb4..05bf07b4c8 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Creating and Customizing Rails Generators & Templates
=====================================================
@@ -197,11 +199,11 @@ $ bin/rails generate scaffold User name:string
create app/views/users/show.json.jbuilder
invoke assets
invoke coffee
- create app/assets/javascripts/users.js.coffee
+ create app/assets/javascripts/users.coffee
invoke scss
- create app/assets/stylesheets/users.css.scss
+ create app/assets/stylesheets/users.scss
invoke scss
- create app/assets/stylesheets/scaffolds.css.scss
+ create app/assets/stylesheets/scaffolds.scss
```
Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
@@ -407,7 +409,7 @@ $ bin/rails generate scaffold Comment body:text
create app/views/comments/show.json.jbuilder
invoke assets
invoke coffee
- create app/assets/javascripts/comments.js.coffee
+ create app/assets/javascripts/comments.coffee
invoke scss
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 1996158e27..d80622ef00 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Getting Started with Rails
==========================
@@ -90,18 +92,18 @@ current version of Ruby installed:
TIP: A number of tools exist to help you quickly install Ruby and Ruby
on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),
while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp).
+For more installation methods for most Operating Systems take a look at
+[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/).
```bash
$ ruby -v
ruby 2.0.0p353
```
-If you don't have Ruby installed have a look at
-[ruby-lang.org](https://www.ruby-lang.org/en/installation/) for possible ways to
-install Ruby on your platform.
-
-Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows
-users and others can find installation instructions at [the SQLite3 website](https://www.sqlite.org).
+Many popular UNIX-like OSes ship with an acceptable version of SQLite3.
+On Windows, if you installed Rails through Rails Installer, you
+already have SQLite installed. Others can find installation instructions
+at the [SQLite3 website](https://www.sqlite.org).
Verify that it is correctly installed and in your PATH:
```bash
@@ -123,7 +125,7 @@ run the following:
$ rails --version
```
-If it says something like "Rails 4.2.0", you are ready to continue.
+If it says something like "Rails 5.0.0", you are ready to continue.
### Creating the Blog Application
@@ -165,14 +167,14 @@ of the files and folders that Rails created by default:
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
|config.ru|Rack configuration for Rack based servers used to start the application.|
|db/|Contains your current database schema, as well as the database migrations.|
-|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://bundler.io).|
+|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](http://bundler.io).|
|lib/|Extended modules for your application.|
|log/|Application log files.|
|public/|The only folder seen by the world as-is. Contains static files and compiled assets.|
|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.|
|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).|
-|tmp/|Temporary files (like cache, pid, and session files).|
+|tmp/|Temporary files (like cache and pid files).|
|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.|
Hello, Rails!
@@ -259,9 +261,9 @@ invoke helper
create app/helpers/welcome_helper.rb
invoke assets
invoke coffee
-create app/assets/javascripts/welcome.js.coffee
+create app/assets/javascripts/welcome.coffee
invoke scss
-create app/assets/stylesheets/welcome.css.scss
+create app/assets/stylesheets/welcome.scss
```
Most important of these are of course the controller, located at
@@ -300,8 +302,9 @@ Rails.application.routes.draw do
# ...
```
-This is your application's _routing file_ which holds entries in a special DSL
-(domain-specific language) that tells Rails how to connect incoming requests to
+This is your application's _routing file_ which holds entries in a special
+[DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language)
+that tells Rails how to connect incoming requests to
controllers and actions. This file contains many sample routes on commented
lines, and one of them actually shows you how to connect the root of your site
to a specific controller and action. Find the line beginning with `root` and
@@ -422,12 +425,12 @@ If you refresh <http://localhost:3000/articles/new> now, you'll get a new error:
This error indicates that Rails cannot find the `new` action inside the
`ArticlesController` that you just generated. This is because when controllers
are generated in Rails they are empty by default, unless you tell it
-your wanted actions during the generation process.
+your desired actions during the generation process.
To manually define an action inside a controller, all you need to do is to
define a new method inside the controller. Open
`app/controllers/articles_controller.rb` and inside the `ArticlesController`
-class, define a `new` method so that the controller now looks like this:
+class, define the `new` method so that your controller now looks like this:
```ruby
class ArticlesController < ApplicationController
@@ -444,23 +447,23 @@ With the `new` method defined in `ArticlesController`, if you refresh
You're getting this error now because Rails expects plain actions like this one
to have views associated with them to display their information. With no view
-available, Rails errors out.
+available, Rails will raise an exception.
In the above image, the bottom line has been truncated. Let's see what the full
-thing looks like:
+error message looks like:
>Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
That's quite a lot of text! Let's quickly go through and understand what each
-part of it does.
+part of it means.
-The first part identifies what template is missing. In this case, it's the
+The first part identifies which template is missing. In this case, it's the
`articles/new` template. Rails will first look for this template. If not found,
then it will attempt to load a template called `application/new`. It looks for
one here because the `ArticlesController` inherits from `ApplicationController`.
The next part of the message contains a hash. The `:locale` key in this hash
-simply indicates what spoken language template should be retrieved. By default,
+simply indicates which spoken language template should be retrieved. By default,
this is the English - or "en" - template. The next key, `:formats` specifies the
format of template to be served in response. The default format is `:html`, and
so Rails is looking for an HTML template. The final key, `:handlers`, is telling
@@ -473,14 +476,16 @@ Templates within a basic Rails application like this are kept in a single
location, but in more complex applications it could be many different paths.
The simplest template that would work in this case would be one located at
-`app/views/articles/new.html.erb`. The extension of this file name is key: the
-first extension is the _format_ of the template, and the second extension is the
-_handler_ that will be used. Rails is attempting to find a template called
-`articles/new` within `app/views` for the application. The format for this
-template can only be `html` and the handler must be one of `erb`, `builder` or
-`coffee`. Because you want to create a new HTML form, you will be using the `ERB`
-language. Therefore the file should be called `articles/new.html.erb` and needs
-to be located inside the `app/views` directory of the application.
+`app/views/articles/new.html.erb`. The extension of this file name is important:
+the first extension is the _format_ of the template, and the second extension
+is the _handler_ that will be used. Rails is attempting to find a template
+called `articles/new` within `app/views` for the application. The format for
+this template can only be `html` and the handler must be one of `erb`,
+`builder` or `coffee`. Because you want to create a new HTML form, you will be
+using the `ERB` language which is designed to embed Ruby in HTML.
+
+Therefore the file should be called `articles/new.html.erb` and needs to be
+located inside the `app/views` directory of the application.
Go ahead now and create a new file at `app/views/articles/new.html.erb` and
write this content in it:
@@ -665,8 +670,8 @@ rake commands to run migrations, and it's possible to undo a migration after
it's been applied to your database. Migration filenames include a timestamp to
ensure that they're processed in the order that they were created.
-If you look in the `db/migrate/20140120191729_create_articles.rb` file (remember,
-yours will have a slightly different name), here's what you'll find:
+If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file
+(remember, yours will have a slightly different name), here's what you'll find:
```ruby
class CreateArticles < ActiveRecord::Migration
@@ -675,7 +680,7 @@ class CreateArticles < ActiveRecord::Migration
t.string :title
t.text :text
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -736,7 +741,7 @@ database columns. In the first line we do just that (remember that
`@article.save` is responsible for saving the model in the database. Finally,
we redirect the user to the `show` action, which we'll define later.
-TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `\models\article.rb`. Class names in Ruby must begin with a capital letter.
+TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `app/models/article.rb`. Class names in Ruby must begin with a capital letter.
TIP: As we'll see later, `@article.save` returns a boolean indicating whether
the article was saved or not.
@@ -833,7 +838,7 @@ class ArticlesController < ApplicationController
A couple of things to note. We use `Article.find` to find the article we're
interested in, passing in `params[:id]` to get the `:id` parameter from the
-request. We also use an instance variable (prefixed by `@`) to hold a
+request. We also use an instance variable (prefixed with `@`) to hold a
reference to the article object. We do this because Rails will pass all instance
variables to the view.
@@ -1279,7 +1284,7 @@ And here's how our app looks so far:
Our `edit` page looks very similar to the `new` page; in fact, they
both share the same code for displaying the form. Let's remove this
duplication by using a view partial. By convention, partial files are
-prefixed by an underscore.
+prefixed with an underscore.
TIP: You can read more about partials in the
[Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
@@ -1537,8 +1542,9 @@ class CreateComments < ActiveRecord::Migration
# this line adds an integer column called `article_id`.
t.references :article, index: true
- t.timestamps
+ t.timestamps null: false
end
+ add_foreign_key :comments, :articles
end
end
```
@@ -1558,6 +1564,8 @@ run against the current database, so in this case you will just see:
== CreateComments: migrating =================================================
-- create_table(:comments)
-> 0.0115s
+-- add_foreign_key(:comments, :articles)
+ -> 0.0000s
== CreateComments: migrated (0.0119s) ========================================
```
@@ -1635,8 +1643,8 @@ This creates five files and one empty directory:
| app/views/comments/ | Views of the controller are stored here |
| test/controllers/comments_controller_test.rb | The test for the controller |
| app/helpers/comments_helper.rb | A view helper file |
-| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller |
-| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller |
+| app/assets/javascripts/comment.coffee | CoffeeScript for the controller |
+| app/assets/stylesheets/comment.scss | Cascading style sheet for the controller |
Like with any blog, our readers will create their comments directly after
reading the article, and once they have added their comment, will be sent back
@@ -2029,9 +2037,11 @@ What's Next?
------------
Now that you've seen your first Rails application, you should feel free to
-update it and experiment on your own. But you don't have to do everything
-without help. As you need assistance getting up and running with Rails, feel
-free to consult these support resources:
+update it and experiment on your own.
+
+Remember you don't have to do everything without help. As you need assistance
+getting up and running with Rails, feel free to consult these support
+resources:
* The [Ruby on Rails Guides](index.html)
* The [Ruby on Rails Tutorial](http://railstutorial.org/book)
@@ -2049,7 +2059,7 @@ command-line utility:
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 RedCloth and Nokogiri gems. Add it to your `Gemfile` and run
+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 f6cbc1823a..bd6babff41 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Rails Internationalization (I18n) API
=====================================
@@ -626,7 +628,7 @@ entry[count == 1 ? 0 : 1]
I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero).
-If the lookup for the key does not return a Hash suitable for pluralization, an `18n::InvalidPluralizationData` exception is raised.
+If the lookup for the key does not return a Hash suitable for pluralization, an `I18n::InvalidPluralizationData` exception is raised.
### Setting and Passing a Locale
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 53bf3039fa..a93ceb7fb5 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
The Rails Initialization Process
================================
@@ -357,7 +359,7 @@ private
end
def create_tmp_directories
- %w(cache pids sessions sockets).each do |dir_to_make|
+ %w(cache pids sockets).each do |dir_to_make|
FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
end
end
@@ -373,13 +375,12 @@ private
end
```
-This is where the first output of the Rails initialization happens. This
-method creates a trap for `INT` signals, so if you `CTRL-C` the server,
-it will exit the process. As we can see from the code here, it will
-create the `tmp/cache`, `tmp/pids`, `tmp/sessions` and `tmp/sockets`
-directories. It then calls `wrapped_app` which is responsible for
-creating the Rack app, before creating and assigning an
-instance of `ActiveSupport::Logger`.
+This is where the first output of the Rails initialization happens. This method
+creates a trap for `INT` signals, so if you `CTRL-C` the server, it will exit the
+process. As we can see from the code here, it will create the `tmp/cache`,
+`tmp/pids`, and `tmp/sockets` directories. It then calls `wrapped_app` which is
+responsible for creating the Rack app, before creating and assigning an instance
+of `ActiveSupport::Logger`.
The `super` method will call `Rack::Server.start` which begins its definition like this:
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index ae16ad86cd..c3cde49630 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Layouts and Rendering in Rails
==============================
@@ -425,6 +427,9 @@ Rails understands both numeric status codes and the corresponding symbols shown
| | 510 | :not_extended |
| | 511 | :network_authentication_required |
+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.
+
#### 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.
@@ -904,7 +909,10 @@ You can also specify multiple videos to play by passing an array of videos to th
This will produce:
```erb
-<video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+<video>
+ <source src="/videos/trailer.ogg">
+ <source src="/videos/movie.ogg">
+</video>
```
#### Linking to Audio Files with the `audio_tag`
@@ -1022,6 +1030,42 @@ One way to use partials is to treat them as the equivalent of subroutines: as a
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.
+As you already could see from the previous sections of this guide, `yield` is a very powerful tool for cleaning up your layouts. Keep in mind that it's pure ruby, so you can use it almost everywhere. For example, we can use it to DRY form layout definition for several similar resources:
+
+* `users/index.html.erb`
+
+ ```html+erb
+ <%= render "shared/search_filters", search: @q do |f| %>
+ <p>
+ Name contains: <%= f.text_field :name_contains %>
+ </p>
+ <%= end %>
+ ```
+
+* `roles/index.html.erb`
+
+ ```html+erb
+ <%= render "shared/search_filters", search: @q do |f| %>
+ <p>
+ Title contains: <%= f.text_field :title_contains %>
+ </p>
+ <%= end %>
+ ```
+
+* `shared/_search_filters.html.erb`
+
+ ```html+erb
+ <%= form_for(@q) do |f| %>
+ <h1>Search form:</h1>
+ <fieldset>
+ <%= yield f %>
+ </fieldset>
+ <p>
+ <%= f.submit "Search" %>
+ </p>
+ <% end %>
+ ```
+
TIP: For content that is shared among all pages in your application, you can use partials directly from layouts.
#### Partial Layouts
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 050a64ddf3..45cdc549f7 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Maintenance Policy for Ruby on Rails
====================================
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index f0ee34cfb1..44236ad239 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Rails Nested Model Forms
========================
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 7b7eb80081..bd884441ac 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
The Basics of Creating Rails Plugins
====================================
@@ -433,7 +435,7 @@ Once your README is solid, go through and add rdoc comments to all of the method
Once your comments are good to go, navigate to your plugin directory and run:
```bash
-$ bin/rake rdoc
+$ bundle exec rake rdoc
```
### References
diff --git a/guides/source/profiling.md b/guides/source/profiling.md
new file mode 100644
index 0000000000..695b09647f
--- /dev/null
+++ b/guides/source/profiling.md
@@ -0,0 +1,16 @@
+*DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
+A Guide to Profiling Rails Applications
+=======================================
+
+This guide covers built-in mechanisms in Rails for profiling your application.
+
+After reading this guide, you will know:
+
+* Rails profiling terminology.
+* How to write benchmark tests for your application.
+* Other benchmarking approaches and plugins.
+
+--------------------------------------------------------------------------------
+
+
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 6512b14e60..0db777b9bb 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Rails Application Templates
===========================
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 8bc2678d8f..561a3d9392 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Rails on Rack
=============
@@ -61,7 +63,6 @@ Here's how it loads the middlewares:
```ruby
def middleware
middlewares = []
- middlewares << [Rails::Rack::Debugger] if options[:debugger]
middlewares << [::Rack::ContentLength]
Hash.new(middlewares)
end
@@ -233,7 +234,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`ActionDispatch::Static`**
-* Used to serve static assets. Disabled if `config.serve_static_assets` is `false`.
+* Used to serve static files. Disabled if `config.serve_static_files` is `false`.
**`Rack::Lock`**
@@ -277,7 +278,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`ActionDispatch::Callbacks`**
-* Runs the prepare callbacks before serving the request.
+* Provides callbacks to be executed before and after dispatching the request.
**`ActiveRecord::Migration::CheckPending`**
@@ -307,7 +308,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Parses out parameters from the request into `params`.
-**`ActionDispatch::Head`**
+**`Rack::Head`**
* Converts HEAD requests to `GET` requests and serves them as so.
diff --git a/guides/source/routing.md b/guides/source/routing.md
index b1a287f53a..893cedeefc 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Rails Routing from the Outside In
=================================
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index c0438f6341..0ecf8a80df 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails Guides Guidelines
===============================
diff --git a/guides/source/security.md b/guides/source/security.md
index 125dd82666..e4cc79df55 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Ruby on Rails Security Guide
============================
@@ -237,7 +239,7 @@ Or the attacker places the code into the onmouseover event handler of an image:
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
```
-There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request.
+There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since `XMLHttpRequest` is subject to the browser Same-Origin policy - meaning only your site can initiate the request.
To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications:
@@ -247,6 +249,15 @@ protect_from_forgery with: :exception
This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown.
+NOTE: By default, Rails includes jQuery and an [unobtrusive scripting adapter for
+jQuery](https://github.com/rails/jquery-ujs), which adds a header called
+`X-CSRF-Token` on every non-GET Ajax call made by jQuery with the security token.
+Without this header, non-GET Ajax requests won't be accepted by Rails. When using
+another library to make Ajax calls, it is necessary to add the security token as
+a default header for Ajax calls in your library. To get the token, have a look at
+`<meta name='csrf-token' content='THE-TOKEN'>` tag printed by
+`<%= csrf_meta_tags %>` in your application view.
+
It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
```ruby
@@ -362,7 +373,7 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend
**CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
-A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
+A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.

@@ -942,7 +953,7 @@ unless params[:token].nil?
end
```
-When `params[:token]` is one of: `[]`, `[nil]`, `[nil, nil, ...]` or
+When `params[:token]` is one of: `[nil]`, `[nil, nil, ...]` or
`['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or
`IN ('foo', NULL)` where clauses still will be added to the SQL query.
@@ -953,9 +964,9 @@ request:
| JSON | Parameters |
|-----------------------------------|--------------------------|
| `{ "person": null }` | `{ :person => nil }` |
-| `{ "person": [] }` | `{ :person => nil }` |
-| `{ "person": [null] }` | `{ :person => nil }` |
-| `{ "person": [null, null, ...] }` | `{ :person => nil }` |
+| `{ "person": [] }` | `{ :person => [] }` |
+| `{ "person": [null] }` | `{ :person => [] }` |
+| `{ "person": [null, null, ...] }` | `{ :person => [] }` |
| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` |
It is possible to return to old behaviour and disable `deep_munge` configuring
diff --git a/guides/source/testing.md b/guides/source/testing.md
index b4c70dfa1d..21b0b37efa 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
A Guide to Testing Rails Applications
=====================================
@@ -29,11 +31,13 @@ Testing support was woven into the Rails fabric from the beginning. It wasn't an
By default, every Rails application has three environments: development, test, and production. The database for each one of them is configured in `config/database.yml`.
-A dedicated test database allows you to set up and interact with test data in isolation. Tests can mangle test data with confidence, that won't touch the data in the development or production databases.
+A dedicated test database allows you to set up and interact with test data in isolation. This way your tests can mangle test data with confidence, without worrying about the data in the development or production databases.
+
+Also, each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`.
### Rails Sets up for Testing from the Word Go
-Rails creates a `test` folder for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this folder then you shall see:
+Rails creates a `test` directory for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this directory then you shall see:
```bash
$ ls -F test
@@ -41,9 +45,9 @@ controllers/ helpers/ mailers/ test_helper.rb
fixtures/ integration/ models/
```
-The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting.
+The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. There is also a directory for testing your mailers and one for testing view helpers.
-Fixtures are a way of organizing test data; they reside in the `fixtures` folder.
+Fixtures are a way of organizing test data; they reside in the `fixtures` directory.
The `test_helper.rb` file holds the default configuration for your tests.
@@ -51,7 +55,7 @@ The `test_helper.rb` file holds the default configuration for your tests.
For good tests, you'll need to give some thought to setting up test data.
In Rails, you can handle this by defining and customizing fixtures.
-You can find comprehensive documentation in the [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+You can find comprehensive documentation in the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
#### What Are Fixtures?
@@ -61,7 +65,7 @@ You'll find fixtures under your `test/fixtures` directory. When you run `rails g
#### YAML
-YAML-formatted fixtures are a very human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`).
+YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`).
Here's a sample YAML fixture file:
@@ -78,7 +82,7 @@ steve:
profession: guy with keyboard
```
-Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them.
+Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column.
If you are working with [associations](/association_basics.html), you can simply
define a reference node between two different fixtures. Here's an example with
@@ -96,11 +100,9 @@ one:
category: about
```
-Note: For associations to reference one another by name, you cannot specify the `id:`
- attribute on the fixtures. Rails will auto assign a primary key to be consistent between
- runs. If you manually specify an `id:` attribute, this behavior will not work. For more
- information on this association behavior please read the
- [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`.
+
+NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
#### ERB'in It Up
@@ -116,15 +118,17 @@ user_<%= n %>:
#### Fixtures in Action
-Rails by default automatically loads all fixtures from the `test/fixtures` folder for your models and controllers test. Loading involves three steps:
+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 variable in case you want to access it directly
+* 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))
#### Fixtures are Active Record objects
-Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example:
+Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically available as a method whose scope is local of the test case. For example:
```ruby
# this will return the User object for the fixture named david
@@ -137,16 +141,35 @@ users(:david).id
email(david.girlfriend.email, david.location_tonight)
```
+### Rake Tasks for Running your Tests
+
+Rails comes with a number of built-in rake tasks to help with testing. The
+table below lists the commands included in the default Rakefile when a Rails
+project is created.
+
+| Tasks | Description |
+| ----------------------- | ----------- |
+| `rake test` | Runs all tests in the `test` directory. You can also run `rake` and Rails will run all tests by default |
+| `rake test:controllers` | Runs all the controller tests from `test/controllers` |
+| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` |
+| `rake test:helpers` | Runs all the helper tests from `test/helpers` |
+| `rake test:integration` | Runs all the integration tests from `test/integration` |
+| `rake test:jobs` | Runs all the job tests from `test/jobs` |
+| `rake test:mailers` | Runs all the mailer tests from `test/mailers` |
+| `rake test:models` | Runs all the model tests from `test/models` |
+| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` |
+| `rake test:db` | Runs all tests in the `test` directory and resets the db |
+
+We will cover each of types Rails tests listed above in this guide.
+
Unit Testing your Models
------------------------
-In Rails, models tests are what you write to test your models.
-
-For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. We will be using examples from this generated code and will be supplementing it with additional examples where necessary.
+In Rails, unit tests are what you write to test your models.
-NOTE: For more information on Rails _scaffolding_, refer to [Getting Started with Rails](getting_started.html)
+For this guide we will be using the application we built in the [Getting Started with Rails](getting_started.html) guide.
-When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder:
+If you remember when you used the `rails generate scaffold` command from earlier. We created our first resource among other things it created a test stub in the `test/models` directory:
```bash
$ bin/rails generate scaffold article title:string body:text
@@ -175,18 +198,18 @@ A line by line examination of this file will help get you oriented to Rails test
require 'test_helper'
```
-As you know by now, `test_helper.rb` specifies the default configuration to run our tests. This is included with all the tests, so any methods added to this file are available to all your tests.
+By requiring this file, `test_helper.rb` the default configuration to run our tests is loaded. We will include this with all the tests we write, so any methods added to this file are available to all your tests.
```ruby
class ArticleTest < ActiveSupport::TestCase
```
-The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide.
+The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. Later in this guide, you'll see some of the methods it gives you.
Any method defined within a class inherited from `Minitest::Test`
-(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run.
+(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, methods defined as `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run.
-Rails adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So,
+Rails also adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So you don't have to worry about naming the methods, and you can write something like:
```ruby
test "the truth" do
@@ -194,7 +217,7 @@ test "the truth" do
end
```
-acts as if you had written
+Which is approximately the same as writing this:
```ruby
def test_the_truth
@@ -202,26 +225,37 @@ def test_the_truth
end
```
-only the `test` macro allows a more readable test name. You can still use regular method definitions though.
+However only the `test` macro allows a more readable test name. You can still use regular method definitions though.
-NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. Odd ones need `define_method` and `send` calls, but formally there's no restriction.
+NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. This may require use of `define_method` and `send` calls to function properly, but formally there's little restriction on the name.
+
+Next, let's look at our first assertion:
```ruby
assert true
```
-This line of code is called an _assertion_. An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check:
+An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check:
* does this value = that value?
* is this object nil?
* does this line of code throw an exception?
* is the user's password greater than 5 characters?
-Every test contains one or more assertions. Only when all the assertions are successful will the test pass.
+Every test must contain at least one assertion, with no restriction as to how many assertions are allowed. Only when all the assertions are successful will the test pass.
### Maintaining the test database schema
-In order to run your tests, your test database will need to have the current structure. The test helper checks whether your test database has any pending migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql` into the test database. If migrations are still pending, an error will be raised.
+In order to run your tests, your test database will need to have the current
+structure. The test helper checks whether your test database has any pending
+migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql`
+into the test database. If migrations are still pending, an error will be
+raised. Usually this indicates that your schema is not fully migrated. Running
+the migrations against the development database (`bin/rake db:migrate`) will
+bring the schema up to date.
+
+NOTE: If existing migrations required modifications, the test database needs to
+be rebuilt. This can be done by executing `bin/rake db:test:prepare`.
### Running Tests
@@ -247,10 +281,12 @@ 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. Note that `test_helper.rb` is in the `test` directory, hence this directory needs to be added to the load path using the `-I` switch.
+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
+
To see how a test failure is reported, you can add a failing test to the `article_test.rb` test case.
```ruby
@@ -313,7 +349,7 @@ Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
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).
-TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an excellent way to build up a test suite that exercises every part of your application. TDD is beyond the scope of this guide, but one place to start is with [15 TDD steps to create a Rails application](http://andrzejonsoftware.blogspot.com/2007/05/15-tdd-steps-to-create-rails.html).
+#### What an error looks like
To see how an error gets reported, here's a test containing an error:
@@ -356,52 +392,26 @@ behavior:
$ BACKTRACE=1 bin/rake test test/models/article_test.rb
```
-### What to Include in Your Unit Tests
+If we want this test to pass we can modify it to use `assert_raises` like so:
-Ideally, you would like to include a test for everything which could possibly break. It's a good practice to have at least one test for each of your validations and at least one test for every method in your model.
+```ruby
+test "should report error" do
+ # some_undefined_variable is not defined elsewhere in the test case
+ assert_raises(NameError) do
+ some_undefined_variable
+ end
+end
+```
+
+This test should now pass.
### Available Assertions
By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned.
-There are a bunch of different types of assertions you can use.
-Here's an extract of the assertions you can use with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required.
-
-| Assertion | Purpose |
-| ---------------------------------------------------------------- | ------- |
-| `assert( test, [msg] )` | Ensures that `test` is true.|
-| `assert_not( test, [msg] )` | Ensures that `test` is false.|
-| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.|
-| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
-| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.|
-| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
-| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.|
-| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
-| `assert_empty( obj, [msg] )` | Ensures that `obj` is `empty?`.|
-| `assert_not_empty( obj, [msg] )` | Ensures that `obj` is not `empty?`.|
-| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.|
-| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
-| `assert_includes( collection, obj, [msg] )` | Ensures that `obj` is in `collection`.|
-| `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.|
-| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
-| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
-| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
-| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
-| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.|
-| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
-| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
-| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.|
-| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.|
-| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.|
-| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
-| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
-| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
-| `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`|
-| `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`|
-| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?|
-| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
-
-The above are subset of assertions that minitest supports. For an exhaustive & more up-to-date list, please check [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
+There are a bunch of different types of assertions you can use that come with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails.
+
+For a list of all available assertions please check the [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier.
@@ -423,10 +433,24 @@ Rails adds some custom assertions of its own to the `minitest` framework:
You'll see the usage of some of these assertions in the next chapter.
+### A Brief Note About Minitest
+
+All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from:
+
+* `ActiveSupport::TestCase`
+* `ActionController::TestCase`
+* `ActionMailer::TestCase`
+* `ActionView::TestCase`
+* `ActionDispatch::IntegrationTest`
+
+Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
+
+NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
+
Functional Tests for Your Controllers
-------------------------------------
-In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view.
+In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you're testing how your actions handle the requests and the expected result, or response in some cases an HTML view.
### What to Include in your Functional Tests
@@ -500,13 +524,13 @@ If you're familiar with the HTTP protocol, you'll know that `get` is a type of r
* `head`
* `delete`
-All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others.
+All of request types have equivalent methods that you can use. In a typical C.R.U.D. application you'll be using `get`, `post`, `put` and `delete` more often.
-NOTE: Functional tests do not verify whether the specified request type should be accepted by the action. Request types in this context exist to make your tests more descriptive.
+NOTE: Functional tests do not verify whether the specified request type is accepted by the action, we're more concerned with the result. Request tests exist for this use case to make your tests more purposeful.
### The Four Hashes of the Apocalypse
-After a request has been made using one of the 6 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use:
+After a request has been made and processed, you will have 4 Hash objects ready for use:
* `assigns` - Any objects that are stored as instance variables in actions for use in views.
* `cookies` - Any cookies that are set.
@@ -529,8 +553,8 @@ assigns["something"] assigns(:something)
You also have access to three instance variables in your functional tests:
* `@controller` - The controller processing the request
-* `@request` - The request
-* `@response` - The response
+* `@request` - The request object
+* `@response` - The response object
### Setting Headers and CGI variables
@@ -551,6 +575,10 @@ post :create # simulate the request with custom env variable
### Testing Templates and Layouts
+Eventually, you may want to test whether a specific layout is rendered in the view of a response.
+
+#### Asserting Templates
+
If you want to make sure that the response rendered the correct template and layout, you can use the `assert_template`
method:
@@ -559,24 +587,22 @@ test "index should render correct template and layout" do
get :index
assert_template :index
assert_template layout: "layouts/application"
+
+ # You can also pass a regular expression.
+ assert_template layout: /layouts\/application/
end
```
-Note that you cannot test for template and layout at the same time, with one call to `assert_template` method.
-Also, for the `layout` test, you can give a regular expression instead of a string, but using the string, makes
-things clearer. On the other hand, you have to include the "layouts" directory name even if you save your layout
-file in this standard layout directory. Hence,
+NOTE: You cannot test for template and layout at the same time, with a single call to `assert_template`.
-```ruby
-assert_template layout: "application"
-```
+WARNING: You must include the "layouts" directory name even if you save your layout file in this standard layout directory. Hence, `assert_template layout: "application"` will not work.
-will not work.
+#### Asserting Partials
-If your view renders any partial, when asserting for the layout, you have to assert for the partial at the same time.
+If your view renders any partial, when asserting for the layout, you can to assert for the partial at the same time.
Otherwise, assertion will fail.
-Hence:
+Remember, we added the "_form" partial to our creating Articles view? Let's write an assertion for that in the `:new` action now:
```ruby
test "new should render correct layout" do
@@ -585,27 +611,201 @@ test "new should render correct layout" do
end
```
-is the correct way to assert for the layout when the view renders a partial with name `_form`. Omitting the `:partial` key in your `assert_template` call will complain.
+This is the correct way to assert for when the view renders a partial with a given name. As identified by the `:partial` key passed to the `assert_template` call.
+
+### Testing `flash` notices
+
+If you remember from earlier one of the Four Hashes of the Apocalypse was `flash`.
-### A Fuller Functional Test Example
+We want to add a `flash` message to our blog application whenever someone
+successfully creates a new Article.
-Here's another example that uses `flash`, `assert_redirected_to`, and `assert_difference`:
+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: 'Hi', body: 'This is my first article.'}
+ post :create, article: {title: 'Some title'}
end
+
assert_redirected_to article_path(assigns(:article))
assert_equal 'Article was successfully created.', flash[:notice]
end
```
-### Testing Views
+If we run our test now, we should see a failure:
+
+```bash
+$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article
+Run options: -n test_should_create_article --seed 32266
+
+# Running:
+
+F
+
+Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.
+
+ 1) Failure:
+ArticlesControllerTest#test_should_create_article [/Users/zzak/code/bench/sharedapp/test/controllers/articles_controller_test.rb:16]:
+--- expected
++++ actual
+@@ -1 +1 @@
+-"Article was successfully created."
++nil
+
+1 runs, 4 assertions, 1 failures, 0 errors, 0 skips
+```
+
+Let's implement the flash message now in our controller. Our `:create` action should now look like this:
+
+```ruby
+def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ flash[:notice] = 'Article was successfully created.'
+ redirect_to @article
+ else
+ render 'new'
+ end
+end
+```
+
+Now if we run our tests, we should see it pass:
-Testing the response to your request by asserting the presence of key HTML elements and their content is a useful way to test the views of your application. The `assert_select` assertion allows you to do this by using a simple yet powerful syntax.
+```bash
+$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article
+Run options: -n test_should_create_article --seed 18981
+
+# Running:
+
+.
+
+Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.
-NOTE: You may find references to `assert_tag` in other documentation. This has been removed in 4.2. Use `assert_select` instead.
+1 runs, 4 assertions, 0 failures, 0 errors, 0 skips
+```
+
+### Putting it together
+
+At this point our Articles controller tests the `:index` as well as `:new` and `:create` actions. What about dealing with existing data?
+
+Let's write a test for the `:show` action:
+
+```ruby
+test "should show article" do
+ article = articles(:one)
+ get :show, id: article.id
+ assert_response :success
+end
+```
+
+Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures.
+
+How about deleting an existing Article?
+
+```ruby
+test "should destroy article" do
+ article = articles(:one)
+ assert_difference('Article.count', -1) do
+ delete :destroy, id: article.id
+ end
+
+ assert_redirected_to articles_path
+end
+```
+
+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"}
+ assert_redirected_to article_path(assigns(:article))
+end
+```
+
+Notice we're starting to see some duplication in these three tests, they both access the same Article fixture data. We can D.R.Y. this up by using the `setup` and `teardown` methods provided by `ActiveSupport::Callbacks`.
+
+Our test should now look something like this, disregard the other tests we're leaving them out for brevity.
+
+```ruby
+require 'test_helper'
+
+class ArticlesControllerTest < ActionController::TestCase
+ # called before every single test
+ def setup
+ @article = articles(:one)
+ end
+
+ # called after every single test
+ def teardown
+ # as we are re-initializing @article before every test
+ # setting it to nil here is not essential but I hope
+ # you understand how you can use the teardown method
+ @article = nil
+ end
+
+ test "should show article" do
+ # Reuse the @article instance variable from setup
+ get :show, id: @article.id
+ assert_response :success
+ end
+
+ test "should destroy article" do
+ assert_difference('Article.count', -1) do
+ delete :destroy, id: @article.id
+ end
+
+ assert_redirected_to articles_path
+ end
+
+ test "should update article" do
+ patch :update, id: @article.id, article: {title: "updated"}
+ assert_redirected_to article_path(assigns(:article))
+ end
+end
+```
+
+Similar to other callbacks in Rails, the `setup` and `teardown` methods can also be used by passing a block, lambda, or method name as a symbol to call.
+
+Testing Routes
+--------------
+
+Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like:
+
+```ruby
+class ArticleRoutesTest < ActionController::TestCase
+ test "should route to article" do
+ assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" }
+ end
+
+ test "should route to create article" do
+ assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" })
+ end
+end
+```
+
+I've added this file here `test/controllers/articles_routes_test.rb` and if we run the test we should see:
+
+```bash
+$ bin/rake test test/controllers/articles_routes_test.rb
+
+# Running:
+
+..
+
+Finished in 0.069381s, 28.8263 runs/s, 86.4790 assertions/s.
+
+2 runs, 6 assertions, 0 failures, 0 errors, 0 skips
+```
+
+For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html).
+
+Testing Views
+-------------
+
+Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax.
There are two forms of `assert_select`:
@@ -619,7 +819,10 @@ For example, you could verify the contents on the title element in your response
assert_select 'title', "Welcome to Rails Testing Guide"
```
-You can also use nested `assert_select` blocks. In this case the inner `assert_select` runs the assertion on the complete collection of elements selected by the outer `assert_select` block:
+You can also use nested `assert_select` blocks for deeper investigation.
+
+In the following example, the inner `assert_select` for `li.menu_item` runs
+within the collection of elements selected by the outer block:
```ruby
assert_select 'ul.navigation' do
@@ -627,7 +830,9 @@ assert_select 'ul.navigation' do
end
```
-Alternatively the collection of elements selected by the outer `assert_select` may be iterated through so that `assert_select` may be called separately for each element. Suppose for example that the response contains two ordered lists, each with four list elements then the following tests will both pass.
+A collection of selected elements may be iterated through so that `assert_select` may be called separately for each element.
+
+For example if the response contains two ordered lists, each with four nested list elements then the following tests will both pass.
```ruby
assert_select "ol" do |elements|
@@ -641,7 +846,7 @@ assert_select "ol" do
end
```
-The `assert_select` assertion is quite powerful. For more advanced usage, refer to its [documentation](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb).
+This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing).
#### Additional View-Based Assertions
@@ -661,12 +866,45 @@ assert_select_email do
end
```
+Testing helpers
+---------------
+
+In order to test helpers, all you need to do is check that the output of the
+helper method matches what you'd expect. Tests related to the helpers are
+located under the `test/helpers` directory.
+
+A helper test looks like so:
+
+```ruby
+require 'test_helper'
+
+class UserHelperTest < ActionView::TestCase
+end
+```
+
+A helper is just a simple module where you can define methods which are
+available into your views. To test the output of the helper's methods, you just
+have to use a mixin like this:
+
+```ruby
+class UserHelperTest < ActionView::TestCase
+ include UserHelper
+
+ test "should return the user name" do
+ # ...
+ end
+end
+```
+
+Moreover, since the test class extends from `ActionView::TestCase`, you have
+access to Rails' helper methods such as `link_to` or `pluralize`.
+
Integration Testing
-------------------
-Integration tests are used to test the interaction among any number of controllers. They are generally used to test important work flows within your application.
+Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application.
-Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you.
+For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you.
```bash
$ bin/rails generate integration_test user_flows
@@ -686,233 +924,92 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
end
```
-Integration tests inherit from `ActionDispatch::IntegrationTest`. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test.
+Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests.
### Helpers Available for Integration Tests
-In addition to the standard testing helpers, there are some additional helpers available to integration tests:
+In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from.
-| Helper | Purpose |
-| ------------------------------------------------------------------ | ------- |
-| `https?` | Returns `true` if the session is mimicking a secure HTTPS request.|
-| `https!` | Allows you to mimic a secure HTTPS request.|
-| `host!` | Allows you to set the host name to use in the next request.|
-| `redirect?` | Returns `true` if the last request was a redirect.|
-| `follow_redirect!` | Follows a single redirect response.|
-| `request_via_redirect(http_method, path, [parameters], [headers])` | Allows you to make an HTTP request and follow any subsequent redirects.|
-| `post_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP POST request and follow any subsequent redirects.|
-| `get_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP GET request and follow any subsequent redirects.|
-| `patch_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PATCH request and follow any subsequent redirects.|
-| `put_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PUT request and follow any subsequent redirects.|
-| `delete_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP DELETE request and follow any subsequent redirects.|
-| `open_session` | Opens a new session instance.|
+For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html).
-### Integration Testing Examples
+When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use.
-A simple integration test that exercises multiple controllers:
+If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help.
-```ruby
-require 'test_helper'
+### Implementing an integration test
-class UserFlowsTest < ActionDispatch::IntegrationTest
- test "login and browse site" do
- # login via https
- https!
- get "/login"
- assert_response :success
+Let's add an integration test to our blog application. We'll start with a basic workflow of creating a new blog article, to verify that everything is working properly.
- post_via_redirect "/login", username: users(:david).username, password: users(:david).password
- assert_equal '/welcome', path
- assert_equal 'Welcome david!', flash[:notice]
+We'll start by generating our integration test skeleton:
- https!(false)
- get "/articles/all"
- assert_response :success
- assert assigns(:articles)
- end
-end
+```bash
+$ bin/rails generate integration_test blog_flow
```
-As you can see the integration test involves multiple controllers and exercises the entire stack from database to dispatcher. In addition you can have multiple session instances open simultaneously in a test and extend those instances with assertion methods to create a very powerful testing DSL (domain-specific language) just for your application.
+It should have created a test file placeholder for us, with the output of the previous command you should see:
-Here's an example of multiple sessions and custom DSL in an integration test
+```bash
+ invoke test_unit
+ create test/integration/blog_flow_test.rb
+```
+
+Now let's open that file and write our first assertion:
```ruby
require 'test_helper'
-class UserFlowsTest < ActionDispatch::IntegrationTest
- test "login and browse site" do
- # User david logs in
- david = login(:david)
- # User guest logs in
- guest = login(:guest)
-
- # Both are now available in different sessions
- assert_equal 'Welcome david!', david.flash[:notice]
- assert_equal 'Welcome guest!', guest.flash[:notice]
-
- # User david can browse site
- david.browses_site
- # User guest can browse site as well
- guest.browses_site
-
- # Continue with other assertions
+class BlogFlowTest < ActionDispatch::IntegrationTest
+ test "can see the welcome page" do
+ get "/"
+ assert_select "h1", "Welcome#index"
end
-
- private
-
- module CustomDsl
- def browses_site
- get "/products/all"
- assert_response :success
- assert assigns(:products)
- end
- end
-
- def login(user)
- open_session do |sess|
- sess.extend(CustomDsl)
- u = users(user)
- sess.https!
- sess.post "/login", username: u.username, password: u.password
- assert_equal '/welcome', sess.path
- sess.https!(false)
- end
- end
end
```
-Rake Tasks for Running your Tests
----------------------------------
-
-Rails comes with a number of built-in rake tasks to help with testing. The
-table below lists the commands included in the default Rakefile when a Rails
-project is created.
+If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request.
-| Tasks | Description |
-| ----------------------- | ----------- |
-| `rake test` | Runs all tests in the `test` folder. You can also simply run `rake` as Rails will run all the tests by default |
-| `rake test:controllers` | Runs all the controller tests from `test/controllers` |
-| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` |
-| `rake test:helpers` | Runs all the helper tests from `test/helpers` |
-| `rake test:integration` | Runs all the integration tests from `test/integration` |
-| `rake test:jobs` | Runs all the job tests from `test/jobs` |
-| `rake test:mailers` | Runs all the mailer tests from `test/mailers` |
-| `rake test:models` | Runs all the model tests from `test/models` |
-| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` |
-| `rake test:db` | Runs all tests in the `test` folder and resets the db |
-
-
-A Brief Note About Minitest
------------------------------
-
-Ruby ships with a vast Standard Library for all common use-cases including testing. Since version 1.9, Ruby provides `Minitest`, a framework for testing. All the basic assertions such as `assert_equal` discussed above are actually defined in `Minitest::Assertions`. The classes `ActiveSupport::TestCase`, `ActionController::TestCase`, `ActionMailer::TestCase`, `ActionView::TestCase` and `ActionDispatch::IntegrationTest` - which we have been inheriting in our test classes - include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
+When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
-NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
-
-Setup and Teardown
-------------------
+#### Creating articles integration
-If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Articles` controller:
+How about testing our ability to create a new article in our blog and see the resulting article.
```ruby
-require 'test_helper'
-
-class ArticlesControllerTest < ActionController::TestCase
-
- # called before every single test
- def setup
- @article = articles(:one)
- end
-
- # called after every single test
- def teardown
- # as we are re-initializing @article before every test
- # setting it to nil here is not essential but I hope
- # you understand how you can use the teardown method
- @article = nil
- end
-
- test "should show article" do
- get :show, id: @article.id
- assert_response :success
- end
-
- test "should destroy article" do
- assert_difference('Article.count', -1) do
- delete :destroy, id: @article.id
- end
-
- assert_redirected_to articles_path
- end
-
+test "can create an article" do
+ get "/articles/new"
+ assert_response :success
+ assert_template "articles/new", partial: "articles/_form"
+
+ post "/articles", article: {title: "can create", body: "article successfully."}
+ assert_response :redirect
+ follow_redirect!
+ assert_response :success
+ assert_template "articles/show"
+ assert_select "p", "Title:\n can create"
end
```
-Above, the `setup` method is called before each test and so `@article` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using:
+Let's break this test down so we can understand it.
-* a block
-* a method (like in the earlier example)
-* a method name as a symbol
-* a lambda
+We start by calling the `:new` action on our Articles controller. This response should be successful, and we can verify the correct template is rendered including the form partial.
-Let's see the earlier example by specifying `setup` callback by specifying a method name as a symbol:
+After this we make a post request to the `:create` action of our Articles controller:
```ruby
-require 'test_helper'
-
-class ArticlesControllerTest < ActionController::TestCase
-
- # called before every single test
- setup :initialize_article
-
- # called after every single test
- def teardown
- @article = nil
- end
-
- test "should show article" do
- get :show, id: @article.id
- assert_response :success
- end
-
- test "should update article" do
- patch :update, id: @article.id, article: {}
- assert_redirected_to article_path(assigns(:article))
- end
-
- test "should destroy article" do
- assert_difference('Article.count', -1) do
- delete :destroy, id: @article.id
- end
-
- assert_redirected_to articles_path
- end
-
- private
-
- def initialize_article
- @article = articles(:one)
- end
-end
+post "/articles", article: {title: "can create", body: "article successfully."}
+assert_response :redirect
+follow_redirect!
```
-Testing Routes
---------------
+The two lines following the request are to handle the redirect we setup when creating a new article.
-Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like:
+NOTE: Don't forget to call `follow_redirect!` if you plan to make subsequent requests after a redirect is made.
-```ruby
-class ArticleRoutesTest < ActionController::TestCase
- test "should route to article" do
- assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" }
- end
+Finally we can assert that our response was successful, template was rendered, and our new article is readable on the page.
- test "should route to create article" do
- assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" })
- end
-end
-```
+#### Taking it further
+
+We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editting comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
Testing Your Mailers
--------------------
@@ -1014,39 +1111,6 @@ class UserControllerTest < ActionController::TestCase
end
```
-Testing helpers
----------------
-
-In order to test helpers, all you need to do is check that the output of the
-helper method matches what you'd expect. Tests related to the helpers are
-located under the `test/helpers` directory.
-
-A helper test looks like so:
-
-```ruby
-require 'test_helper'
-
-class UserHelperTest < ActionView::TestCase
-end
-```
-
-A helper is just a simple module where you can define methods which are
-available into your views. To test the output of the helper's methods, you just
-have to use a mixin like this:
-
-```ruby
-class UserHelperTest < ActionView::TestCase
- include UserHelper
-
- test "should return the user name" do
- # ...
- end
-end
-```
-
-Moreover, since the test class extends from `ActionView::TestCase`, you have
-access to Rails' helper methods such as `link_to` or `pluralize`.
-
Testing Jobs
------------
@@ -1080,17 +1144,7 @@ no jobs have already been executed in the scope of each test.
### Custom Assertions And Testing Jobs Inside Other Components
-Active Job ships with a bunch of custom assertions that can be used to lessen
-the verbosity of tests:
-
-| Assertion | Purpose |
-| -------------------------------------- | ------- |
-| `assert_enqueued_jobs(number)` | Asserts that the number of enqueued jobs matches the given number. |
-| `assert_performed_jobs(number)` | Asserts that the number of performed jobs matches the given number. |
-| `assert_no_enqueued_jobs { ... }` | Asserts that no jobs have been enqueued. |
-| `assert_no_performed_jobs { ... }` | Asserts that no jobs have been performed. |
-| `assert_enqueued_with([args]) { ... }` | Asserts that the job passed in the block has been enqueued with the given arguments. |
-| `assert_performed_with([args]) { ... }`| Asserts that the job passed in the block has been performed with the given arguments. |
+Active Job ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActiveJob::TestHelper`](http://api.rubyonrails.org/classes/ActiveJob/TestHelper.html).
It's a good practice to ensure that your jobs correctly get enqueued or performed
wherever you invoke them (e.g. inside your controllers). This is precisely where
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 6f5dea45b5..c0c94475e0 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
A Guide for Upgrading Ruby on Rails
===================================
@@ -18,9 +20,10 @@ The best way to be sure that your application still works after upgrading is to
Rails generally stays close to the latest released Ruby version when it's released:
-* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible.
-* Rails 3.2.x is the last branch to support Ruby 1.8.7.
+* Rails 5 requires Ruby 2.2 or newer.
* Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer.
+* Rails 3.2.x is the last branch to support Ruby 1.8.7.
+* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible.
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
@@ -47,24 +50,37 @@ Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
Don't forget to review the difference, to see if there were any unexpected changes.
-Upgrading from Rails 4.1 to Rails 4.2
+Upgrading from Rails 4.2 to Rails 5.0
-------------------------------------
-NOTE: This section is a work in progress, please help to improve this by sending
-a [pull request](https://github.com/rails/rails/edit/master/guides/source/upgrading_ruby_on_rails.md).
+### Halting callback chains by returning `false`
-### Web Console
+In Rails 4.2, when a 'before' callback returns `false` in ActiveRecord,
+ActiveModel and ActiveModel::Validations, then the entire callback chain
+is halted. In other words, successive 'before' callbacks are not executed,
+and neither is the action wrapped in callbacks.
-First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment.
+In Rails 5.0, returning `false` in a callback will not have this side effect
+of halting the callback chain. Instead, callback chains must be explicitly
+halted by calling `throw(:abort)`.
-Additionally, you can tell Rails to automatically mount a VT100-compatible console on a predetermined path by setting the appropriate configuration flags in your development config:
+When you upgrade from Rails 4.2 to Rails 5.0, returning `false` in a callback
+will still halt the callback chain, but you will receive a deprecation warning
+about this upcoming change.
-```ruby
-# config/environments/development.rb
+When you are ready, you can opt into the new behavior and remove the deprecation
+warning by adding the following configuration to your `config/application.rb`:
-config.web_console.automount = true
-config.web_console.default_mount_path = '/terminal' # Optional, defaults to /console
-```
+ config.active_support.halt_callback_chains_on_return_false = false
+
+See [#17227](https://github.com/rails/rails/pull/17227) for more details.
+
+Upgrading from Rails 4.1 to Rails 4.2
+-------------------------------------
+
+### Web Console
+
+First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment.
### Responders
@@ -145,6 +161,18 @@ assigning `nil` to a serialized attribute will save it to the database
as `NULL` instead of passing the `nil` value through the coder (e.g. `"null"`
when using the `JSON` coder).
+### Production log level
+
+In Rails 5, the default log level for the production environment will be changed
+to `:debug` (from `:info`). To preserve the current default, add the following
+line to your `production.rb`:
+
+```ruby
+# Set to `:info` to match the current default, or set to `:debug` to opt-into
+# the future default.
+config.log_level = :info
+```
+
### `after_bundle` in Rails templates
If you have a Rails template that adds all the files in version control, it
@@ -207,12 +235,47 @@ gem 'rails-deprecated_sanitizer'
```
### Rails DOM Testing
+
The [`TagAssertions` module](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing).
### Masked Authenticity Tokens
+
In order to mitigate SSL attacks, `form_authenticity_token` is now masked so that it varies with each request. Thus, tokens are validated by unmasking and then decrypting. As a result, any strategies for verifying requests from non-rails forms that relied on a static session CSRF token have to take this into account.
+### Action Mailer
+
+Previously, calling a mailer method on a mailer class will result in the
+corresponding instance method being executed directly. With the introduction of
+Active Job and `#deliver_later`, this is no longer true. In Rails 4.2, the
+invocation of the instance methods are deferred until either `deliver_now` or
+`deliver_later` is called. For example:
+
+```ruby
+class Notifier < ActionMailer::Base
+ def notify(user, ...)
+ puts "Called"
+ mail(to: user.email, ...)
+ end
+end
+
+mail = Notifier.notify(user, ...) # Notifier#notify is not yet called at this point
+mail = mail.deliver_now # Prints "Called"
+```
+
+This should not result in any noticeable differences for most applications.
+However, if you need some non-mailer methods to be executed synchronously, and
+you were previously relying on the synchronous proxying behavior, you should
+define them as class methods on the mailer class directly:
+
+```ruby
+class Notifier < ActionMailer::Base
+ def self.broadcast_notifications(users, ...)
+ users.each { |user| Notifier.notify(user, ...) }
+ end
+end
+```
+
Upgrading from Rails 4.0 to Rails 4.1
-------------------------------------
@@ -731,7 +794,7 @@ file (in `config/application.rb`):
```ruby
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
-Bundler.require(:default, Rails.env)
+Bundler.require(*Rails.groups)
```
### vendor/plugins
@@ -950,7 +1013,7 @@ The following changes are meant for upgrading your application to the latest
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.18'
+gem 'rails', '3.2.21'
group :assets do
gem 'sass-rails', '~> 3.2.6'
@@ -1075,7 +1138,7 @@ You can help test performance with these additions to your test environment:
```ruby
# Configure static asset server for tests with Cache-Control for performance
-config.serve_static_assets = true
+config.serve_static_files = true
config.static_cache_control = 'public, max-age=3600'
```
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 7c3fd9f69d..5131f809d7 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
Working with JavaScript in Rails
================================
diff --git a/rails.gemspec b/rails.gemspec
index 99685252e0..b3143e6fe1 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Full-stack web application framework.'
s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.required_rubygems_version = '>= 1.8.11'
s.license = 'MIT'
@@ -28,5 +28,5 @@ Gem::Specification.new do |s|
s.add_dependency 'railties', version
s.add_dependency 'bundler', '>= 1.3.0', '< 2.0'
- s.add_dependency 'sprockets-rails', '~> 3.0.0.beta1'
+ s.add_dependency 'sprockets-rails'
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index f267be5eb8..623f44d56a 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,194 +1,48 @@
-* `Rails::Paths::Path.unshift` now has the same interface as `Array.unshift`.
-
- *Igor Kapkov*
-
-* Make `rake test` run all tests in test folder.
-
- Deprecate `rake test:all` and replace `rake test:all:db` with `rake test:db`
-
- *David Geukers*
-
-* `secret_token` is now saved in `Rails.application.secrets.secret_token`
- and it falls back to the value of `config.secret_token` when it is not
- present in `config/secrets.yml`.
-
- *Benjamin Fleischer*
-
-* Remove `--skip-action-view` option from `Rails::Generators::AppBase`.
-
- Fixes #17023.
-
- *Dan Olson*
-
-* Specify dummy app's db migrate path in plugin's test_helper.rb.
-
- Fixes #16877.
-
- *Yukio Mizuta*
-
-* Inject `Rack::Lock` if `config.eager_load` is false.
-
- Fixes #15089.
-
- *Xavier Noria*
-
-* Change the path of dummy app location in plugin's test_helper.rb for cases
- you specify dummy_path option.
-
- *Yukio Mizuta*
-
-* Fix a bug in the `gem` method for Rails templates when non-String options
- are used.
-
- Fixes #16709.
-
- *Yves Senn*
-
-* The [web-console](https://github.com/rails/web-console) gem is now
- installed by default for new applications. It can help you debug
- development exceptions by spawning an interactive console in its cause
- binding.
-
- *Ryan Dao*, *Genadi Samokovarov*, *Guillermo Iguaran*
-
-* Add a `required` option to the model generator for associations
-
- *Sean Griffin*
-
-* Add `after_bundle` callbacks in Rails templates. Useful for allowing the
- generated binstubs to be added to version control.
-
- Fixes #16292.
-
- *Stefan Kanev*
-
-* Pull in the custom configuration concept from dhh/custom_configuration, which allows you to
- configure your own code through the Rails configuration object with custom configuration:
-
- # config/environments/production.rb
- config.x.payment_processing.schedule = :daily
- config.x.payment_processing.retries = 3
- config.x.super_debugger = true
-
- These configuration points are then available through the configuration object:
-
- Rails.configuration.x.payment_processing.schedule # => :daily
- Rails.configuration.x.payment_processing.retries # => 3
- Rails.configuration.x.super_debugger # => true
-
- *DHH*
-
-* Scaffold generator `_form` partial adds `class="field"` for password
- confirmation fields.
-
- *noinkling*
-
-* Add `Rails::Application.config_for` to load a configuration for the current
- environment.
-
- # config/exception_notification.yml:
- production:
- url: http://127.0.0.1:8080
- namespace: my_app_production
- development:
- url: http://localhost:3001
- namespace: my_app_development
-
- # config/production.rb
- Rails.application.configure do
- config.middleware.use ExceptionNotifier, config_for(:exception_notification)
- end
-
- *Rafael Mendonça França*, *DHH*
-
-* Deprecate `Rails::Rack::LogTailer` without replacement.
+* Remove deprecated `test:all` and `test:all:db` tasks.
*Rafael Mendonça França*
-* Add `--skip-turbolinks` option to the app generator.
+* Remove deprecated `Rails::Rack::LogTailer`.
*Rafael Mendonça França*
-* Invalid `bin/rails generate` commands will now show spelling suggestions.
-
- *Richard Schneeman*
-
-* Add `bin/setup` script to bootstrap an application.
-
- *Yves Senn*
-
-* Replace double quotes with single quotes while adding an entry into Gemfile.
-
- *Alexander Belaev*
-
-* Default `config.assets.digest` to `true` in development.
-
- *Dan Kang*
-
-* Load database configuration from the first `database.yml` available in paths.
-
- *Pier-Olivier Thibault*
-
-* Reading name and email from git for plugin gemspec.
-
- Fixes #9589.
-
- *Arun Agrawal*, *Abd ar-Rahman Hamidi*, *Roman Shmatov*
-
-* Fix `console` and `generators` blocks defined at different environments.
-
- Fixes #14748.
+* Remove deprecated `RAILS_CACHE` constant.
*Rafael Mendonça França*
-* Move configuration of asset precompile list and version to an initializer.
-
- *Matthew Draper*
+* Remove deprecated `serve_static_assets` configuration.
-* Remove sqlite3 lines from `.gitignore` if the application is not using sqlite3.
-
- *Dmitrii Golub*
-
-* Add public API to register new extensions for `rake notes`.
-
- Example:
-
- config.annotations.register_extensions("scss", "sass") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
-
- *Roberto Miranda*
-
-* Removed unnecessary `rails application` command.
+ *Rafael Mendonça França*
- *Arun Agrawal*
+* Use local variables in `_form.html.erb` partial generated by scaffold.
-* Make the `rails:template` rake task load the application's initializers.
+ *Andrew Kozlov*
- Fixes #12133.
+* Add `config/initializers/callback_terminator.rb`
- *Robin Dupret*
+ Newly generated Rails apps have a new initializer called
+ `callback_terminator.rb` which sets the value of the configuration option
+ `config.active_support.halt_callback_chains_on_return_false` to `false`.
-* Introduce `Rails.gem_version` as a convenience method to return
- `Gem::Version.new(Rails.version)`, suggesting a more reliable way to perform
- version comparison.
+ As a result, new Rails apps do not halt callback chains when a callback
+ returns `false`; only when they are explicitly halted with `throw(:abort)`.
- Example:
+ The terminator is *not* added when running `rake rails:update`, so returning
+ `false` will still work on old apps ported to Rails 5, displaying a
+ deprecation warning to prompt users to update their code to the new syntax.
- Rails.version #=> "4.1.2"
- Rails.gem_version #=> #<Gem::Version "4.1.2">
+ *claudiob*
- Rails.version > "4.1.10" #=> false
- Rails.gem_version > Gem::Version.new("4.1.10") #=> true
- Gem::Requirement.new("~> 4.1.2") =~ Rails.gem_version #=> true
+* Generated fixtures won't use the id when generated with references attributes.
- *Prem Sichanugrist*
+ *Pablo Olmos de Aguilera Corradini*
-* Avoid namespacing routes inside engines.
+* Add `--skip-action-mailer` option to the app generator.
- Mountable engines are namespaced by default so the generated routes
- were too while they should not.
+ *claudiob*
- Fixes #14079.
+* Autoload any second level directories called `app/*/concerns`.
- *Yves Senn*, *Carlos Antonio da Silva*, *Robin Dupret*
+ *Alex Robbin*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/railties/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 2950f05b11..7c2197229d 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index e7172e491f..b1f7c29b5a 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -14,7 +14,7 @@ require 'rails/version'
require 'active_support/railtie'
require 'action_dispatch/railtie'
-# For Ruby 1.9, UTF-8 is the default internal and external encoding.
+# UTF-8 is the default internal and external encoding.
silence_warnings do
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
@@ -56,10 +56,18 @@ module Rails
application && application.config.root
end
+ # Returns the current Rails environment.
+ #
+ # Rails.env # => "development"
+ # Rails.env.development? # => true
+ # Rails.env.production? # => false
def env
@_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development")
end
+ # Sets the Rails environment.
+ #
+ # Rails.env = "staging" # => "staging"
def env=(environment)
@_env = ActiveSupport::StringInquirer.new(environment)
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index f8bd6096f2..8da73db821 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -368,7 +368,21 @@ module Rails
@config = configuration
end
- def secrets #:nodoc:
+ # Returns secrets added to config/secrets.yml.
+ #
+ # Example:
+ #
+ # development:
+ # secret_key_base: 836fa3665997a860728bcb9e9a1e704d427cfc920e79d847d79c8a9a907b9e965defa4154b2b86bdec6930adbe33f21364523a6f6ce363865724549fdfc08553
+ # test:
+ # secret_key_base: 5a37811464e7d378488b0f073e2193b093682e4e21f5d6f3ae0a4e1781e61a351fdc878a843424e81c73fb484a40d23f92c8dafac4870e74ede6e5e174423010
+ # production:
+ # secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+ # namespace: my_app_production
+ #
+ # +Rails.application.secrets.namespace+ returns +my_app_production+ in the
+ # production environment.
+ def secrets
@secrets ||= begin
secrets = ActiveSupport::OrderedOptions.new
yaml = config.paths["config/secrets"].first
@@ -406,16 +420,7 @@ module Rails
console do
unless ::Kernel.private_method_defined?(:y)
- if RUBY_VERSION >= '2.0'
- require "psych/y"
- else
- module ::Kernel
- def y(*objects)
- puts ::Psych.dump_stream(*objects)
- end
- private :y
- end
- end
+ require "psych/y"
end
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 71d3febde4..0f4d932749 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,6 +1,5 @@
require "active_support/notifications"
require "active_support/dependencies"
-require "active_support/deprecation"
require "active_support/descendants_tracker"
module Rails
@@ -55,18 +54,6 @@ INFO
logger
end
- if Rails.env.production? && !config.has_explicit_log_level?
- ActiveSupport::Deprecation.warn \
- "You did not specify a `log_level` in `production.rb`. Currently, " \
- "the default value for `log_level` is `:info` for the production " \
- "environment and `:debug` in all other environments. In Rails 5 " \
- "the default value will be unified to `:debug` across all " \
- "environments. To preserve the current setting, add the following " \
- "line to your `production.rb`:\n" \
- "\n" \
- " config.log_level = :info\n\n"
- end
-
Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 268ef2c7aa..2821c8d757 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -11,10 +11,11 @@ module Rails
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
- :serve_static_assets, :ssl_options, :static_cache_control, :session_options,
+ :serve_static_files, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x
+ attr_writer :log_level
attr_reader :encoding
def initialize(*)
@@ -25,7 +26,7 @@ module Rails
@filter_parameters = []
@filter_redirect = []
@helpers_paths = []
- @serve_static_assets = true
+ @serve_static_files = true
@static_cache_control = nil
@force_ssl = false
@ssl_options = {}
@@ -33,7 +34,6 @@ module Rails
@session_options = {}
@time_zone = "UTC"
@beginning_of_week = :monday
- @has_explicit_log_level = false
@log_level = nil
@middleware = app_middleware
@generators = app_generators
@@ -117,15 +117,6 @@ module Rails
raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace
end
- def has_explicit_log_level? # :nodoc:
- @has_explicit_log_level
- end
-
- def log_level=(level)
- @has_explicit_log_level = !!(level)
- @log_level = level
- end
-
def log_level
@log_level ||= (Rails.env.production? ? :info : :debug)
end
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index d1789192ef..02eea82b0c 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -17,7 +17,7 @@ module Rails
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
- if config.serve_static_assets
+ if config.serve_static_files
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
end
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
index 737977adf9..cf0a4e128f 100644
--- a/railties/lib/rails/application/routes_reloader.rb
+++ b/railties/lib/rails/application/routes_reloader.rb
@@ -41,9 +41,7 @@ module Rails
end
def finalize!
- route_sets.each do |routes|
- routes.finalize!
- end
+ route_sets.each(&:finalize!)
end
def revert
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 96ced3c2f9..35a815a34f 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -18,14 +18,6 @@ module Rails
opt.on("-e", "--environment=name", String,
"Specifies the environment to run this console under (test/development/production).",
"Default: development") { |v| options[:environment] = v.strip }
- opt.on("--debugger", 'Enables the debugger.') do |v|
- if RUBY_VERSION < '2.0.0'
- options[:debugger] = v
- else
- puts "=> Notice: debugger option is ignored since Ruby 2.0 and " \
- "it will be removed in future versions."
- end
- end
opt.parse!(arguments)
end
@@ -76,25 +68,7 @@ module Rails
Rails.env = environment
end
- if RUBY_VERSION < '2.0.0'
- def debugger?
- options[:debugger]
- end
-
- def require_debugger
- require 'debugger'
- puts "=> Debugger enabled"
- rescue LoadError
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
- exit(1)
- end
- end
-
def start
- if RUBY_VERSION < '2.0.0'
- require_debugger if debugger?
- end
-
set_environment! if environment?
if sandbox?
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 1a2613a8d0..0d8b3de0eb 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -44,7 +44,7 @@ module Rails
find_cmd_and_exec(['mysql', 'mysql5'], *args)
- when "postgresql", "postgres", "postgis"
+ when /^postgres|^postgis/
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
@@ -74,6 +74,21 @@ module Rails
find_cmd_and_exec('sqlplus', logon)
+ when "sqlserver"
+ args = []
+
+ args += ["-D", "#{config['database']}"] if config['database']
+ args += ["-U", "#{config['username']}"] if config['username']
+ args += ["-P", "#{config['password']}"] if config['password']
+
+ if config['host']
+ host_arg = "#{config['host']}"
+ host_arg << ":#{config['port']}" if config['port']
+ args += ["-S", host_arg]
+ end
+
+ find_cmd_and_exec("sqsh", *args)
+
else
abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
end
@@ -163,7 +178,7 @@ module Rails
found = commands.detect do |cmd|
dirs_on_path.detect do |path|
full_path_command = File.join(path, cmd)
- File.executable? full_path_command
+ File.file?(full_path_command) && File.executable?(full_path_command)
end
end
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
index 95bbdd4cdf..52d8966ead 100644
--- a/railties/lib/rails/commands/plugin.rb
+++ b/railties/lib/rails/commands/plugin.rb
@@ -11,7 +11,7 @@ else
end
if File.exist?(railsrc)
extra_args_string = File.read(railsrc)
- extra_args = extra_args_string.split(/\n+/).flat_map {|l| l.split}
+ extra_args = extra_args_string.split(/\n+/).flat_map(&:split)
puts "Using #{extra_args.join(" ")} from #{railsrc}"
ARGV.insert(1, *extra_args)
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index e39f0920af..bc8f1a8dea 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -28,14 +28,6 @@ module Rails
opts.on("-c", "--config=file", String,
"Uses a custom rackup configuration.") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true }
- opts.on("-u", "--debugger", "Enables the debugger.") do
- if RUBY_VERSION < '2.0.0'
- options[:debugger] = true
- else
- puts "=> Notice: debugger option is ignored since Ruby 2.0 and " \
- "it will be removed in future versions."
- end
- end
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
@@ -86,9 +78,6 @@ module Rails
def middleware
middlewares = []
- if RUBY_VERSION < '2.0.0'
- middlewares << [Rails::Rack::Debugger] if options[:debugger]
- end
middlewares << [::Rack::ContentLength]
# FIXME: add Rack::Lock in the case people are using webrick.
@@ -112,7 +101,6 @@ module Rails
DoNotReverseLookup: true,
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
- debugger: false,
pid: File.expand_path("tmp/pids/server.pid"),
config: File.expand_path("config.ru")
})
@@ -130,7 +118,7 @@ module Rails
end
def create_tmp_directories
- %w(cache pids sessions sockets).each do |dir_to_make|
+ %w(cache pids sockets).each do |dir_to_make|
FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
end
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index f5d7dede66..f99cec04c5 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -18,11 +18,11 @@ module Rails
# This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack.
# You can use +insert_before+ if you wish to add a middleware before another:
#
- # config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns
+ # config.middleware.insert_before Rack::Head, Magical::Unicorns
#
# There's also +insert_after+ which will insert a middleware after another:
#
- # config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns
+ # config.middleware.insert_after Rack::Head, Magical::Unicorns
#
# Middlewares can also be completely swapped out and replaced with others:
#
diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb
deleted file mode 100644
index 89f54069e9..0000000000
--- a/railties/lib/rails/deprecation.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'active_support/deprecation/proxy_wrappers'
-
-module Rails
- class DeprecatedConstant < ActiveSupport::Deprecation::DeprecatedConstantProxy
- def self.deprecate(old, current)
- # double assignment is used to avoid "assigned but unused variable" warning
- constant = constant = new(old, current)
- eval "::#{old} = constant"
- end
-
- private
-
- def target
- ::Kernel.eval @new_const.to_s
- end
- end
-
- DeprecatedConstant.deprecate('RAILS_CACHE', '::Rails.cache')
-end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 023ea98145..a338f31f15 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -351,7 +351,7 @@ module Rails
base.called_from = begin
call_stack = if Kernel.respond_to?(:caller_locations)
- caller_locations.map(&:path)
+ caller_locations.map { |l| l.absolute_path || l.path }
else
# Remove the line number from backtraces making sure we don't leave anything behind
caller.map { |p| p.sub(/:\d+.*/, '') }
@@ -571,10 +571,10 @@ module Rails
end
initializer :add_routing_paths do |app|
- paths = self.paths["config/routes.rb"].existent
+ routing_paths = self.paths["config/routes.rb"].existent
- if routes? || paths.any?
- app.routes_reloader.paths.unshift(*paths)
+ if routes? || routing_paths.any?
+ app.routes_reloader.paths.unshift(*routing_paths)
app.routes_reloader.route_sets << routes
end
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 10d1821709..62a4139d07 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -39,7 +39,7 @@ module Rails
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
- paths.add "app", eager_load: true, glob: "*"
+ paths.add "app", eager_load: true, glob: "{*,*/concerns}"
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
paths.add "app/helpers", eager_load: true
@@ -47,9 +47,6 @@ module Rails
paths.add "app/mailers", eager_load: true
paths.add "app/views"
- paths.add "app/controllers/concerns", eager_load: true
- paths.add "app/models/concerns", eager_load: true
-
paths.add "lib", load_path: true
paths.add "lib/assets", glob: "*"
paths.add "lib/tasks", glob: "**/*.rake"
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 8abed99f2c..7d74b1bfe5 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -5,10 +5,10 @@ module Rails
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index bf2390cb7e..e25c364178 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -153,7 +153,7 @@ module Rails
def self.invoke(namespace, args=ARGV, config={})
names = namespace.to_s.split(':')
if klass = find_by_namespace(names.pop, names.any? && names.join(':'))
- args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
+ args << "--help" if args.empty? && klass.arguments.any?(&:required?)
klass.start(args, config)
else
options = sorted_groups.map(&:last).flatten
@@ -226,7 +226,7 @@ module Rails
def self.public_namespaces
lookup!
- subclasses.map { |k| k.namespace }
+ subclasses.map(&:namespace)
end
def self.print_generators
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index ffdb314612..5373121835 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -189,7 +189,7 @@ module Rails
# generate(:authenticated, "user session")
def generate(what, *args)
log :generate, what
- argument = args.flat_map {|arg| arg.to_s }.join(" ")
+ argument = args.flat_map(&:to_s).join(" ")
in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) }
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 5f8c33c713..71186891a3 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -38,6 +38,10 @@ module Rails
class_option :skip_keeps, type: :boolean, default: false,
desc: 'Skip source control .keep files'
+ class_option :skip_action_mailer, type: :boolean, aliases: "-M",
+ default: false,
+ desc: "Skip Action Mailer files"
+
class_option :skip_active_record, type: :boolean, aliases: '-O', default: false,
desc: 'Skip Active Record files'
@@ -164,7 +168,7 @@ module Rails
end
def include_all_railties?
- !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets]
+ options.values_at(:skip_active_record, :skip_action_mailer, :skip_test_unit, :skip_sprockets).none?
end
def comment_if(value)
@@ -180,8 +184,12 @@ module Rails
super
end
- def self.github(name, github, comment = nil)
- new(name, nil, comment, github: github)
+ def self.github(name, github, branch = nil, comment = nil)
+ if branch
+ new(name, nil, comment, github: github, branch: branch)
+ else
+ new(name, nil, comment, github: github)
+ end
end
def self.version(name, version, comment = nil)
@@ -195,11 +203,15 @@ module Rails
def rails_gemfile_entry
if options.dev?
- [GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
- GemfileEntry.github('rails-dom-testing', 'rails/rails-dom-testing')]
+ [
+ GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
+ GemfileEntry.github('arel', 'rails/arel')
+ ]
elsif options.edge?
- [GemfileEntry.github('rails', 'rails/rails'),
- GemfileEntry.github('rails-dom-testing', 'rails/rails-dom-testing')]
+ [
+ GemfileEntry.github('rails', 'rails/rails'),
+ GemfileEntry.github('arel', 'rails/arel')
+ ]
else
[GemfileEntry.version('rails',
Rails::VERSION::STRING,
@@ -238,16 +250,8 @@ module Rails
return [] if options[:skip_sprockets]
gems = []
- if options.dev? || options.edge?
- gems << GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails',
- 'Use edge version of sprockets-rails')
- gems << GemfileEntry.github('sass-rails', 'rails/sass-rails',
- 'Use SCSS for stylesheets')
- else
- gems << GemfileEntry.version('sass-rails',
- '~> 5.0.0.beta1',
+ gems << GemfileEntry.version('sass-rails', '~> 5.0',
'Use SCSS for stylesheets')
- end
gems << GemfileEntry.version('uglifier',
'>= 1.3.0',
@@ -269,7 +273,7 @@ module Rails
def coffee_gemfile_entry
comment = 'Use CoffeeScript for .coffee assets and views'
if options.dev? || options.edge?
- GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', comment
+ GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', nil, comment
else
GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment
end
@@ -280,14 +284,8 @@ module Rails
[]
else
gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry]
-
- if options[:javascript] == 'jquery'
- gems << GemfileEntry.version('jquery-rails', '~> 4.0.0.beta2',
- 'Use jQuery as the JavaScript library')
- else
- gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
- "Use #{options[:javascript]} as the JavaScript library")
- end
+ gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
+ "Use #{options[:javascript]} as the JavaScript library")
unless options[:skip_turbolinks]
gems << GemfileEntry.version("turbolinks", nil,
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 9af6435f23..813b8b629e 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -273,7 +273,7 @@ module Rails
# Use Rails default banner.
def self.banner
- "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]".gsub(/\s+/, ' ')
+ "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, ' ')
end
# Sets the base_name taking into account the current class namespace.
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 66b17bd10e..7d27321610 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -1,8 +1,31 @@
-require 'rails/generators/erb/controller/controller_generator'
+require 'rails/generators/erb'
module Erb # :nodoc:
module Generators # :nodoc:
- class MailerGenerator < ControllerGenerator # :nodoc:
+ class MailerGenerator < Base # :nodoc:
+ argument :actions, type: :array, default: [], banner: "method method"
+
+ def copy_view_files
+ view_base_path = File.join("app/views", class_path, file_name)
+ empty_directory view_base_path
+
+ if self.behavior == :invoke
+ formats.each do |format|
+ layout_path = File.join("app/views/layouts", filename_with_extensions("mailer", format))
+ template filename_with_extensions(:layout, format), layout_path
+ end
+ end
+
+ actions.each do |action|
+ @action = action
+
+ formats.each do |format|
+ @path = File.join(view_base_path, filename_with_extensions(action, format))
+ template filename_with_extensions(:view, format), @path
+ end
+ end
+ end
+
protected
def formats
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb
new file mode 100644
index 0000000000..93110e74ad
--- /dev/null
+++ b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <%%= yield %>
+ </body>
+</html>
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb
new file mode 100644
index 0000000000..6363733e6e
--- /dev/null
+++ b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb
@@ -0,0 +1 @@
+<%%= yield %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index bba9141fb8..d9713b0238 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -1,10 +1,10 @@
-<%%= form_for(@<%= singular_table_name %>) do |f| %>
- <%% if @<%= singular_table_name %>.errors.any? %>
+<%%= form_for(<%= singular_table_name %>) do |f| %>
+ <%% if <%= singular_table_name %>.errors.any? %>
<div id="error_explanation">
- <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
+ <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
<ul>
- <%% @<%= singular_table_name %>.errors.full_messages.each do |message| %>
+ <%% <%= singular_table_name %>.errors.full_messages.each do |message| %>
<li><%%= message %></li>
<%% end %>
</ul>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
index 5620fcc850..81329473d9 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
@@ -1,6 +1,6 @@
<h1>Editing <%= singular_table_name.titleize %></h1>
-<%%= render 'form' %>
+<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
<%%= link_to 'Show', @<%= singular_table_name %> %> |
<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
index db13a5d870..9b2b2f4875 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
@@ -1,5 +1,5 @@
<h1>New <%= singular_table_name.titleize %></h1>
-<%%= render 'form' %>
+<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index f16bd8e082..77a3dbf9a2 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -159,6 +159,10 @@ module Rails
options.delete(:required)
options[:null] = false
end
+
+ if reference? && !polymorphic?
+ options[:foreign_key] = true
+ end
end
end
end
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index b7da44ca2d..36456e64f5 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -99,7 +99,7 @@ module Rails
end
def class_name
- (class_path + [file_name]).map!{ |m| m.camelize }.join('::')
+ (class_path + [file_name]).map!(&:camelize).join('::')
end
def human_name
@@ -145,7 +145,7 @@ module Rails
@route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name
end
- # Tries to retrieve the application name or simple return application.
+ # Tries to retrieve the application name or simply return application.
def application_name
if defined?(Rails) && Rails.application
Rails.application.class.name.split('::').first.underscore
@@ -156,7 +156,7 @@ module Rails
def assign_names!(name) #:nodoc:
@class_path = name.include?('/') ? name.split('/') : name.split('::')
- @class_path.map! { |m| m.underscore }
+ @class_path.map!(&:underscore)
@file_name = @class_path.pop
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 9110c129d1..0550bf113e 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -88,9 +88,14 @@ module Rails
def config_when_updating
cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb')
+ callback_terminator_config_exist = File.exist?('config/initializers/callback_terminator.rb')
config
+ unless callback_terminator_config_exist
+ remove_file 'config/initializers/callback_terminator.rb'
+ end
+
unless cookie_serializer_config_exist
gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal'
end
@@ -260,9 +265,7 @@ module Rails
public_task :generate_spring_binstubs
def run_after_bundle_callbacks
- @after_bundle_callbacks.each do |callback|
- callback.call
- end
+ @after_bundle_callbacks.each(&:call)
end
protected
@@ -340,7 +343,7 @@ module Rails
#
# This class should be called before the AppGenerator is required and started
# since it configures and mutates ARGV correctly.
- class ARGVScrubber # :nodoc
+ class ARGVScrubber # :nodoc:
def initialize(argv = ARGV)
@argv = argv
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 5961f7515c..3659edcfcd 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -22,17 +22,16 @@ source 'https://rubygems.org'
# gem 'capistrano-rails', group: :development
group :development, :test do
-<% unless defined?(JRUBY_VERSION) -%>
- <%- if RUBY_VERSION < '2.0.0' -%>
- # Call 'debugger' anywhere in the code to stop execution and get a debugger console
- gem 'debugger'
- <%- else -%>
+<% if RUBY_ENGINE == 'ruby' -%>
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
- <%- end -%>
# Access an IRB console on exception pages or by using <%%= console %> in views
- gem 'web-console', '~> 2.0.0.beta4'
+ <%- if options.dev? || options.edge? -%>
+ gem 'web-console', github: "rails/web-console"
+ <%- else -%>
+ gem 'web-console', '~> 2.0'
+ <%- end -%>
<%- if spring_install? %>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index 07ea09cdbd..c1a77944e8 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -2,7 +2,7 @@
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
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 a443db3401..f9cd5b3483 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
@@ -3,7 +3,7 @@
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
+ * 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
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
index 0e22b3fa5c..eee810be30 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -1,28 +1,30 @@
require 'pathname'
+require 'fileutils'
+include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
-Dir.chdir APP_ROOT do
+chdir APP_ROOT do
# This script is a starting point to setup your application.
- # Add necessary setup steps to this file:
+ # Add necessary setup steps to this file.
- puts "== Installing dependencies =="
- system "gem install bundler --conservative"
- system "bundle check || bundle install"
+ puts '== Installing dependencies =='
+ system 'gem install bundler --conservative'
+ system('bundle check') or system('bundle install')
# puts "\n== Copying sample files =="
- # unless File.exist?("config/database.yml")
- # system "cp config/database.yml.sample config/database.yml"
+ # unless File.exist?('config/database.yml')
+ # cp 'config/database.yml.sample', 'config/database.yml'
# end
puts "\n== Preparing database =="
- system "bin/rake db:setup"
+ system 'ruby bin/rake db:setup'
puts "\n== Removing old logs and tempfiles =="
- system "rm -f log/*"
- system "rm -rf tmp/cache"
+ rm_f Dir.glob('log/*')
+ rm_rf 'tmp/cache'
puts "\n== Restarting application server =="
- system "touch tmp/restart.txt"
+ touch 'tmp/restart.txt'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 111b680e4b..c59ffb3491 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -8,7 +8,7 @@ require "active_model/railtie"
require "active_job/railtie"
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
-require "action_mailer/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"
@@ -31,10 +31,5 @@ module <%= app_const_base %>
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
- <%- unless options.skip_active_record? -%>
-
- # Do not swallow errors in after_commit/after_rollback callbacks.
- config.active_record.raise_in_transactional_callbacks = true
- <%- end -%>
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index 4b2e6646c7..596c916573 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -1,6 +1,6 @@
# MySQL. Versions 5.0+ are recommended.
#
-# Install the MYSQL driver
+# Install the MySQL driver
# gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index d8326d1728..ecb5d4170f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -12,9 +12,11 @@ Rails.application.configure do
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
+ <%- unless options.skip_action_mailer? -%>
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
+ <%- end -%>
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index ddc04d446c..99d7bfb3c9 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -20,8 +20,9 @@ Rails.application.configure do
# NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
- # Disable Rails's static asset server (Apache or NGINX will already do this).
- config.serve_static_assets = false
+ # Disable serving static files from the `/public` folder by default since
+ # Apache or NGINX already handles this.
+ config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
<%- unless options.skip_sprockets? -%>
# Compress JavaScripts and CSS.
@@ -60,10 +61,12 @@ Rails.application.configure do
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
+ <%- unless options.skip_action_mailer? -%>
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
+ <%- end -%>
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index 32756eb88b..0306deb18c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -12,8 +12,8 @@ Rails.application.configure do
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
- # Configure static asset server for tests with Cache-Control for performance.
- config.serve_static_assets = true
+ # Configure static file server for tests with Cache-Control for performance.
+ config.serve_static_files = true
config.static_cache_control = 'public, max-age=3600'
# Show full error reports and disable caching.
@@ -25,13 +25,15 @@ Rails.application.configure do
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
+ <%- unless options.skip_action_mailer? -%>
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
+ <%- end -%>
- # Randomize the order test cases are executed
+ # Randomize the order test cases are executed.
config.active_support.test_order = :random
# Print deprecation notices to the stderr.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
new file mode 100644
index 0000000000..e63022da91
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Do not halt callback chains when a callback returns false.
+Rails.application.config.active_support.halt_callback_chains_on_return_false = false
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 8775e5e235..7c6f2098b8 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -14,5 +14,6 @@
<% end -%>
# Ignore all logfiles and tempfiles.
-/log/*.log
+/log/*
+!/log/.keep
/tmp
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 584f776c01..1c270dd7d4 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -74,7 +74,8 @@ task default: :test
end
PASSTHROUGH_OPTIONS = [
- :skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip
+ :skip_active_record, :skip_action_mailer, :skip_javascript, :database,
+ :javascript, :quiet, :pretend, :force, :skip
]
def generate_test_dummy(force = false)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index 35ad9fbf9e..f325455bac 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -37,11 +37,11 @@ end
<% end -%>
<% end -%>
-<% unless defined?(JRUBY_VERSION) -%>
+<% if RUBY_ENGINE == 'ruby' -%>
# To use a debugger
- <%- if RUBY_VERSION < '2.0.0' -%>
-# gem 'debugger', group: [:development, :test]
- <%- else -%>
# gem 'byebug', group: [:development, :test]
- <%- end -%>
+<% 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/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
index b2aa82344a..3a9a7e5437 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
@@ -6,7 +6,7 @@ require 'rails/all'
# Pick the frameworks you want:
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
-require "action_mailer/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"
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
index 5bc2e1c8b5..c28e5badc6 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
@@ -2,7 +2,7 @@
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
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 a443db3401..f9cd5b3483 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
@@ -3,7 +3,7 @@
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
+ * 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
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 d492e68357..bf3da1fc4d 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
@@ -10,7 +10,9 @@ ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate',
<% end -%>
require "rails/test_help"
-Rails.backtrace_cleaner.remove_silencers!
+# Filter out Minitest backtrace while allowing backtrace from other libraries
+# to be shown.
+Minitest.backtrace_filter = Minitest::BacktraceFilter.new
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 6bf0a33a5f..c01b82884d 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -7,6 +7,7 @@ module Rails
check_class_collision suffix: "Controller"
+ class_option :helper, type: :boolean
class_option :orm, banner: "NAME", type: :string, required: true,
desc: "ORM to generate the controller for"
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 4669935156..3f84d76ae0 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -39,7 +39,7 @@ module Rails
def assign_controller_names!(name)
@controller_name = name
@controller_class_path = name.include?('/') ? name.split('/') : name.split('::')
- @controller_class_path.map! { |m| m.underscore }
+ @controller_class_path.map!(&:underscore)
@controller_file_name = @controller_class_path.pop
end
@@ -48,7 +48,7 @@ module Rails
end
def controller_class_name
- (controller_class_path + [controller_file_name]).map!{ |m| m.camelize }.join('::')
+ (controller_class_path + [controller_file_name]).map!(&:camelize).join('::')
end
def controller_i18n_scope
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
index f19e9d1d87..50ca61a35b 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
@@ -5,6 +5,8 @@
<% attributes.each do |attribute| -%>
<%- if attribute.password_digest? -%>
password_digest: <%%= BCrypt::Password.create('secret') %>
+ <%- elsif attribute.reference? -%>
+ <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default) %>
<%- else -%>
<%= yaml_key_value(attribute.column_name, attribute.default) %>
<%- end -%>
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index 94ede76932..5909446b66 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -8,7 +8,7 @@ module Rails
mattr_accessor :properties
class << (@@properties = [])
def names
- map {|val| val.first }
+ map(&:first)
end
def value_for(property_name)
@@ -26,7 +26,7 @@ module Rails
end
def to_s
- column_width = properties.names.map {|name| name.length}.max
+ column_width = properties.names.map(&:length).max
info = properties.map do |name, value|
value = value.join(", ") if value.is_a?(Array)
"%-#{column_width}s %s" % [name, value]
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index d11804af17..5458036219 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -77,23 +77,23 @@ module Rails
end
def all_paths
- values.tap { |v| v.uniq! }
+ values.tap(&:uniq!)
end
def autoload_once
- filter_by { |p| p.autoload_once? }
+ filter_by(&:autoload_once?)
end
def eager_load
- filter_by { |p| p.eager_load? }
+ filter_by(&:eager_load?)
end
def autoload_paths
- filter_by { |p| p.autoload? }
+ filter_by(&:autoload?)
end
def load_paths
- filter_by { |p| p.load_path? }
+ filter_by(&:load_path?)
end
private
diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb
index 886f0e52e1..a4c4527a72 100644
--- a/railties/lib/rails/rack.rb
+++ b/railties/lib/rails/rack.rb
@@ -1,7 +1,5 @@
module Rails
module Rack
- autoload :Debugger, "rails/rack/debugger" if RUBY_VERSION < '2.0.0'
- autoload :Logger, "rails/rack/logger"
- autoload :LogTailer, "rails/rack/log_tailer"
+ autoload :Logger, "rails/rack/logger"
end
end
diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb
index f7b77bcb3b..1fde3db070 100644
--- a/railties/lib/rails/rack/debugger.rb
+++ b/railties/lib/rails/rack/debugger.rb
@@ -1,24 +1,3 @@
-module Rails
- module Rack
- class Debugger
- def initialize(app)
- @app = app
+require 'active_support/deprecation'
- ARGV.clear # clear ARGV so that rails server options aren't passed to IRB
-
- require 'debugger'
-
- ::Debugger.start
- ::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings)
- puts "=> Debugger enabled"
- rescue LoadError
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
- exit(1)
- end
-
- def call(env)
- @app.call(env)
- end
- end
- end
-end
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb
deleted file mode 100644
index 46517713c9..0000000000
--- a/railties/lib/rails/rack/log_tailer.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'active_support/deprecation'
-
-module Rails
- module Rack
- class LogTailer
- def initialize(app, log = nil)
- ActiveSupport::Deprecation.warn('LogTailer is deprecated and will be removed on Rails 5.')
-
- @app = app
-
- path = Pathname.new(log || "#{::File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
-
- @cursor = @file = nil
- if ::File.exist?(path)
- @cursor = ::File.size(path)
- @file = ::File.open(path, 'r')
- end
- end
-
- def call(env)
- response = @app.call(env)
- tail!
- response
- end
-
- def tail!
- return unless @cursor
- @file.seek @cursor
-
- unless @file.eof?
- contents = @file.read
- @cursor = @file.tell
- $stdout.print contents
- end
- end
- end
- end
-end
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index df74643a59..aea3d2339c 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -1,13 +1,13 @@
-if RUBY_VERSION < '1.9.3'
+if RUBY_VERSION < '2.2.0'
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
- Rails 4 prefers to run on Ruby 2.1 or newer.
+ Rails 5 requires to run on Ruby 2.2.0 or newer.
You're running
#{desc}
- Please upgrade to Ruby 1.9.3 or newer to continue.
+ Please upgrade to Ruby 2.2.0 or newer to continue.
end_message
end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index af5f2707b1..2f82d1285d 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -1,3 +1,5 @@
+require 'rake'
+
# Load Rails Rakefile extensions
%w(
annotations
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index 4f09ded31d..ba6168e208 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -1,6 +1,6 @@
-# while having global constant is not good,
-# many 3rd party tools depend on it, like rspec-rails, cucumber-rails, etc
-# so if will be removed - deprecation warning is needed
+# While global constants are bad, many 3rd party tools depend on this one (e.g
+# rspec-rails & cucumber-rails). So a deprecation warning is needed if we want
+# to remove it.
STATS_DIRECTORIES = [
%w(Controllers app/controllers),
%w(Helpers app/helpers),
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index 116988665f..b33ae9862b 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -1,9 +1,8 @@
namespace :tmp do
- desc "Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)"
- task clear: [ "tmp:sessions:clear", "tmp:cache:clear", "tmp:sockets:clear"]
+ desc "Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)"
+ task clear: ["tmp:cache:clear", "tmp:sockets:clear"]
- tmp_dirs = [ 'tmp/sessions',
- 'tmp/cache',
+ tmp_dirs = [ 'tmp/cache',
'tmp/sockets',
'tmp/pids',
'tmp/cache/assets/development',
@@ -15,13 +14,6 @@ namespace :tmp do
desc "Creates tmp directories for sessions, cache, sockets, and pids"
task create: tmp_dirs
- namespace :sessions do
- # desc "Clears all files in tmp/sessions"
- task :clear do
- FileUtils.rm(Dir['tmp/sessions/[^.]*'])
- end
- end
-
namespace :cache do
# desc "Clears all files and directories in tmp/cache"
task :clear do
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index 89792066d5..6726c23fc9 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -237,7 +237,7 @@
<ol>
<li>
- <h2>Use <code>rails generate</code> to create your models and controllers</h2>
+ <h2>Use <code>bin/rails generate</code> to create your models and controllers</h2>
<p>To see all available options, run it without parameters.</p>
</li>
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 0d0cfa3c6b..d836c0d6d6 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -10,7 +10,8 @@ end
namespace :test do
task :prepare do
- # Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
+ # Placeholder task for other Railtie and plugins to enhance.
+ # If used with Active Record, this task runs before the database schema is synchronized.
end
Rails::TestTask.new(:run) do |t|
@@ -20,29 +21,6 @@ namespace :test do
desc "Run tests quickly, but also reset db"
task :db => %w[db:test:prepare test]
- desc "Run tests quickly by merging all types and not resetting db"
- Rails::TestTask.new(:all) do |t|
- t.pattern = "test/**/*_test.rb"
- end
-
- Rake::Task["test:all"].enhance do
- Rake::Task["test:deprecate_all"].invoke
- end
-
- task :deprecate_all do
- ActiveSupport::Deprecation.warn "rake test:all is deprecated and will be removed in Rails 5. " \
- "Use rake test to run all tests in test directory."
- end
-
- namespace :all do
- desc "Run tests quickly, but also reset db"
- task :db => %w[db:test:prepare test:all]
-
- Rake::Task["test:all:db"].enhance do
- Rake::Task["test:deprecate_all"].invoke
- end
- end
-
Rails::TestTask.new(single: "test:prepare")
["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name|
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 56b8736800..7a1c897e3d 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Tools for creating, working with, and running Rails applications.'
s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 8f091cfdbf..d58a27403e 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -225,7 +225,7 @@ module ApplicationTests
test "assets do not require any assets group gem when manifest file is present" do
app_file "app/assets/javascripts/application.js", "alert();"
- add_to_env_config "production", "config.serve_static_assets = true"
+ add_to_env_config "production", "config.serve_static_files = true"
ENV["RAILS_ENV"] = "production"
precompile!
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 391139d7f8..8f5b2d0d68 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -41,7 +41,7 @@ module ApplicationTests
def setup
build_app
boot_rails
- FileUtils.rm_rf("#{app_path}/config/environments")
+ supress_default_config
end
def teardown
@@ -49,6 +49,15 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
+ def supress_default_config
+ FileUtils.mv("#{app_path}/config/environments", "#{app_path}/config/__environments__")
+ end
+
+ def restore_default_config
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ FileUtils.mv("#{app_path}/config/__environments__", "#{app_path}/config/environments")
+ end
+
test "Rails.env does not set the RAILS_ENV environment variable which would leak out into rake tasks" do
require "rails"
@@ -280,10 +289,41 @@ module ApplicationTests
assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path
end
+ test "In production mode, config.serve_static_files is off by default" do
+ restore_default_config
+
+ with_rails_env "production" do
+ require "#{app_path}/config/environment"
+ assert_not app.config.serve_static_files
+ end
+ end
+
+ test "In production mode, config.serve_static_files is enabled when RAILS_SERVE_STATIC_FILES is set" do
+ restore_default_config
+
+ with_rails_env "production" do
+ switch_env "RAILS_SERVE_STATIC_FILES", "1" do
+ require "#{app_path}/config/environment"
+ assert app.config.serve_static_files
+ end
+ end
+ end
+
+ test "In production mode, config.serve_static_files is disabled when RAILS_SERVE_STATIC_FILES is blank" do
+ restore_default_config
+
+ with_rails_env "production" do
+ switch_env "RAILS_SERVE_STATIC_FILES", " " do
+ require "#{app_path}/config/environment"
+ assert_not app.config.serve_static_files
+ end
+ end
+ end
+
test "Use key_generator when secret_key_base is set" do
- make_basic_app do |app|
- app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
- app.config.session_store :disabled
+ make_basic_app do |application|
+ application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ application.config.session_store :disabled
end
class ::OmgController < ActionController::Base
@@ -301,9 +341,9 @@ module ApplicationTests
end
test "application verifier can be used in the entire application" do
- make_basic_app do |app|
- app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
- app.config.session_store :disabled
+ make_basic_app do |application|
+ application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ application.config.session_store :disabled
end
message = app.message_verifier(:sensitive_value).generate("some_value")
@@ -361,9 +401,9 @@ module ApplicationTests
end
test "application verifier can build different verifiers" do
- make_basic_app do |app|
- app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
- app.config.session_store :disabled
+ make_basic_app do |application|
+ application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ application.config.session_store :disabled
end
default_verifier = app.message_verifier(:sensitive_value)
@@ -494,6 +534,45 @@ module ApplicationTests
assert last_response.body =~ /csrf\-param/
end
+ test "default form builder specified as a string" do
+
+ app_file 'config/initializers/form_builder.rb', <<-RUBY
+ class CustomFormBuilder < ActionView::Helpers::FormBuilder
+ def text_field(attribute, *args)
+ label(attribute) + super(attribute, *args)
+ end
+ end
+ Rails.configuration.action_view.default_form_builder = "CustomFormBuilder"
+ RUBY
+
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ include ActiveModel::Model
+ attr_accessor :name
+ end
+ RUBY
+
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render inline: "<%= begin; form_for(Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get "/posts"
+ assert_match(/label/, last_response.body)
+ end
+
test "default method for update can be changed" do
app_file 'app/models/post.rb', <<-RUBY
class Post
@@ -548,8 +627,8 @@ module ApplicationTests
end
test "request forgery token param can be changed" do
- make_basic_app do
- app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
+ make_basic_app do |application|
+ application.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
end
class ::OmgController < ActionController::Base
@@ -568,8 +647,8 @@ module ApplicationTests
end
test "sets ActionDispatch::Response.default_charset" do
- make_basic_app do |app|
- app.config.action_dispatch.default_charset = "utf-16"
+ make_basic_app do |application|
+ application.config.action_dispatch.default_charset = "utf-16"
end
assert_equal "utf-16", ActionDispatch::Response.default_charset
@@ -750,8 +829,8 @@ module ApplicationTests
end
test "config.action_dispatch.show_exceptions is sent in env" do
- make_basic_app do |app|
- app.config.action_dispatch.show_exceptions = true
+ make_basic_app do |application|
+ application.config.action_dispatch.show_exceptions = true
end
class ::OmgController < ActionController::Base
@@ -912,8 +991,8 @@ module ApplicationTests
end
test "config.action_dispatch.ignore_accept_header" do
- make_basic_app do |app|
- app.config.action_dispatch.ignore_accept_header = true
+ make_basic_app do |application|
+ application.config.action_dispatch.ignore_accept_header = true
end
class ::OmgController < ActionController::Base
@@ -950,9 +1029,9 @@ module ApplicationTests
test "config.session_store with :active_record_store with activerecord-session_store gem" do
begin
- make_basic_app do |app|
+ make_basic_app do |application|
ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore)
- app.config.session_store :active_record_store
+ application.config.session_store :active_record_store
end
ensure
ActionDispatch::Session.send :remove_const, :ActiveRecordStore
@@ -961,16 +1040,16 @@ module ApplicationTests
test "config.session_store with :active_record_store without activerecord-session_store gem" do
assert_raise RuntimeError, /activerecord-session_store/ do
- make_basic_app do |app|
- app.config.session_store :active_record_store
+ make_basic_app do |application|
+ application.config.session_store :active_record_store
end
end
end
test "config.log_level with custom logger" do
- make_basic_app do |app|
- app.config.logger = Logger.new(STDOUT)
- app.config.log_level = :info
+ make_basic_app do |application|
+ application.config.logger = Logger.new(STDOUT)
+ application.config.log_level = :info
end
assert_equal Logger::INFO, Rails.logger.level
end
@@ -1000,8 +1079,8 @@ module ApplicationTests
end
test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do
- make_basic_app do |app|
- app.config.annotations.register_extensions("coffee") do |tag|
+ make_basic_app do |application|
+ application.config.annotations.register_extensions("coffee") do |tag|
/#\s*(#{tag}):?\s*(.*)$/
end
end
@@ -1089,7 +1168,7 @@ module ApplicationTests
app_file 'config/environments/development.rb', <<-RUBY
Rails.application.configure do
- config.paths.add 'config/database', with: 'config/nonexistant.yml'
+ config.paths.add 'config/database', with: 'config/nonexistent.yml'
config.paths['config/database'] << 'config/database.yml'
end
RUBY
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 2d45c9b53f..97b51911d9 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -65,7 +65,6 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- assert Foo.method_defined?(:foo_path)
assert Foo.method_defined?(:foo_url)
assert Foo.method_defined?(:main_app)
end
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 4f30f30f95..85066210f3 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -33,6 +33,35 @@ class LoadingTest < ActiveSupport::TestCase
assert_equal 'omg', p.title
end
+ test "concerns in app are autoloaded" do
+ app_file "app/controllers/concerns/trackable.rb", <<-CONCERN
+ module Trackable
+ end
+ CONCERN
+
+ app_file "app/mailers/concerns/email_loggable.rb", <<-CONCERN
+ module EmailLoggable
+ end
+ CONCERN
+
+ app_file "app/models/concerns/orderable.rb", <<-CONCERN
+ module Orderable
+ end
+ CONCERN
+
+ app_file "app/validators/concerns/matchable.rb", <<-CONCERN
+ module Matchable
+ end
+ CONCERN
+
+ require "#{rails_root}/config/environment"
+
+ assert_nothing_raised(NameError) { Trackable }
+ assert_nothing_raised(NameError) { EmailLoggable }
+ assert_nothing_raised(NameError) { Orderable }
+ assert_nothing_raised(NameError) { Matchable }
+ end
+
test "models without table do not panic on scope definitions when loaded" do
app_file "app/models/user.rb", <<-MODEL
class User < ActiveRecord::Base
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 55e917c3ec..1752a9f3c6 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -40,6 +40,17 @@ module ApplicationTests
assert_equal 404, last_response.status
end
+ test "/rails/mailers is accessible with globbing route present" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '*foo', to: 'foo#index'
+ end
+ RUBY
+ app("development")
+ get "/rails/mailers"
+ assert_equal 200, last_response.status
+ end
+
test "mailer previews are loaded from the default preview_path" do
mailer 'notifier', <<-RUBY
class Notifier < ActionMailer::Base
@@ -417,58 +428,6 @@ module ApplicationTests
assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
end
- test "*_path helpers emit a deprecation" do
-
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- get 'foo', to: 'foo#index'
- end
- RUBY
-
- mailer 'notifier', <<-RUBY
- class Notifier < ActionMailer::Base
- default from: "from@example.com"
-
- def path_in_view
- mail to: "to@example.org"
- end
-
- def path_in_mailer
- @url = foo_path
- mail to: "to@example.org"
- end
- end
- RUBY
-
- html_template 'notifier/path_in_view', "<%= link_to 'foo', foo_path %>"
-
- mailer_preview 'notifier', <<-RUBY
- class NotifierPreview < ActionMailer::Preview
- def path_in_view
- Notifier.path_in_view
- end
-
- def path_in_mailer
- Notifier.path_in_mailer
- end
- end
- RUBY
-
- app('development')
-
- assert_deprecated do
- get "/rails/mailers/notifier/path_in_view.html"
- assert_equal 200, last_response.status
- end
-
- html_template 'notifier/path_in_mailer', "No ERB in here"
-
- assert_deprecated do
- get "/rails/mailers/notifier/path_in_mailer.html"
- assert_equal 200, last_response.status
- end
- end
-
private
def build_app
super
diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb
index eb791f5687..dc96480d6d 100644
--- a/railties/test/application/middleware/sendfile_test.rb
+++ b/railties/test/application/middleware/sendfile_test.rb
@@ -61,7 +61,7 @@ module ApplicationTests
test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do
make_basic_app do |app|
app.config.action_dispatch.x_sendfile_header = 'X-Sendfile'
- app.config.serve_static_assets = true
+ app.config.serve_static_files = true
app.paths["public"] = File.join(rails_root, "public")
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index caef39d16f..c64fe082f3 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -113,8 +113,8 @@ module ApplicationTests
assert !middleware.include?("Rack::Lock")
end
- test "removes static asset server if serve_static_assets is disabled" do
- add_to_config "config.serve_static_assets = false"
+ test "removes static asset server if serve_static_files is disabled" do
+ add_to_config "config.serve_static_files = false"
boot!
assert !middleware.include?("ActionDispatch::Static")
end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 524c70aad2..c414732f92 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -156,6 +156,31 @@ module ApplicationTests
end
end
+ test 'db:schema:load and db:structure:load do not purge the existing database' do
+ Dir.chdir(app_path) do
+ `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
+
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table(:comments) {}
+ end
+ RUBY
+
+ list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
+
+ assert_equal '["posts"]', list_tables[]
+ `bin/rake db:schema:load`
+ assert_equal '["posts", "comments", "schema_migrations"]', list_tables[]
+
+ app_file 'db/structure.sql', <<-SQL
+ CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
+ SQL
+
+ `bin/rake db:structure:load`
+ assert_equal '["posts", "comments", "schema_migrations", "users"]', list_tables[]
+ end
+ end
+
def db_test_load_structure
Dir.chdir(app_path) do
`rails generate model book title:string;
@@ -175,15 +200,6 @@ module ApplicationTests
db_test_load_structure
end
- test 'db:test deprecation' do
- require "#{app_path}/config/environment"
- Dir.chdir(app_path) do
- output = `bundle exec rake db:migrate db:test:prepare 2>&1`
- assert_equal "WARNING: db:test:prepare is deprecated. The Rails test helper now maintains " \
- "your test schema automatically, see the release notes for details.\n", output
- end
- end
-
test 'db:setup loads schema and seeds database' do
begin
@old_rails_env = ENV["RAILS_ENV"]
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 8576a2b738..cbada6be97 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -123,6 +123,26 @@ module ApplicationTests
assert_equal '/archives', last_response.body
end
+ test "mount named rack app" do
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def index
+ render text: my_blog_path
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog", as: "my_blog"
+ get '/foo' => 'foo#index'
+ end
+ RUBY
+
+ get '/foo'
+ assert_equal '/blog', last_response.body
+ end
+
test "multiple controllers" do
controller :foo, <<-RUBY
class FooController < ApplicationController
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index c724c867ec..c7132837b1 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -193,6 +193,98 @@ module ApplicationTests
assert_successful_test_run('models/user_test.rb')
end
+ # TODO: would be nice if we could detect the schema change automatically.
+ # For now, the user has to synchronize the schema manually.
+ # This test-case serves as a reminder for this use-case.
+ test "manually synchronize test schema after rollback" do
+ output = script('generate model user name:string')
+ version = output.match(/(\d+)_create_users\.rb/)[1]
+
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ assert_equal ["id", "name"], User.columns_hash.keys
+ end
+ end
+ RUBY
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: #{version}) do
+ create_table :users do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ assert_successful_test_run "models/user_test.rb"
+
+ # Simulate `db:rollback` + edit of the migration file + `db:migrate`
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: #{version}) do
+ create_table :users do |t|
+ t.string :name
+ t.integer :age
+ end
+ end
+ RUBY
+
+ assert_successful_test_run "models/user_test.rb"
+
+ Dir.chdir(app_path) { `bin/rake db:test:prepare` }
+
+ assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION
+Expected: ["id", "name"]
+ Actual: ["id", "name", "age"]
+ ASSERTION
+ end
+
+ test "hooks for plugins" do
+ output = script('generate model user name:string')
+ version = output.match(/(\d+)_create_users\.rb/)[1]
+
+ app_file 'lib/tasks/hooks.rake', <<-RUBY
+ task :before_hook do
+ has_user_table = ActiveRecord::Base.connection.table_exists?('users')
+ puts "before: " + has_user_table.to_s
+ end
+
+ task :after_hook do
+ has_user_table = ActiveRecord::Base.connection.table_exists?('users')
+ puts "after: " + has_user_table.to_s
+ end
+
+ Rake::Task["db:test:prepare"].enhance [:before_hook] do
+ Rake::Task[:after_hook].invoke
+ end
+ RUBY
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ User.create! name: "Jon"
+ end
+ end
+ RUBY
+
+ # Simulate `db:migrate`
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: #{version}) do
+ create_table :users do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ output = assert_successful_test_run "models/user_test.rb"
+ assert_includes output, "before: false\nafter: true"
+
+ # running tests again won't trigger a schema update
+ output = assert_successful_test_run "models/user_test.rb"
+ assert_not_includes output, "before:"
+ assert_not_includes output, "after:"
+ end
+
private
def assert_unsuccessful_run(name, message)
result = run_test_file(name)
@@ -208,7 +300,7 @@ module ApplicationTests
end
def run_test_file(name, options = {})
- ruby '-Itest', "#{app_path}/test/#{name}", options
+ ruby '-Itest', "#{app_path}/test/#{name}", options.deep_merge(env: {"RAILS_ENV" => "test"})
end
def ruby(*args)
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 4aea3e980f..de0cf0ba9e 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -46,28 +46,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/Loading \w+ environment in sandbox \(Rails/, output)
end
- if RUBY_VERSION < '2.0.0'
- def test_debugger_option
- console = Rails::Console.new(app, parse_arguments(["--debugger"]))
- assert console.debugger?
- end
-
- def test_no_options_does_not_set_debugger_flag
- console = Rails::Console.new(app, parse_arguments([]))
- assert !console.debugger?
- end
-
- def test_start_with_debugger
- stubbed_console = Class.new(Rails::Console) do
- def require_debugger
- end
- end
-
- rails_console = stubbed_console.new(app, parse_arguments(["--debugger"]))
- silence_stream(STDOUT) { rails_console.start }
- end
- end
-
def test_console_with_environment
start ["-e production"]
assert_match(/\sproduction\s/, output)
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 2206e389b5..c4b6441397 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -129,7 +129,7 @@ class ActionsTest < Rails::Generators::TestCase
run_generator
action :environment do
- '# This wont be added'
+ _ = '# This wont be added'# assignment to silence parse-time warning "unused literal ignored"
'# This will be added'
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index b7cbe04003..40fd7b77f1 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -160,6 +160,38 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
end
+ def test_rails_update_does_not_create_callback_terminator_initializer
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb")
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
+
+ def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb")
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
+
def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
@@ -259,6 +291,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_without_skips
+ run_generator
+ assert_file "config/application.rb", /\s+require\s+["']rails\/all["']/
+ assert_file "config/environments/development.rb" do |content|
+ assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ assert_file "config/environments/test.rb" do |content|
+ assert_match(/config\.action_mailer\.delivery_method = :test/, content)
+ end
+ assert_file "config/environments/production.rb" do |content|
+ assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ end
+
def test_generator_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
@@ -268,6 +314,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_if_skip_action_mailer_is_given
+ run_generator [destination_root, "--skip-action-mailer"]
+ assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/
+ assert_file "config/environments/development.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "config/environments/test.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "config/environments/production.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ end
+
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_no_file "config/initializers/assets.rb"
@@ -340,13 +400,10 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_a_debugger
run_generator
- if defined?(JRUBY_VERSION)
+ if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx"
assert_file "Gemfile" do |content|
assert_no_match(/byebug/, content)
- assert_no_match(/debugger/, content)
end
- elsif RUBY_VERSION < '2.0.0'
- assert_gem 'debugger'
else
assert_gem 'byebug'
end
@@ -420,6 +477,24 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_gem 'web-console'
end
+ def test_web_console_with_dev_option
+ run_generator [destination_root, "--dev"]
+
+ assert_file "Gemfile" do |content|
+ assert_match(/gem 'web-console',\s+github: "rails\/web-console"/, content)
+ assert_no_match(/gem 'web-console', '~> 2.0'/, content)
+ end
+ end
+
+ def test_web_console_with_edge_option
+ run_generator [destination_root, "--edge"]
+
+ assert_file "Gemfile" do |content|
+ assert_match(/gem 'web-console',\s+github: "rails\/web-console"/, content)
+ assert_no_match(/gem 'web-console', '~> 2.0'/, content)
+ end
+ end
+
def test_spring
run_generator
assert_gem 'spring'
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 6cc91f166b..94099fcd2e 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -49,4 +49,14 @@ module GeneratorsTestHelper
end
end
end
+
+ def silence_stream(stream)
+ old_stream = stream.dup
+ stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
+ stream.sync = true
+ yield
+ ensure
+ stream.reopen(old_stream)
+ old_stream.close
+ end
end
diff --git a/railties/test/generators/job_generator_test.rb b/railties/test/generators/job_generator_test.rb
new file mode 100644
index 0000000000..a9e0cea94f
--- /dev/null
+++ b/railties/test/generators/job_generator_test.rb
@@ -0,0 +1,29 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/job/job_generator'
+
+class JobGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+
+ def test_job_skeleton_is_created
+ run_generator ["refresh_counters"]
+ assert_file "app/jobs/refresh_counters_job.rb" do |job|
+ assert_match(/class RefreshCountersJob < ActiveJob::Base/, job)
+ end
+ end
+
+ def test_job_queue_param
+ run_generator ["refresh_counters", "--queue", "important"]
+ assert_file "app/jobs/refresh_counters_job.rb" do |job|
+ assert_match(/class RefreshCountersJob < ActiveJob::Base/, job)
+ assert_match(/queue_as :important/, job)
+ end
+ end
+
+ def test_job_namespace
+ run_generator ["admin/refresh_counters", "--queue", "admin"]
+ assert_file "app/jobs/admin/refresh_counters_job.rb" do |job|
+ assert_match(/class Admin::RefreshCountersJob < ActiveJob::Base/, job)
+ assert_match(/queue_as :admin/, job)
+ end
+ end
+end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index 25649881eb..3d1cf87dae 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -8,8 +8,18 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_mailer_skeleton_is_created
run_generator
assert_file "app/mailers/notifier.rb" do |mailer|
- assert_match(/class Notifier < ActionMailer::Base/, mailer)
+ assert_match(/class Notifier < ApplicationMailer/, mailer)
+ assert_no_match(/default from: "from@example.com"/, mailer)
+ assert_no_match(/layout :mailer_notifier/, mailer)
+ end
+ end
+
+ def test_application_mailer_skeleton_is_created
+ run_generator
+ assert_file "app/mailers/application_mailer.rb" do |mailer|
+ assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer)
assert_match(/default from: "from@example.com"/, mailer)
+ assert_match(/layout 'mailer'/, mailer)
end
end
@@ -77,6 +87,10 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(%r(\sapp/views/notifier/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
+
+ assert_file "app/views/layouts/mailer.text.erb" do |view|
+ assert_match(/<%= yield %>/, view)
+ end
end
def test_invokes_default_html_template_engine
@@ -90,11 +104,17 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(%r(\sapp/views/notifier/bar\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
+
+ assert_file "app/views/layouts/mailer.html.erb" do |view|
+ assert_match(%r{<html>\n <body>\n <%= yield %>\n </body>\n</html>}, view)
+ end
end
def test_invokes_default_template_engine_even_with_no_action
run_generator ["notifier"]
assert_file "app/views/notifier"
+ assert_file "app/views/layouts/mailer.text.erb"
+ assert_file "app/views/layouts/mailer.html.erb"
end
def test_logs_if_the_template_engine_cannot_be_found
@@ -105,7 +125,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_mailer_with_namedspaced_mailer
run_generator ["Farm::Animal", "moos"]
assert_file "app/mailers/farm/animal.rb" do |mailer|
- assert_match(/class Farm::Animal < ActionMailer::Base/, mailer)
+ assert_match(/class Farm::Animal < ApplicationMailer/, mailer)
assert_match(/en\.farm\.animal\.moos\.subject/, mailer)
end
assert_file "test/mailers/previews/farm/animal_preview.rb" do |preview|
@@ -132,4 +152,19 @@ class MailerGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_mailer_on_revoke
+ run_generator
+ run_generator ["notifier"], behavior: :revoke
+
+ assert_no_file "app/mailers/notifier.rb"
+ assert_no_file "app/views/notifier/foo.text.erb"
+ assert_no_file "app/views/notifier/bar.text.erb"
+ assert_no_file "app/views/notifier/foo.html.erb"
+ assert_no_file "app/views/notifier/bar.html.erb"
+
+ assert_file "app/mailers/application_mailer.rb"
+ assert_file "app/views/layouts/mailer.text.erb"
+ assert_file "app/views/layouts/mailer.html.erb"
+ end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 72f5fe29ca..413d457d54 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -85,6 +85,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_remove_migration_with_references_removes_foreign_keys
+ migration = "remove_references_from_books"
+ run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/remove_reference :books, :author,.*\sforeign_key: true/, change)
+ assert_match(/remove_reference :books, :distributor/, change) # sanity check
+ assert_no_match(/remove_reference :books, :distributor,.*\sforeign_key: true/, change)
+ end
+ end
+ end
+
def test_add_migration_with_attributes_and_indices
migration = "add_title_with_index_and_body_to_posts"
run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"]
@@ -171,6 +184,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_add_migration_with_references_adds_foreign_keys
+ migration = "add_references_to_books"
+ run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/add_reference :books, :author,.*\sforeign_key: true/, change)
+ assert_match(/add_reference :books, :distributor/, change) # sanity check
+ assert_no_match(/add_reference :books, :distributor,.*\sforeign_key: true/, change)
+ end
+ end
+ end
+
def test_create_join_table_migration
migration = "add_media_join_table"
run_generator [migration, "artist_id", "musics:uniq"]
@@ -205,7 +231,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
end
-
+
def test_properly_identifies_usage_file
assert generator_class.send(:usage_path)
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index c78597c81b..f3b699101f 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -223,7 +223,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_with_timestamps
run_generator
- assert_migration "db/migrate/create_accounts.rb", /t.timestamps null: false/
+ assert_migration "db/migrate/create_accounts.rb", /t.timestamps/
end
def test_migration_timestamps_are_skipped
@@ -287,18 +287,18 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_fixtures_use_the_references_ids
run_generator ["LineItem", "product:references", "cart:belongs_to"]
- assert_file "test/fixtures/line_items.yml", /product_id: \n cart_id: /
+ assert_file "test/fixtures/line_items.yml", /product: \n cart: /
assert_generated_fixture("test/fixtures/line_items.yml",
- {"one"=>{"product_id"=>nil, "cart_id"=>nil}, "two"=>{"product_id"=>nil, "cart_id"=>nil}})
+ {"one"=>{"product"=>nil, "cart"=>nil}, "two"=>{"product"=>nil, "cart"=>nil}})
end
def test_fixtures_use_the_references_ids_and_type
run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"]
- assert_file "test/fixtures/line_items.yml", /product_id: \n product_type: Product\n cart_id: /
+ assert_file "test/fixtures/line_items.yml", /product: \n product_type: Product\n cart: /
assert_generated_fixture("test/fixtures/line_items.yml",
- {"one"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil},
- "two"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil}})
+ {"one"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil},
+ "two"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}})
end
def test_fixtures_respect_reserved_yml_keywords
@@ -407,6 +407,37 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_foreign_key_is_not_added_for_non_references
+ run_generator ["account", "supplier:string"]
+
+ assert_migration "db/migrate/create_accounts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_no_match(/foreign_key/, up)
+ end
+ end
+ end
+
+ def test_foreign_key_is_added_for_references
+ run_generator ["account", "supplier:belongs_to", "user:references"]
+
+ assert_migration "db/migrate/create_accounts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/t\.belongs_to :supplier,.*\sforeign_key: true/, up)
+ assert_match(/t\.references :user,.*\sforeign_key: true/, up)
+ end
+ end
+ end
+
+ def test_foreign_key_is_skipped_for_polymorphic_references
+ run_generator ["account", "supplier:belongs_to{polymorphic}"]
+
+ assert_migration "db/migrate/create_accounts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_no_match(/foreign_key/, up)
+ end
+ end
+ end
+
private
def assert_generated_fixture(path, parsed_contents)
fixture_file = File.new File.expand_path(path, destination_root)
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 7eeb084eab..6075805152 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -148,8 +148,8 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
run_generator
assert_file "app/mailers/test_app/notifier.rb" do |mailer|
assert_match(/module TestApp/, mailer)
- assert_match(/class Notifier < ActionMailer::Base/, mailer)
- assert_match(/default from: "from@example.com"/, mailer)
+ assert_match(/class Notifier < ApplicationMailer/, mailer)
+ assert_no_match(/default from: "from@example.com"/, mailer)
end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 4329c6e1a4..318ea5b2cb 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -57,6 +57,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/test_helper.rb" do |content|
assert_match(/require.+test\/dummy\/config\/environment/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+test\/dummy\/db\/migrate/, content)
+ assert_match(/Minitest\.backtrace_filter = Minitest::BacktraceFilter\.new/, content)
end
assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/
end
@@ -70,13 +71,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_a_debugger
run_generator [destination_root, '--full']
- if defined?(JRUBY_VERSION)
+ if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx"
assert_file "Gemfile" do |content|
assert_no_match(/byebug/, content)
- assert_no_match(/debugger/, content)
end
- elsif RUBY_VERSION < '2.0.0'
- assert_file "Gemfile", /# gem 'debugger'/
else
assert_file "Gemfile", /# gem 'byebug'/
end
@@ -139,6 +137,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_app_generator_without_skips
+ run_generator
+ assert_file "test/dummy/config/application.rb", /\s+require\s+["']rails\/all["']/
+ assert_file "test/dummy/config/environments/development.rb" do |content|
+ assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ assert_file "test/dummy/config/environments/test.rb" do |content|
+ assert_match(/config\.action_mailer\.delivery_method = :test/, content)
+ end
+ assert_file "test/dummy/config/environments/production.rb" do |content|
+ assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ end
+
def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
@@ -152,6 +164,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_action_mailer_is_removed_from_frameworks_if_skip_action_mailer_is_given
+ run_generator [destination_root, "--skip-action-mailer"]
+ assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/
+ assert_file "test/dummy/config/environments/development.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "test/dummy/config/environments/test.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "test/dummy/config/environments/production.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ end
+
def test_ensure_that_database_option_is_passed_to_app_generator
run_generator [destination_root, "--database", "postgresql"]
assert_file "test/dummy/config/database.yml", /postgres/
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 637bde2a44..3b545328b5 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -63,10 +63,20 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
# Views
- %w(index edit new show _form).each do |view|
+ assert_no_file "app/views/layouts/product_lines.html.erb"
+
+ %w(index show).each do |view|
assert_file "app/views/product_lines/#{view}.html.erb"
end
- assert_no_file "app/views/layouts/product_lines.html.erb"
+
+ %w(edit new).each do |view|
+ assert_file "app/views/product_lines/#{view}.html.erb", /render 'form', product_line: @product_line/
+ end
+
+ assert_file "app/views/product_lines/_form.html.erb" do |test|
+ assert_match 'product_line', test
+ assert_no_match '@product_line', test
+ end
# Helpers
assert_file "app/helpers/product_lines_helper.rb"
@@ -249,13 +259,27 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/assets/stylesheets/posts.css"
end
- def test_scaffold_generator_no_assets_with_switch_resource_route_false
+ def test_scaffold_generator_with_switch_resource_route_false
run_generator [ "posts", "--resource-route=false" ]
assert_file "config/routes.rb" do |route|
assert_no_match(/resources :posts$/, route)
end
end
+ def test_scaffold_generator_no_helper_with_switch_no_helper
+ output = run_generator [ "posts", "--no-helper" ]
+
+ assert_no_match(/error/, output)
+ assert_no_file "app/helpers/posts_helper.rb"
+ end
+
+ def test_scaffold_generator_no_helper_with_switch_helper_false
+ output = run_generator [ "posts", "--helper=false" ]
+
+ assert_no_match(/error/, output)
+ assert_no_file "app/helpers/posts_helper.rb"
+ end
+
def test_scaffold_generator_no_stylesheets
run_generator [ "posts", "--no-stylesheets" ]
assert_no_file "app/assets/stylesheets/scaffold.css"
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index bf2992005b..39e8a5f756 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -143,6 +143,7 @@ module TestHelpers
config.active_support.deprecation = :log
config.active_support.test_order = :random
config.action_controller.allow_forgery_protection = false
+ config.log_level = :info
RUBY
end
@@ -162,6 +163,8 @@ module TestHelpers
app.secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, key: "_myapp_session"
app.config.active_support.deprecation = :log
+ app.config.active_support.test_order = :random
+ app.config.log_level = :info
yield app if block_given?
app.initialize!
@@ -335,6 +338,16 @@ class ActiveSupport::TestCase
end
end
end
+
+ def silence_stream(stream)
+ old_stream = stream.dup
+ stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/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/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 8d61af4972..d336a2e6c0 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'mocha/setup' # FIXME: stop using mocha
module ActionController
class Base
@@ -15,26 +14,26 @@ class InfoControllerTest < ActionController::TestCase
get '/rails/info/properties' => "rails/info#properties"
get '/rails/info/routes' => "rails/info#routes"
end
- @controller.stubs(:local_request? => true)
@routes = Rails.application.routes
Rails::InfoController.send(:include, @routes.url_helpers)
+
+ @request.env["REMOTE_ADDR"] = "127.0.0.1"
end
test "info controller does not allow remote requests" do
- @controller.stubs(local_request?: false)
+ @request.env["REMOTE_ADDR"] = "example.org"
get :properties
assert_response :forbidden
end
test "info controller renders an error message when request was forbidden" do
- @controller.stubs(local_request?: false)
+ @request.env["REMOTE_ADDR"] = "example.org"
get :properties
assert_select 'p'
end
test "info controller allows requests when all requests are considered local" do
- @controller.stubs(local_request?: true)
get :properties
assert_response :success
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 6239af2066..6185742cc1 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -498,17 +498,12 @@ YAML
boot_rails
initializers = Rails.application.initializers.tsort
- index = initializers.index { |i| i.name == "dummy_initializer" }
- selection = initializers[(index-3)..(index)].map(&:name).map(&:to_s)
+ dummy_index = initializers.index { |i| i.name == "dummy_initializer" }
+ config_index = initializers.rindex { |i| i.name == :load_config_initializers }
+ stack_index = initializers.index { |i| i.name == :build_middleware_stack }
- assert_equal %w(
- load_config_initializers
- load_config_initializers
- engines_blank_point
- dummy_initializer
- ), selection
-
- assert index < initializers.index { |i| i.name == :build_middleware_stack }
+ assert config_index < dummy_index
+ assert dummy_index < stack_index
end
class Upcaser
@@ -518,7 +513,7 @@ YAML
def call(env)
response = @app.call(env)
- response[2].each { |b| b.upcase! }
+ response[2].each(&:upcase!)
response
end
end
@@ -746,8 +741,8 @@ YAML
assert_equal "bukkits_", Bukkits.table_name_prefix
assert_equal "bukkits", Bukkits::Engine.engine_name
assert_equal Bukkits.railtie_namespace, Bukkits::Engine
- assert ::Bukkits::MyMailer.method_defined?(:foo_path)
- assert !::Bukkits::MyMailer.method_defined?(:bar_path)
+ assert ::Bukkits::MyMailer.method_defined?(:foo_url)
+ assert !::Bukkits::MyMailer.method_defined?(:bar_url)
get("/bukkits/from_app")
assert_equal "false", last_response.body
@@ -1210,7 +1205,7 @@ YAML
test "engine can be properly mounted at root" do
add_to_config("config.action_dispatch.show_exceptions = false")
- add_to_config("config.serve_static_assets = false")
+ add_to_config("config.serve_static_files = false")
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
diff --git a/tasks/release.rb b/tasks/release.rb
index de05dfad99..d8c1390eef 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,4 +1,4 @@
-FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack actionmailer railties activejob )
+FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer railties )
root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip
@@ -102,7 +102,7 @@ namespace :all do
abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed"
end
- unless ENV['SKIP_TAG'] || `git tag | grep '^#{tag}$`.strip.empty?
+ unless ENV['SKIP_TAG'] || `git tag | grep '^#{tag}$'`.strip.empty?
abort "[ABORTING] `git tag` shows that #{tag} already exists. Has this version already\n"\
" been released? Git tagging can be skipped by setting SKIP_TAG=1"
end
diff --git a/version.rb b/version.rb
index 8abed99f2c..7d74b1bfe5 100644
--- a/version.rb
+++ b/version.rb
@@ -5,10 +5,10 @@ module Rails
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end